use alloc::vec::Vec;
#[cfg(feature = "libm")]
#[allow(unused_imports)]
use core_maths::CoreFloat;
use parlance::BidiLevel;
use crate::layout::data::{AlignedSubtreeOffset, run_box_metrics};
use crate::layout::spacing::{EffectiveSpacing, Justification, is_word_separator};
use crate::layout::style_metrics::{
BoxMetrics, InlineBoxPlacement, StyleMetrics, inline_box_placement,
};
use crate::layout::whitespace::atom_hanging_advance;
use crate::layout::{
BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData,
LineMetrics, Run,
};
use crate::style::Brush;
use crate::{
BaselineShift, InlineBox, InlineBoxKind, OverflowWrap, TextWrapMode, VerticalAlign,
WhiteSpaceCollapse,
};
use core::ops::Range;
use parley_engine::Atom;
use parley_engine::shape::{Character, Whitespace};
use smallvec::SmallVec;
#[derive(Default)]
struct LineLayout {
lines: Vec<LineData>,
line_items: Vec<LineItemData>,
aligned_subtree_offsets: Vec<AlignedSubtreeOffset>,
}
impl LineLayout {
fn swap<B: Brush>(&mut self, layout: &mut LayoutData<B>) {
core::mem::swap(&mut self.lines, &mut layout.lines);
core::mem::swap(&mut self.line_items, &mut layout.line_items);
core::mem::swap(
&mut self.aligned_subtree_offsets,
&mut layout.aligned_subtree_offsets,
);
}
fn truncate(&mut self, len: usize) {
self.lines.truncate(len);
let offsets = self
.lines
.last()
.map_or(0, |line| line.aligned_subtree_offsets.end as usize);
self.aligned_subtree_offsets.truncate(offsets);
}
}
#[derive(Clone, Default)]
struct LineState {
x: f32,
items: Range<usize>,
clusters: Range<u32>,
box_metrics: LineBoxMetrics,
max_height_exceeded: bool,
text_wrap_mode: TextWrapMode,
num_word_separators: u32,
}
impl LineState {
fn reset(&mut self) {
self.x = 0.0;
self.box_metrics.reset();
self.num_word_separators = 0;
}
}
#[derive(Clone, Copy, Debug)]
struct LineBoxMetrics {
root: SubtreeExtents,
non_root_height: f32,
line_relative_top_height: f32,
line_relative_bottom_height: f32,
has_content: bool,
last_text: (usize, u16),
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct SubtreeExtents {
root: u16,
line_box: Extents,
content_box: Extents,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Extents {
over: f32,
under: f32,
}
impl Default for Extents {
fn default() -> Self {
Self {
over: f32::NEG_INFINITY,
under: f32::NEG_INFINITY,
}
}
}
impl Extents {
fn or_zero(self) -> Self {
if self.over == f32::NEG_INFINITY && self.under == f32::NEG_INFINITY {
Self {
over: 0.,
under: 0.,
}
} else {
self
}
}
#[inline(always)]
fn add(&mut self, baseline_offset: f32, over: f32, under: f32) {
self.over = self.over.max(baseline_offset + over);
self.under = self.under.max(under - baseline_offset);
}
fn height(self) -> f32 {
let this = self.or_zero();
this.over + this.under
}
}
impl SubtreeExtents {
fn new(root: u16) -> Self {
Self {
root,
line_box: Extents::default(),
content_box: Extents::default(),
}
}
#[inline(always)]
fn add(&mut self, baseline_offset: f32, metrics: BoxMetrics) {
self.line_box
.add(baseline_offset, metrics.over, metrics.under);
self.content_box
.add(baseline_offset, metrics.ascent, metrics.descent);
}
}
#[derive(Clone, Default)]
struct SubtreeHistory {
entries: Vec<SubtreeExtents>,
frozen_count: usize,
}
impl SubtreeHistory {
fn current(&self) -> SmallVec<[SubtreeExtents; 4]> {
let mut current = SmallVec::<[SubtreeExtents; 4]>::new();
for entry in self.entries.iter().rev() {
if current.iter().rev().all(|s| s.root != entry.root) {
current.push(*entry);
}
}
current
}
fn grow(&mut self, root: u16, baseline_offset: f32, metrics: BoxMetrics) -> SubtreeExtents {
let index = self.entries.iter().rposition(|s| s.root == root);
let old = index.map(|i| self.entries[i]);
let mut new = old.unwrap_or(SubtreeExtents::new(root));
new.add(baseline_offset, metrics);
if old != Some(new) {
match index {
Some(i) if i >= self.frozen_count => self.entries[i] = new,
_ => self.entries.push(new),
}
}
new
}
fn save(&mut self) -> usize {
self.frozen_count = self.entries.len();
self.frozen_count
}
fn restore(&mut self, len: usize) {
self.entries.truncate(len);
self.frozen_count = len;
}
fn clear(&mut self) {
self.entries.clear();
self.frozen_count = 0;
}
}
impl Default for LineBoxMetrics {
fn default() -> Self {
Self {
root: SubtreeExtents::new(0),
non_root_height: 0.,
line_relative_top_height: 0.,
line_relative_bottom_height: 0.,
has_content: false,
last_text: (usize::MAX, 0),
}
}
}
impl LineBoxMetrics {
fn reset(&mut self) {
*self = Self::default();
}
#[inline]
fn add_box(
&mut self,
aligned_subtree_root: u16,
baseline_offset: f32,
metrics: BoxMetrics,
subtrees: &mut SubtreeHistory,
) {
if aligned_subtree_root == 0 {
self.root.add(baseline_offset, metrics);
} else {
let subtree = subtrees.grow(aligned_subtree_root, baseline_offset, metrics);
self.non_root_height = self.non_root_height.max(subtree.line_box.height());
}
}
#[inline]
fn line_height(&self) -> f32 {
self.root
.line_box
.height()
.max(self.non_root_height)
.max(self.line_relative_top_height)
.max(self.line_relative_bottom_height)
}
fn add_style(
&mut self,
style_index: u16,
style_metrics: &[StyleMetrics],
contributed: &mut Vec<u16>,
subtrees: &mut SubtreeHistory,
) {
let mut index = style_index;
while !contributed.contains(&index) {
contributed.push(index);
let Some(metrics) = style_metrics.get(usize::from(index)) else {
return;
};
let span_box = BoxMetrics {
ascent: metrics.ascent,
descent: metrics.descent,
over: metrics.over,
under: metrics.under,
};
self.add_box(
metrics.aligned_subtree_root,
metrics.baseline_offset,
span_box,
subtrees,
);
if index == 0 {
return;
}
index = metrics.parent;
}
}
#[inline]
fn add_text<B: Brush>(
&mut self,
item_idx: usize,
run_idx: usize,
style_index: u16,
characters: &[Character],
data: &LayoutData<B>,
contributed: &mut Vec<u16>,
subtrees: &mut SubtreeHistory,
) {
self.has_content = true;
if self.last_text == (item_idx, style_index) && !data.runs[run_idx].has_mixed_style_atoms {
return;
}
self.add_text_boxes(
item_idx,
run_idx,
style_index,
characters,
data,
contributed,
subtrees,
);
}
#[inline(never)]
fn add_text_boxes<B: Brush>(
&mut self,
item_idx: usize,
run_idx: usize,
style_index: u16,
characters: &[Character],
data: &LayoutData<B>,
contributed: &mut Vec<u16>,
subtrees: &mut SubtreeHistory,
) {
self.last_text = (item_idx, style_index);
if contributed.last() != Some(&style_index) {
self.add_style(style_index, &data.style_metrics, contributed, subtrees);
}
if data.runs[run_idx].has_mixed_style_atoms {
for character in characters.iter().skip(1) {
if character.style_index != style_index {
self.add_style(
character.style_index,
&data.style_metrics,
contributed,
subtrees,
);
}
}
}
let style = usize::from(style_index);
let shaped_run = &data.shaped_text.runs()[run_idx];
let Some(run_box) = data.runs[run_idx]
.run_box
.or_else(|| run_box_metrics(&data.styles[style], shaped_run, data.quantize))
else {
return;
};
let (baseline_offset, aligned_subtree_root) = data
.style_metrics
.get(style)
.map_or((0., 0), |m| (m.baseline_offset, m.aligned_subtree_root));
self.add_box(aligned_subtree_root, baseline_offset, run_box, subtrees);
}
fn add_inline_box(
&mut self,
aligned_subtree_root: u16,
baseline_offset: f32,
ascent: f32,
descent: f32,
subtrees: &mut SubtreeHistory,
) {
if (ascent != 0. || descent != 0.) && ascent.is_finite() && descent.is_finite() {
self.has_content = true;
}
let inline_box = BoxMetrics {
ascent,
descent,
over: ascent,
under: descent,
};
self.add_box(aligned_subtree_root, baseline_offset, inline_box, subtrees);
}
fn add_line_relative_inline_box(&mut self, align: VerticalAlign, height: f32, quantize: bool) {
let height = if quantize { height.round() } else { height };
if height > 0. {
self.has_content = true;
}
let slot = match align.shift {
BaselineShift::Bottom => &mut self.line_relative_bottom_height,
_ => &mut self.line_relative_top_height,
};
*slot = slot.max(height);
}
}
#[derive(Clone)]
struct PrevBoundaryState {
item_idx: usize,
run_idx: usize,
cluster_idx: u32,
state: LineState,
contributed_len: usize,
subtrees_len: usize,
}
#[derive(Clone, Debug)]
pub enum YieldData {
LineBreak(LineBreakData),
MaxHeightExceeded(MaxHeightBreakData),
InlineBoxBreak(BoxBreakData),
}
#[derive(Clone, Debug)]
pub struct LineBreakData {
pub reason: BreakReason,
pub metrics: LineMetrics,
pub line_y_start: f64,
pub line_y_end: f64,
}
#[derive(Clone, Debug)]
pub struct MaxHeightBreakData {
pub advance: f32,
pub line_height: f32,
}
#[derive(Clone, Debug)]
pub struct BoxBreakData {
pub inline_box_id: u64,
pub inline_box_index: usize,
pub advance: f32,
}
#[derive(Clone)]
pub struct BreakerState {
items: usize,
lines: usize,
item_idx: usize,
run_idx: usize,
cluster_idx: u32,
line_x: f32,
line_y: f64,
layout_max_advance: f32,
line_max_advance: f32,
line_max_height: f32,
line: LineState,
contributed: Vec<u16>,
subtrees: SubtreeHistory,
prev_boundary: Option<PrevBoundaryState>,
emergency_boundary: Option<PrevBoundaryState>,
}
impl Default for BreakerState {
fn default() -> Self {
Self {
items: 0,
lines: 0,
item_idx: 0,
run_idx: 0,
cluster_idx: 0,
line_x: 0.0,
line_y: 0.0,
layout_max_advance: 0.0,
line_max_advance: 0.0,
line_max_height: f32::MAX,
line: LineState::default(),
contributed: Vec::new(),
subtrees: SubtreeHistory::default(),
prev_boundary: None,
emergency_boundary: None,
}
}
}
impl BreakerState {
#[inline]
fn append_atom_to_line<B: Brush>(
&mut self,
atom: &Atom<'_>,
next_x: f32,
style_index: u16,
data: &LayoutData<B>,
is_word_separator: bool,
) {
self.line.items.end = self.item_idx + 1;
self.line.clusters.end = atom.shaped_clusters_range().end;
self.cluster_idx = atom.shaped_clusters_range().end;
self.line.x = next_x;
self.line.num_word_separators += u32::from(is_word_separator);
self.line.box_metrics.add_text(
self.item_idx,
self.run_idx,
style_index,
atom.characters(),
data,
&mut self.contributed,
&mut self.subtrees,
);
self.update_max_height_exceeded();
}
pub fn append_inline_box_to_line(&mut self, next_x: f32, ascent: f32, descent: f32) {
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, &mut self.subtrees);
self.update_max_height_exceeded();
}
fn append_aligned_inline_box_to_line(
&mut self,
next_x: f32,
inline_box: &InlineBox,
placement: InlineBoxPlacement,
quantize: bool,
) {
self.item_idx += 1;
self.line.items.end += 1;
self.line.x = next_x;
if inline_box.vertical_align.is_line_relative() {
self.line.box_metrics.add_line_relative_inline_box(
inline_box.vertical_align,
inline_box.height,
quantize,
);
} else {
self.line.box_metrics.add_inline_box(
placement.aligned_subtree_root,
placement.baseline_offset,
placement.ascent,
placement.descent,
&mut self.subtrees,
);
}
self.update_max_height_exceeded();
}
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(),
subtrees_len: self.subtrees.save(),
});
}
fn mark_emergency_break_opportunity(&mut self) {
self.emergency_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(),
subtrees_len: self.subtrees.save(),
});
}
fn reset_to(&mut self, prev_state: PrevBoundaryState) {
self.item_idx = prev_state.item_idx;
self.run_idx = prev_state.run_idx;
self.cluster_idx = prev_state.cluster_idx;
self.line = prev_state.state;
self.contributed.truncate(prev_state.contributed_len);
self.subtrees.restore(prev_state.subtrees_len);
}
#[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;
}
#[inline(always)]
pub fn layout_max_advance(&self) -> f32 {
self.layout_max_advance
}
#[inline(always)]
pub fn set_layout_max_advance(&mut self, advance: f32) {
self.layout_max_advance = advance;
}
#[inline(always)]
pub fn line_max_advance(&self) -> f32 {
self.line_max_advance
}
#[inline(always)]
pub fn set_line_max_advance(&mut self, advance: f32) {
self.line_max_advance = advance;
}
#[inline(always)]
pub fn line_max_height(&self) -> f32 {
self.line_max_height
}
#[inline(always)]
pub fn set_line_max_height(&mut self, height: f32) {
self.line_max_height = height;
self.update_max_height_exceeded();
}
#[inline(always)]
pub fn line_x(&self) -> f32 {
self.line_x
}
#[inline(always)]
pub fn set_line_x(&mut self, x: f32) {
self.line_x = x;
}
#[inline(always)]
pub fn line_y(&self) -> f64 {
self.line_y
}
#[inline(always)]
pub fn set_line_y(&mut self, y: f64) {
self.line_y = y;
}
}
pub struct BreakLines<'a, B: Brush> {
layout: &'a mut Layout<B>,
lines: LineLayout,
state: BreakerState,
prev_state: Option<BreakerState>,
done: bool,
}
impl<'a, B: Brush> BreakLines<'a, B> {
pub(crate) fn new(layout: &'a mut Layout<B>) -> Self {
layout.data.width = 0.;
layout.data.height = 0.;
let mut lines = LineLayout::default();
lines.swap(&mut layout.data);
lines.lines.clear();
lines.line_items.clear();
lines.aligned_subtree_offsets.clear();
let mut this = Self {
layout,
lines,
state: BreakerState::default(),
prev_state: None,
done: false,
};
this.reset_line();
this
}
fn reset_line(&mut self) {
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();
}
fn append_layout_inline_box(&mut self, index: usize, next_x: f32) {
let quantize = self.layout.data.quantize;
let layout_box = &mut self.layout.data.inline_boxes[index];
let inline_box = &layout_box.inline_box;
if inline_box.kind == InlineBoxKind::InFlow {
let placement = inline_box_placement(
inline_box,
layout_box.parent_style_index,
&self.layout.data.style_metrics,
quantize,
);
layout_box.baseline_offset = placement.baseline_offset;
self.state
.append_aligned_inline_box_to_line(next_x, inline_box, placement, quantize);
} else {
self.state
.append_inline_box_to_line(next_x, f32::NEG_INFINITY, f32::NEG_INFINITY);
}
}
fn start_new_line(
&mut self,
reason: BreakReason,
max_advance: f32,
line_indent: f32,
) -> Option<YieldData> {
commit_line(
self.layout,
&mut self.lines,
&mut self.state.line,
max_advance,
reason,
line_indent,
);
let invisible = !self.state.line.box_metrics.has_content
&& !self.lines.lines.last().unwrap().item_range.is_empty();
let line_height = if invisible {
0.
} else {
self.state.line.box_metrics.line_height()
};
let line_y_start = self.state.line_y;
self.state.items = self.lines.line_items.len();
self.state.lines = self.lines.lines.len();
self.state.prev_boundary = None;
self.state.emergency_boundary = None;
let line_height = self.finish_line(self.lines.lines.len() - 1, line_height, invisible);
self.reset_line();
self.state.line_y += line_height as f64;
Some(YieldData::LineBreak(
self.last_line_data(reason, line_y_start),
))
}
#[inline(always)]
fn last_line_data(&self, reason: BreakReason, line_y_start: f64) -> LineBreakData {
LineBreakData {
reason,
metrics: self.lines.lines.last().unwrap().metrics,
line_y_start,
line_y_end: self.state.line_y,
}
}
#[inline(always)]
fn max_height_break_data(&self, line_height: f32) -> Option<YieldData> {
Some(YieldData::MaxHeightExceeded(MaxHeightBreakData {
advance: self.state.line.x,
line_height,
}))
}
#[inline(always)]
pub fn state(&self) -> &BreakerState {
&self.state
}
#[inline(always)]
pub fn state_mut(&mut self) -> &mut BreakerState {
&mut self.state
}
#[doc(hidden)]
pub fn set_prior_line_width(&mut self, advance: f32) {
if let Some(line) = self.lines.lines.last_mut() {
line.metrics.inline_max_coord = line.metrics.inline_min_coord + advance;
}
}
pub fn revert_to(&mut self, state: BreakerState) {
self.state = state;
self.lines.truncate(self.state.lines);
self.lines.line_items.truncate(self.state.items);
self.done = false;
}
#[inline(always)]
pub fn revert(&mut self) -> bool {
if let Some(state) = self.prev_state.take() {
self.revert_to(state);
true
} else {
false
}
}
#[inline(always)]
pub fn committed_y(&self) -> f64 {
self.state.line_y
}
#[inline(always)]
pub fn is_done(&self) -> bool {
self.done
}
pub fn break_next(&mut self) -> Option<YieldData> {
assert!(
self.state.layout_max_advance == f32::INFINITY
|| self.state.line_max_advance - self.state.layout_max_advance < 1.0
);
if self.done {
return None;
}
match &mut self.prev_state {
Some(prev_state) => prev_state.clone_from(&self.state),
None => self.prev_state = Some(self.state.clone()),
}
let max_advance =
if self.layout.data.text_len == 0 && self.layout.data.inline_boxes.is_empty() {
f32::MAX
} else {
self.state.line_max_advance
};
let line_indent = self.resolve_indent();
let max_advance = max_advance - line_indent;
let item_count = self.layout.data.items.len();
while self.state.item_idx < item_count {
let item = &self.layout.data.items[self.state.item_idx];
match item.kind {
LayoutItemKind::InlineBox => {
let inline_box = &self.layout.data.inline_boxes[item.index].inline_box;
let (width_contribution, height_contribution) = match inline_box.kind {
InlineBoxKind::InFlow => (inline_box.width, inline_box.height),
InlineBoxKind::OutOfFlow => {
self.append_layout_inline_box(item.index, self.state.line.x);
continue;
}
InlineBoxKind::CustomOutOfFlow => {
return Some(YieldData::InlineBoxBreak(BoxBreakData {
inline_box_id: inline_box.id,
inline_box_index: item.index,
advance: self.state.line.x,
}));
}
};
let next_x = self.state.line.x + width_contribution;
let box_will_be_appended = next_x <= max_advance || self.state.line.x == 0.0;
if height_contribution > self.state.line_max_height && box_will_be_appended {
return self.max_height_break_data(height_contribution);
}
if next_x <= max_advance || self.state.line.text_wrap_mode != TextWrapMode::Wrap
{
self.append_layout_inline_box(item.index, next_x);
if self.state.line.text_wrap_mode == TextWrapMode::Wrap {
self.state.mark_line_break_opportunity();
}
} else {
let reason = if self.state.line.x == 0.0 {
self.append_layout_inline_box(item.index, next_x);
BreakReason::Emergency
} else {
BreakReason::Regular
};
return self.start_new_line(reason, max_advance, line_indent);
}
}
LayoutItemKind::TextRun => {
let run_idx = item.index;
let run = Run::new(self.layout, 0, 0, run_idx, None);
let slice = run.full_slice();
let spacing = EffectiveSpacing::new(run.data.spacing, Justification::NONE);
let line_height = run.data.line_height;
for atom in slice.atoms_from(self.state.cluster_idx) {
let first_character = &atom.characters()[0];
let whitespace = first_character.whitespace;
let is_newline = whitespace == Whitespace::Newline;
let is_separator = is_word_separator(whitespace);
let is_soft_wrap_opportunity =
first_character.flags.is_soft_wrap_opportunity();
let max_height_exceeded = self.state.line.max_height_exceeded;
let style_index = first_character.style_index;
let style = &self.layout.data.styles[style_index as usize];
let text_wrap_mode = self.state.line.text_wrap_mode;
self.state.line.text_wrap_mode = style.text_wrap_mode;
if is_newline {
if max_height_exceeded {
return self.max_height_break_data(line_height);
}
self.state.append_atom_to_line(
&atom,
self.state.line.x,
style_index,
&self.layout.data,
is_separator,
);
return self.start_new_line(
BreakReason::Explicit,
max_advance,
line_indent,
);
} else if is_soft_wrap_opportunity && text_wrap_mode == TextWrapMode::Wrap {
if self.state.line.x != 0.0 {
self.state.mark_line_break_opportunity();
}
} else if
style.overflow_wrap != OverflowWrap::Normal
&& text_wrap_mode == TextWrapMode::Wrap
&& self.state.line.x != 0.0
{
self.state.mark_emergency_break_opportunity();
}
let advance = spacing.atom_advance(&atom);
let next_x = self.state.line.x + advance;
if next_x <= max_advance {
if max_height_exceeded {
return self.max_height_break_data(line_height);
}
self.state.append_atom_to_line(
&atom,
next_x,
style_index,
&self.layout.data,
is_separator,
);
}
else {
let (hanging, all_hang) = atom_hanging_advance(
slice,
&atom,
&self.layout.data.styles,
spacing,
item.bidi_level.is_rtl(),
);
if all_hang || next_x - hanging <= max_advance {
if max_height_exceeded {
return self.max_height_break_data(line_height);
}
self.state.append_atom_to_line(
&atom,
next_x,
style_index,
&self.layout.data,
is_separator,
);
}
else if let Some(prev) = self.state.prev_boundary.take() {
self.state.reset_to(prev);
return self.start_new_line(
BreakReason::Regular,
max_advance,
line_indent,
);
}
else if let Some(prev_emergency) =
self.state.emergency_boundary.take()
{
self.state.reset_to(prev_emergency);
return self.start_new_line(
BreakReason::Emergency,
max_advance,
line_indent,
);
}
else {
if max_height_exceeded {
return self.max_height_break_data(line_height);
}
self.state.append_atom_to_line(
&atom,
next_x,
style_index,
&self.layout.data,
is_separator,
);
}
}
}
self.state.run_idx += 1;
self.state.item_idx += 1;
}
}
}
if self.state.line.items.end == 0 {
self.state.line.items.end = 1;
}
self.done = true;
self.start_new_line(BreakReason::None, max_advance, line_indent)
}
pub fn break_next_with_length(&mut self, max_chars: u32) -> Option<()> {
if self.done {
return None;
}
let line_indent = self.resolve_indent();
let mut char_count: u32 = 0;
let item_count = self.layout.data.items.len();
while self.state.item_idx < item_count {
let item = &self.layout.data.items[self.state.item_idx];
match item.kind {
LayoutItemKind::InlineBox => {
let inline_box = &self.layout.data.inline_boxes[item.index].inline_box;
if inline_box.kind != InlineBoxKind::InFlow {
self.append_layout_inline_box(item.index, self.state.line.x);
continue;
}
if char_count >= max_chars && max_chars != 0 {
self.start_new_line(BreakReason::Regular, f32::MAX, line_indent);
return Some(());
}
let next_x = self.state.line.x + inline_box.width;
self.append_layout_inline_box(item.index, next_x);
char_count += 1;
if char_count >= max_chars {
let is_last_item = self.state.item_idx >= self.layout.data.items.len();
let break_reason = if is_last_item {
BreakReason::None
} else {
BreakReason::Regular
};
if break_reason == BreakReason::None {
self.done = true;
}
self.start_new_line(break_reason, f32::MAX, line_indent);
return Some(());
}
}
LayoutItemKind::TextRun => {
let run_idx = item.index;
let shaped_run = &self.layout.data.shaped_text.runs()[run_idx];
let run = Run::new(self.layout, 0, 0, run_idx, None);
let slice = run.full_slice();
let spacing = run.line_spacing();
let cluster_end = shaped_run.shaped_clusters_range.end;
for atom in slice.atoms_from(self.state.cluster_idx) {
if char_count >= max_chars && max_chars != 0 {
self.start_new_line(BreakReason::Regular, f32::MAX, line_indent);
return Some(());
}
let first_character = &atom.characters()[0];
let whitespace = first_character.whitespace;
let is_newline = whitespace == Whitespace::Newline;
let is_separator = is_word_separator(whitespace);
let advance = spacing.atom_advance(&atom);
let next_x = if is_newline {
self.state.line.x
} else {
self.state.line.x + advance
};
self.state.append_atom_to_line(
&atom,
next_x,
first_character.style_index,
&self.layout.data,
is_separator,
);
char_count += atom.char_range().len() as u32;
if char_count >= max_chars {
let is_last_cluster_of_run = self.state.cluster_idx >= cluster_end;
let is_last_item =
self.state.item_idx + 1 >= self.layout.data.items.len();
let break_reason = if is_last_cluster_of_run && is_last_item {
BreakReason::None
} else if is_newline {
BreakReason::Explicit
} else {
BreakReason::Regular
};
if break_reason == BreakReason::None {
self.done = true;
}
self.start_new_line(break_reason, f32::MAX, line_indent);
return Some(());
}
}
self.state.run_idx += 1;
self.state.item_idx += 1;
}
}
}
if self.state.line.items.end == 0 {
self.state.line.items.end = 1;
}
self.done = true;
self.start_new_line(BreakReason::None, f32::MAX, line_indent);
Some(())
}
pub fn break_remaining(mut self, max_advance: f32) {
self.state.layout_max_advance = max_advance;
self.state.line_max_advance = max_advance;
while let Some(yield_data) = self.break_next() {
if let YieldData::InlineBoxBreak(_) = yield_data {
self.state
.append_inline_box_to_line(self.state.line.x, 0.0, 0.0);
}
}
self.finish();
}
pub fn finish(mut self) {
if self.layout.data.text_len == 0
&& let Some(line) = self.lines.line_items.first_mut()
{
line.text_range = 0..0;
line.shaped_cluster_range = 0..0;
}
}
#[inline]
fn resolve_indent(&self) -> f32 {
let options = self.layout.data.indent_options;
let amount = self.layout.data.indent_amount;
match self.lines.lines.last().map(|line| line.break_reason) {
None => options.indent_for_first_line(amount),
Some(BreakReason::Explicit) => options.indent_following_forced_line_break(amount),
Some(_) => options.indent_following_soft_wrap_break(amount),
}
}
fn finish_line(&mut self, line_idx: usize, line_height: f32, invisible: bool) -> f32 {
let line = &mut self.lines.lines[line_idx];
line.metrics.offset = 0.;
line.metrics.line_height = line_height;
if line.item_range.is_empty() {
line.text_range = self.layout.data.text_len..self.layout.data.text_len;
}
let quantize = self.layout.data.quantize;
if line.item_range.is_empty() && line_idx > 0 {
if let Some((index, run)) = self
.layout
.data
.shaped_text
.runs()
.iter()
.enumerate()
.rfind(|(_, run)| !run.range.byte_range.is_empty())
{
let run_index = self.lines.line_items.len();
let cluster = run.shaped_clusters_range.end;
let text = run.range.byte_range.end;
let style_index = self.layout.data.shaped_text.shaped_clusters()
[cluster as usize - 1]
.style_index;
self.state.line.box_metrics.add_text(
index,
index,
style_index,
&[],
&self.layout.data,
&mut self.state.contributed,
&mut self.state.subtrees,
);
line.metrics.line_height = self.state.line.box_metrics.line_height();
self.lines.line_items.push(LineItemData {
kind: LayoutItemKind::TextRun,
index,
bidi_level: BidiLevel::new(0),
shaped_cluster_range: cluster..cluster,
text_range: text..text,
});
line.item_range = run_index..run_index + 1;
}
}
let box_metrics = &self.state.line.box_metrics;
let (mut line_box_extents, mut content_box_extents) = if invisible {
(Extents::default().or_zero(), Extents::default().or_zero())
} else {
(
box_metrics.root.line_box.or_zero(),
box_metrics.root.content_box.or_zero(),
)
};
let subtrees = self.state.subtrees.current();
let mut top_height = box_metrics.line_relative_top_height;
let mut bottom_height = box_metrics.line_relative_bottom_height;
for subtree in &subtrees {
let height = subtree.line_box.height();
match self.layout.data.styles[usize::from(subtree.root)]
.vertical_align
.shift
{
BaselineShift::Bottom => bottom_height = bottom_height.max(height),
_ => top_height = top_height.max(height),
}
}
let root_height = line_box_extents.over + line_box_extents.under;
if top_height > root_height {
line_box_extents.under += top_height - root_height;
}
let root_height = line_box_extents.over + line_box_extents.under;
if bottom_height > root_height {
line_box_extents.over += bottom_height - root_height;
}
let offsets = &mut self.lines.aligned_subtree_offsets;
line.aligned_subtree_offsets.start = offsets.len() as u32;
for subtree in &subtrees {
let extents = subtree.line_box.or_zero();
let offset = match self.layout.data.styles[usize::from(subtree.root)]
.vertical_align
.shift
{
BaselineShift::Bottom => extents.under - line_box_extents.under,
_ => line_box_extents.over - extents.over,
};
let offset = if quantize { offset.round() } else { offset };
offsets.push(AlignedSubtreeOffset {
root: subtree.root,
baseline_offset: offset,
});
let content = subtree.content_box.or_zero();
content_box_extents.add(offset, content.over, content.under);
}
line.aligned_subtree_offsets.end = offsets.len() as u32;
if box_metrics.line_relative_top_height > 0. {
content_box_extents.add(
0.,
line_box_extents.over,
box_metrics.line_relative_top_height - line_box_extents.over,
);
}
if box_metrics.line_relative_bottom_height > 0. {
content_box_extents.add(
0.,
box_metrics.line_relative_bottom_height - line_box_extents.under,
line_box_extents.under,
);
}
let top = if quantize {
self.state.line_y.round() as f32
} else {
self.state.line_y as f32
};
line.metrics.baseline = top + line_box_extents.over;
line.metrics.block_min_coord = top;
line.metrics.block_max_coord = if quantize {
(line.metrics.baseline + line_box_extents.under).round()
} else {
line.metrics.baseline + line_box_extents.under
};
line.metrics.content_block_min_coord = line.metrics.baseline - content_box_extents.over;
line.metrics.content_block_max_coord = line.metrics.baseline + content_box_extents.under;
line.metrics.inline_min_coord = self.state.line_x;
line.metrics.inline_max_coord = self.state.line_x + self.state.line_max_advance;
line.metrics.line_height
}
}
impl<B: Brush> Drop for BreakLines<'_, B> {
fn drop(&mut self) {
let mut layout_width = 0_f32;
let mut layout_full_width = 0_f32;
let mut height = 0_f64; for line in &self.lines.lines {
let indent_extra = line.metrics.indent.max(0.0);
let line_max = line.metrics.inline_min_coord + line.metrics.advance + indent_extra;
layout_full_width = layout_full_width.max(line_max);
layout_width = layout_width.max(line_max - line.metrics.hanging_advance);
height += line.metrics.line_height as f64;
}
if self.state.layout_max_advance >= f32::MAX {
for line in &mut self.lines.lines {
if line.metrics.inline_max_coord >= f32::MAX {
line.metrics.inline_max_coord = layout_width;
}
}
}
if let Some(last_line) = self.lines.lines.last()
&& last_line.item_range.is_empty()
{
height -= last_line.metrics.line_height as f64;
}
self.layout.data.width = layout_width;
self.layout.data.full_width = layout_full_width;
self.layout.data.height = height as f32;
self.layout.data.layout_max_advance = self.state.layout_max_advance;
self.lines.swap(&mut self.layout.data);
}
}
#[expect(clippy::cast_possible_truncation, reason = "deferred")]
fn commit_line<B: Brush>(
layout: &Layout<B>,
lines: &mut LineLayout,
state: &mut LineState,
max_advance: f32,
break_reason: BreakReason,
line_indent: f32,
) {
let shaped_text = &layout.data.shaped_text;
let shaped_clusters = shaped_text.shaped_clusters();
state.clusters.end = state.clusters.end.min(shaped_clusters.len() as u32);
state.items.end = state.items.end.min(layout.data.items.len());
let start_item_idx = lines.line_items.len();
let items_to_commit = &layout.data.items[state.items.clone()];
let is_text_run = |item: &LayoutItem| item.kind == LayoutItemKind::TextRun;
let first_run_pos = items_to_commit.iter().position(is_text_run).unwrap_or(0);
let last_run_pos = items_to_commit.iter().rposition(is_text_run).unwrap_or(0);
let mut last_item_kind = LayoutItemKind::TextRun;
let mut committed_text_run = false;
let mut text_start = usize::MAX;
let mut text_end = 0;
let mut needs_reorder = false;
for (i, item) in items_to_commit.iter().enumerate() {
match item.kind {
LayoutItemKind::InlineBox => {
lines.line_items.push(LineItemData {
kind: LayoutItemKind::InlineBox,
index: item.index,
bidi_level: item.bidi_level,
shaped_cluster_range: 0..0,
text_range: 0..0,
});
last_item_kind = item.kind;
}
LayoutItemKind::TextRun => {
let shaped_run = &shaped_text.runs()[item.index];
let mut cluster_range = shaped_run.shaped_clusters_range.clone();
if i == first_run_pos {
cluster_range.start = state.clusters.start;
}
if i == last_run_pos {
cluster_range.end = state.clusters.end;
}
if cluster_range.start >= shaped_run.shaped_clusters_range.end {
continue;
}
last_item_kind = item.kind;
committed_text_run = true;
let slice = shaped_text.run_slice(item.index as u32);
let item_text_range = if cluster_range.is_empty() {
let char_pos = shaped_clusters[cluster_range.start as usize]
.chars_range()
.start;
let text_pos = slice.text_byte_at(char_pos);
text_pos..text_pos
} else {
let char_range = shaped_clusters[cluster_range.start as usize]
.chars_range()
.start
..shaped_clusters[cluster_range.end as usize - 1]
.chars_range()
.end;
slice.text_byte_range(char_range)
};
text_start = text_start.min(item_text_range.start);
text_end = text_end.max(item_text_range.end);
needs_reorder |= shaped_run.bidi_level != BidiLevel::new(0);
lines.line_items.push(LineItemData {
kind: LayoutItemKind::TextRun,
index: item.index,
bidi_level: shaped_run.bidi_level,
shaped_cluster_range: cluster_range,
text_range: item_text_range,
});
}
}
}
let end_item_idx = lines.line_items.len();
let overflow = match break_reason {
BreakReason::Regular | BreakReason::Emergency => f32::INFINITY,
BreakReason::Explicit | BreakReason::None => state.x - max_advance,
};
let (hanging_advance, justification_end_cluster, hanging_opportunities) = hanging_whitespace(
layout,
&lines.line_items[start_item_idx..end_item_idx],
overflow,
);
let num_justification_opportunities = state.num_word_separators - hanging_opportunities;
if needs_reorder && end_item_idx - start_item_idx > 1 {
reorder_line_items(&mut lines.line_items[start_item_idx..end_item_idx]);
}
lines.lines.push(LineData {
item_range: start_item_idx..end_item_idx,
text_range: text_start..text_end,
max_advance,
break_reason,
num_justification_opportunities,
justification: Justification {
justification_end_cluster,
..Justification::NONE
},
metrics: LineMetrics {
indent: line_indent,
advance: state.x,
hanging_advance,
..Default::default()
},
aligned_subtree_offsets: 0..0,
});
if committed_text_run {
state.clusters.start = state.clusters.end;
}
state.items.start = match last_item_kind {
LayoutItemKind::TextRun => state.items.end.saturating_sub(1),
LayoutItemKind::InlineBox => state.items.end,
};
}
#[inline(never)]
fn hanging_whitespace<B: Brush>(
layout: &Layout<B>,
line_items: &[LineItemData],
overflow: f32,
) -> (f32, u32, u32) {
let mut hanging_whitespace_advance = 0.;
let mut justification_end_cluster = u32::MAX;
let mut hanging_opportunities = 0;
let mut in_conditional_suffix = true;
let mut conditional_advance: Option<f32> = None;
'items: for line_item in line_items.iter().rev() {
match line_item.kind {
LayoutItemKind::InlineBox => {
let item = &layout.data.inline_boxes[line_item.index].inline_box;
if item.kind == InlineBoxKind::InFlow {
break;
}
}
LayoutItemKind::TextRun => {
if line_item.bidi_level != layout.data.base_level {
break;
}
let effective_spacing = EffectiveSpacing::new(
layout.data.runs[line_item.index].spacing,
Justification::NONE,
);
let slice = layout
.data
.shaped_text
.run_slice(line_item.index as u32)
.narrow(line_item.shaped_cluster_range.clone());
for atom in slice.atoms_end().rev() {
let (hanging, all_hang) = atom_hanging_advance(
slice,
&atom,
&layout.data.styles,
effective_spacing,
line_item.is_rtl(),
);
let whitespace = atom.characters()[0].whitespace;
if in_conditional_suffix && whitespace != Whitespace::Newline {
let last_cluster = atom.shaped_clusters().last().unwrap();
if layout.data.styles[last_cluster.style_index as usize]
.white_space_collapse
== WhiteSpaceCollapse::Preserve
{
*conditional_advance.get_or_insert(0.) += hanging;
} else {
in_conditional_suffix = false;
if conditional_advance
.is_some_and(|advance| advance <= 0. || advance > overflow)
{
break 'items;
}
}
}
hanging_whitespace_advance += hanging;
justification_end_cluster = atom.shaped_clusters_range().start;
if is_word_separator(whitespace) {
hanging_opportunities += 1;
}
if !all_hang {
break 'items;
}
}
}
}
}
if let Some(advance) = conditional_advance {
hanging_whitespace_advance -= advance - advance.min(overflow).max(0.);
}
(
hanging_whitespace_advance,
justification_end_cluster,
hanging_opportunities,
)
}
fn reorder_line_items(runs: &mut [LineItemData]) {
let run_count = runs.len();
let mut max_level = 0;
let mut lowest_odd_level = 255;
for run in runs.iter() {
let level = run.bidi_level;
let is_odd = level.to_u8() & 1 != 0;
if level.to_u8() > max_level {
max_level = level.to_u8();
}
if is_odd && level.to_u8() < lowest_odd_level {
lowest_odd_level = level.to_u8();
}
}
for level in (lowest_odd_level..=max_level).rev() {
let mut i = 0;
while i < run_count {
if runs[i].bidi_level.to_u8() >= level {
let mut end = i + 1;
while end < run_count && runs[end].bidi_level.to_u8() >= level {
end += 1;
}
runs[i..end].reverse();
i = end;
}
i += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::{BoxMetrics, BreakerState};
#[test]
fn subtree_extents_restored_at_break_opportunities() {
let metrics = BoxMetrics {
ascent: 6.,
descent: 2.,
over: 6.,
under: 2.,
};
for emergency in [false, true] {
let mut state = BreakerState::default();
let add_box = |state: &mut BreakerState, baseline_offset| {
state
.line
.box_metrics
.add_box(1, baseline_offset, metrics, &mut state.subtrees);
};
let subtree_height =
|state: &BreakerState| state.subtrees.current()[0].line_box.height();
add_box(&mut state, 0.);
add_box(&mut state, 1.);
state.mark_line_break_opportunity();
add_box(&mut state, 4.);
state.mark_emergency_break_opportunity();
add_box(&mut state, -4.);
assert_eq!(state.line.box_metrics.line_height(), 16.);
assert_eq!(subtree_height(&state), 16.);
assert_eq!(state.subtrees.current().len(), 1);
let boundary = if emergency {
state.emergency_boundary.take().unwrap()
} else {
state.prev_boundary.take().unwrap()
};
state.reset_to(boundary);
let expected = if emergency { 12. } else { 9. };
assert_eq!(state.line.box_metrics.line_height(), expected);
assert_eq!(subtree_height(&state), expected);
assert_eq!(state.subtrees.current().len(), 1);
}
}
}