diff --git a/parley/src/builder.rs b/parley/src/builder.rs index d7fecc32f..b758ea165 100644 --- a/parley/src/builder.rs +++ b/parley/src/builder.rs @@ -290,6 +290,8 @@ impl<'b, B: Brush> TreeBuilder<'b, B> { #[inline] pub fn build_into(self, layout: &mut Layout) -> String { + let root_text_wrap_mode = self.lcx.tree_style_builder.root_text_wrap_mode(); + // Apply TreeStyleBuilder styles to LayoutContext. let text = self .lcx @@ -299,6 +301,11 @@ impl<'b, B: Brush> TreeBuilder<'b, B> { // Call generic layout builder method build_into_layout(layout, &text, self.lcx, self.fcx, self.options); + // The tree builder knows its root style, which is a better source for the default + // wrap mode than the first entry of the style table (which may be a synthesized + // default style when the layout contains no text). + layout.data.default_text_wrap_mode = root_text_wrap_mode; + text } @@ -358,6 +365,13 @@ fn build_into_layout( .styles .extend(lcx.style_table.iter().map(|s| s.as_layout_style())); + // Record the wrap mode which applies at the start of the text + layout.data.default_text_wrap_mode = lcx + .style_table + .first() + .map(|s| s.text_wrap_mode) + .unwrap_or_default(); + // Sort the inline boxes as subsequent code assumes that they are in text index order. // Note: It's important that this is a stable sort to allow users to control the order of contiguous inline boxes lcx.inline_boxes.sort_by_key(|b| b.index); diff --git a/parley/src/layout/data.rs b/parley/src/layout/data.rs index 5f6afc3b8..d3dd0f43d 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/layout/data.rs @@ -185,6 +185,10 @@ pub(crate) struct LayoutData { // Output of style resolution (input to line breaking) pub(crate) styles: Vec>, pub(crate) inline_boxes: Vec, + /// The wrap mode that applies before any text cluster has been processed. This is + /// used to determine whether line breaks are allowed around inline boxes that occur + /// before any text (or in layouts that contain no text at all). + pub(crate) default_text_wrap_mode: TextWrapMode, // Output of shaping (input to line breaking) pub(crate) shaped_text: ShapedText, @@ -230,6 +234,7 @@ impl Default for LayoutData { height: 0., styles: Vec::new(), inline_boxes: Vec::new(), + default_text_wrap_mode: TextWrapMode::Wrap, shaped_text: ShapedText::new(), runs: Vec::new(), items: Vec::new(), @@ -256,6 +261,7 @@ impl LayoutData { self.height = 0.; self.styles.clear(); self.inline_boxes.clear(); + self.default_text_wrap_mode = TextWrapMode::Wrap; self.shaped_text.clear(); self.runs.clear(); self.items.clear(); @@ -370,7 +376,7 @@ impl LayoutData { let mut running_min_width = 0.0; let mut running_max_width = 0.0; - let mut text_wrap_mode = TextWrapMode::Wrap; + let mut text_wrap_mode = self.default_text_wrap_mode; // The whitespace class of the previous atom's first character, and the atom's advance. let mut prev_atom: Option<(Whitespace, f32)> = None; let is_rtl = self.base_level.is_rtl(); diff --git a/parley/src/layout/line_break.rs b/parley/src/layout/line_break.rs index 94dcc31e7..5c8ed5e3b 100644 --- a/parley/src/layout/line_break.rs +++ b/parley/src/layout/line_break.rs @@ -453,10 +453,14 @@ impl<'a, B: Brush> BreakLines<'a, B> { lines.swap(&mut layout.data); lines.lines.clear(); lines.line_items.clear(); + // Initialize the wrap mode from the layout's default wrap mode so that inline + // boxes which occur before any text cluster use the correct wrap mode. + let mut state = BreakerState::default(); + state.line.text_wrap_mode = layout.data.default_text_wrap_mode; Self { layout, lines, - state: BreakerState::default(), + state, prev_state: None, done: false, } @@ -680,8 +684,10 @@ impl<'a, B: Brush> BreakLines<'a, B> { self.layout.data.quantize, ); - // We can always line break after an inline box - self.state.mark_line_break_opportunity(); + // We can line break after an inline box unless wrapping is disabled + if self.state.line.text_wrap_mode == TextWrapMode::Wrap { + self.state.mark_line_break_opportunity(); + } } else { // If we're at the start of the line, this box will never fit, so consume it and accept the overflow. let reason = if self.state.line.x == 0.0 { diff --git a/parley/src/resolve/tree.rs b/parley/src/resolve/tree.rs index 023ea9e72..13ce0b2a2 100644 --- a/parley/src/resolve/tree.rs +++ b/parley/src/resolve/tree.rs @@ -4,7 +4,7 @@ //! Hierarchical tree based style application. use alloc::{string::String, vec::Vec}; -use crate::style::WhiteSpaceCollapse; +use crate::style::{TextWrapMode, WhiteSpaceCollapse}; use super::{Brush, ResolvedProperty, ResolvedStyle, StyleRun}; @@ -40,6 +40,13 @@ impl TreeStyleBuilder { fn current_style(&self) -> ResolvedStyle { self.tree[self.current_span].style.clone() } + + pub(crate) fn root_text_wrap_mode(&self) -> TextWrapMode { + self.tree + .first() + .map(|node| node.style.text_wrap_mode) + .unwrap_or(TextWrapMode::Wrap) + } } impl Default for TreeStyleBuilder {