Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 79 additions & 63 deletions parley/src/layout/line_break.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use crate::{
use core::ops::Range;
use parley_engine::Atom;
use parley_engine::shape::{Character, Whitespace};
use smallvec::SmallVec;

#[derive(Default)]
struct LineLayout {
Expand Down Expand Up @@ -102,16 +101,16 @@ impl LineState {
/// parent-relative `vertical-align` are first aligned to each other relative to the baseline of
/// their parent span; each chain of such boxes forms an [aligned subtree], rooted at the root span
/// box or at a box with `vertical-align: top | bottom`. Each subtree's extents are tracked
/// relative to its own baseline in [`Self::subtrees`]; the subtrees are only positioned against
/// each other once the line is complete (see `BreakLines::finish_line`).
/// relative to its own baseline: the root subtree in [`Self::root`], the others in
/// [`BreakerState::subtrees`]. The subtrees are only positioned against each other once the line
/// is complete (see `BreakLines::finish_line`).
/// See <https://www.w3.org/TR/CSS22/visudet.html#line-height>.
///
/// [aligned subtree]: crate::layout::style_metrics#aligned-subtrees
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
struct LineBoxMetrics {
/// Extents of each aligned subtree with content on this line. The first entry is always the
/// root subtree (root style index `0`).
subtrees: SmallVec<[SubtreeExtents; 2]>,
/// Extents of the root aligned subtree (root style index `0`).
root: SubtreeExtents,
/// Height of the tallest `vertical-align: top` inline box, which is positioned against the
/// line box rather than a baseline.
line_relative_top_height: f32,
Expand Down Expand Up @@ -207,10 +206,8 @@ impl SubtreeExtents {
impl Default for LineBoxMetrics {
/// An empty line containing only the root aligned subtree, without a strut.
fn default() -> Self {
let mut subtrees = SmallVec::new();
subtrees.push(SubtreeExtents::new(0));
Self {
subtrees,
root: SubtreeExtents::new(0),
line_relative_top_height: 0.,
line_relative_bottom_height: 0.,
has_content: false,
Expand All @@ -222,49 +219,36 @@ impl Default for LineBoxMetrics {
impl LineBoxMetrics {
/// Reset to an empty line.
fn reset(&mut self) {
let Self {
subtrees,
line_relative_top_height,
line_relative_bottom_height,
has_content,
last_text,
} = Self::default();
self.subtrees.clear();
self.subtrees.extend(subtrees);
self.line_relative_top_height = line_relative_top_height;
self.line_relative_bottom_height = line_relative_bottom_height;
self.has_content = has_content;
self.last_text = last_text;
}

/// The extents of the root aligned subtree.
fn root(&self) -> &SubtreeExtents {
&self.subtrees[0]
*self = Self::default();
}

/// The extents of the aligned subtree rooted at `root`. Non-root subtrees are looked up in
/// (and added to) `subtrees`.
#[inline]
fn subtree_mut(&mut self, root: u16) -> &mut SubtreeExtents {
fn subtree_mut<'a>(
&'a mut self,
subtrees: &'a mut Vec<SubtreeExtents>,
root: u16,
) -> &'a mut SubtreeExtents {
if root == 0 {
return &mut self.subtrees[0];
return &mut self.root;
}
let index = match self.subtrees.iter().position(|s| s.root == root) {
let index = match subtrees.iter().position(|s| s.root == root) {
Some(index) => index,
None => {
self.subtrees.push(SubtreeExtents::new(root));
self.subtrees.len() - 1
subtrees.push(SubtreeExtents::new(root));
subtrees.len() - 1
}
};
&mut self.subtrees[index]
&mut subtrees[index]
}

/// The line height seen so far.
/// The line height seen so far, given the line's non-root aligned `subtrees`.
#[inline]
fn line_height(&self) -> f32 {
let mut height = self.root().line_box.height();
if self.subtrees.len() > 1 {
for subtree in &self.subtrees[1..] {
height = height.max(subtree.line_box.height());
}
fn line_height(&self, subtrees: &[SubtreeExtents]) -> f32 {
let mut height = self.root.line_box.height();
for subtree in subtrees {
height = height.max(subtree.line_box.height());
}
height
.max(self.line_relative_top_height)
Expand All @@ -278,14 +262,15 @@ impl LineBoxMetrics {
style_index: u16,
style_metrics: &[StyleMetrics],
contributed: &mut Vec<u16>,
subtrees: &mut Vec<SubtreeExtents>,
) {
let mut index = style_index;
while !contributed.contains(&index) {
contributed.push(index);
let Some(metrics) = style_metrics.get(usize::from(index)) else {
return;
};
let subtree = self.subtree_mut(metrics.aligned_subtree);
let subtree = self.subtree_mut(subtrees, metrics.aligned_subtree);
subtree
.line_box
.add(metrics.baseline_offset, metrics.over, metrics.under);
Expand Down Expand Up @@ -319,6 +304,7 @@ impl LineBoxMetrics {
characters: &[Character],
data: &LayoutData<B>,
contributed: &mut Vec<u16>,
subtrees: &mut Vec<SubtreeExtents>,
) {
self.has_content = true;
// Consecutive atoms almost always come from the same run and style, whose boxes are then
Expand All @@ -334,6 +320,7 @@ impl LineBoxMetrics {
characters,
data,
contributed,
subtrees,
);
}

Expand All @@ -349,16 +336,22 @@ impl LineBoxMetrics {
characters: &[Character],
data: &LayoutData<B>,
contributed: &mut Vec<u16>,
subtrees: &mut Vec<SubtreeExtents>,
) {
self.last_text = (item_idx, style_index);
if contributed.last() != Some(&style_index) {
self.add_style(style_index, &data.style_metrics, contributed);
self.add_style(style_index, &data.style_metrics, contributed, subtrees);
}
if data.runs[run_idx].has_mixed_style_atoms {
// Add the spans of all the atom's other styles.
for character in characters.iter().skip(1) {
if character.style_index != style_index {
self.add_style(character.style_index, &data.style_metrics, contributed);
self.add_style(
character.style_index,
&data.style_metrics,
contributed,
subtrees,
);
}
}
}
Expand All @@ -374,7 +367,7 @@ impl LineBoxMetrics {
.style_metrics
.get(style)
.map_or((0., 0), |m| (m.baseline_offset, m.aligned_subtree));
let subtree = self.subtree_mut(aligned_subtree);
let subtree = self.subtree_mut(subtrees, aligned_subtree);
subtree
.line_box
.add(baseline_offset, run_box.over, run_box.under);
Expand All @@ -387,6 +380,7 @@ impl LineBoxMetrics {
/// `baseline_offset` above the baseline of the `aligned_subtree` root.
fn add_inline_box(
&mut self,
subtrees: &mut Vec<SubtreeExtents>,
aligned_subtree: u16,
baseline_offset: f32,
ascent: f32,
Expand All @@ -399,7 +393,7 @@ impl LineBoxMetrics {
if (ascent != 0. || descent != 0.) && ascent.is_finite() && descent.is_finite() {
self.has_content = true;
}
let subtree = self.subtree_mut(aligned_subtree);
let subtree = self.subtree_mut(subtrees, aligned_subtree);
subtree.line_box.add(baseline_offset, ascent, descent);
subtree.content_box.add(baseline_offset, ascent, descent);
}
Expand Down Expand Up @@ -427,6 +421,8 @@ struct PrevBoundaryState {
state: LineState,
/// Length of [`BreakerState::contributed`] at this opportunity.
contributed_len: usize,
/// Copy of [`BreakerState::subtrees`] at this opportunity.
subtrees: Vec<SubtreeExtents>,
}

/// Reason that the line breaker has yielded control flow
Expand Down Expand Up @@ -536,6 +532,10 @@ pub struct BreakerState {
/// [`LineBoxMetrics::add_style`]). Lives here rather than in [`LineState`] so that saving a
/// line-breaking opportunity only records its length; reverting truncates it back.
contributed: Vec<u16>,
/// Extents of the non-root aligned subtrees (rooted at a `vertical-align: top | bottom` box)
/// with content on the current line. Lives here rather than in [`LineState`] so that
/// [`LineState`] stays cheap to copy; it is usually empty.
subtrees: Vec<SubtreeExtents>,

// Saved breaker states for reverting to a previously encountered line-breaking opportunity
/// Saved breaker state for the last non-emergency line-breaking opportunity
Expand All @@ -559,6 +559,7 @@ impl Default for BreakerState {
line_max_height: f32::MAX,
line: LineState::default(),
contributed: Vec::new(),
subtrees: Vec::new(),
prev_boundary: None,
emergency_boundary: None,
}
Expand Down Expand Up @@ -593,6 +594,7 @@ impl BreakerState {
atom.characters(),
data,
&mut self.contributed,
&mut self.subtrees,
);
self.update_max_height_exceeded();
}
Expand All @@ -606,7 +608,9 @@ impl BreakerState {
self.item_idx += 1;
self.line.items.end += 1;
self.line.x = next_x;
self.line.box_metrics.add_inline_box(0, 0., ascent, descent);
self.line
.box_metrics
.add_inline_box(&mut self.subtrees, 0, 0., ascent, descent);
self.update_max_height_exceeded();
}

Expand All @@ -630,6 +634,7 @@ impl BreakerState {
);
} else {
self.line.box_metrics.add_inline_box(
&mut self.subtrees,
placement.aligned_subtree,
placement.baseline_offset,
placement.ascent,
Expand All @@ -642,25 +647,25 @@ impl BreakerState {
/// Store the current iteration state so that we can revert to it if we later want to take
/// the line breaking opportunity at this point.
fn mark_line_break_opportunity(&mut self) {
self.prev_boundary = Some(PrevBoundaryState {
item_idx: self.item_idx,
run_idx: self.run_idx,
cluster_idx: self.cluster_idx,
state: self.line.clone(),
contributed_len: self.contributed.len(),
});
self.prev_boundary = Some(self.save_boundary());
}

/// Store the current iteration state so that we can revert to it if we later want to take
/// an *emergency* line breaking opportunity at this point.
fn mark_emergency_break_opportunity(&mut self) {
self.emergency_boundary = Some(PrevBoundaryState {
self.emergency_boundary = Some(self.save_boundary());
}

fn save_boundary(&self) -> PrevBoundaryState {
PrevBoundaryState {
item_idx: self.item_idx,
run_idx: self.run_idx,
cluster_idx: self.cluster_idx,
state: self.line.clone(),
contributed_len: self.contributed.len(),
});
// Doesn't allocate in the common case of no `top`/`bottom` subtrees on the line.
subtrees: self.subtrees.clone(),
}
}

/// Revert boundary state to prev state
Expand All @@ -670,12 +675,13 @@ impl BreakerState {
self.cluster_idx = prev_state.cluster_idx;
self.line = prev_state.state;
self.contributed.truncate(prev_state.contributed_len);
self.subtrees = prev_state.subtrees;
}

#[inline(always)]
fn update_max_height_exceeded(&mut self) {
self.line.max_height_exceeded = self.line_max_height != f32::MAX
&& self.line.box_metrics.line_height() > self.line_max_height;
&& self.line.box_metrics.line_height(&self.subtrees) > self.line_max_height;
}

/// Get the max-advance of the entire layout
Expand Down Expand Up @@ -771,10 +777,12 @@ impl<'a, B: Brush> BreakLines<'a, B> {
let state = &mut self.state;
state.line.reset();
state.contributed.clear();
state.subtrees.clear();
state.line.box_metrics.add_style(
0,
&self.layout.data.style_metrics,
&mut state.contributed,
&mut state.subtrees,
);
state.update_max_height_exceeded();
}
Expand Down Expand Up @@ -825,7 +833,10 @@ impl<'a, B: Brush> BreakLines<'a, B> {
let line_height = if invisible {
0.
} else {
self.state.line.box_metrics.line_height()
self.state
.line
.box_metrics
.line_height(&self.state.subtrees)
};
let line_y_start = self.state.line_y;

Expand Down Expand Up @@ -1425,8 +1436,13 @@ impl<'a, B: Brush> BreakLines<'a, B> {
&[],
&self.layout.data,
&mut self.state.contributed,
&mut self.state.subtrees,
);
line.metrics.line_height = self.state.line.box_metrics.line_height();
line.metrics.line_height = self
.state
.line
.box_metrics
.line_height(&self.state.subtrees);
self.lines.line_items.push(LineItemData {
kind: LayoutItemKind::TextRun,
index,
Expand All @@ -1448,14 +1464,14 @@ impl<'a, B: Brush> BreakLines<'a, B> {
(Extents::default().or_zero(), Extents::default().or_zero())
} else {
(
box_metrics.root().line_box.or_zero(),
box_metrics.root().content_box.or_zero(),
box_metrics.root.line_box.or_zero(),
box_metrics.root.content_box.or_zero(),
)
};

let mut top_height = box_metrics.line_relative_top_height;
let mut bottom_height = box_metrics.line_relative_bottom_height;
for subtree in &box_metrics.subtrees[1..] {
for subtree in &self.state.subtrees {
let height = subtree.line_box.height();
match self.layout.data.styles[usize::from(subtree.root)]
.vertical_align
Expand All @@ -1476,7 +1492,7 @@ impl<'a, B: Brush> BreakLines<'a, B> {

let offsets = &mut self.lines.aligned_subtree_offsets;
line.aligned_subtree_offsets.start = offsets.len() as u32;
for subtree in &box_metrics.subtrees[1..] {
for subtree in &self.state.subtrees {
let extents = subtree.line_box.or_zero();
let offset = match self.layout.data.styles[usize::from(subtree.root)]
.vertical_align
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading