From 0dab6da98f746d57f5b3bf1f05096f7b3ce6e474 Mon Sep 17 00:00:00 2001 From: Thomas Churchman Date: Thu, 30 Jul 2026 18:53:50 +0200 Subject: [PATCH] Consistently pass through the root style for empty text Empty text still needs to be able to size cursors, I hit this when making font selection infallible, which tripped up `builders_empty`. (This might also be useful for sizing "struts".) --- parley/src/builder.rs | 16 ++++++++++++++++ parley/src/resolve/tree.rs | 9 +++++++++ parley/src/tests/test_builders.rs | 31 +------------------------------ 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/parley/src/builder.rs b/parley/src/builder.rs index fcdc7fb46..eb0eab4d8 100644 --- a/parley/src/builder.rs +++ b/parley/src/builder.rs @@ -187,6 +187,22 @@ impl<'b, B: Brush> StyleRunBuilder<'b, B> { self.cursor == self.len, "StyleRunBuilder requires runs that cover the full text" ); + if self.lcx.style_runs.is_empty() { + // This is reachable for empty text. In this case, the layout still needs a style and an + // empty style run, e.g. to size a cursor. + if self.lcx.style_table.is_empty() { + let style = self.lcx.rcx.resolve_entire_style_set( + self.fcx, + &TextStyle::default(), + self.options.scale, + ); + self.lcx.style_table.push(style); + } + self.lcx.style_runs.push(StyleRun { + style_index: 0, + range: 0..0, + }); + } build_into_layout(layout, text.as_ref(), self.lcx, self.fcx, self.options); } diff --git a/parley/src/resolve/tree.rs b/parley/src/resolve/tree.rs index 9e280bd2b..42b9609fa 100644 --- a/parley/src/resolve/tree.rs +++ b/parley/src/resolve/tree.rs @@ -217,6 +217,15 @@ impl TreeStyleBuilder { style_table.extend_from_slice(&self.style_table); style_runs.extend_from_slice(&self.style_runs); + if style_runs.is_empty() { + // If there's no text, the layout still needs the root style, e.g., to size a cursor. + style_runs.push(StyleRun { + style_index: style_table.len() as u16, + range: 0..0, + }); + style_table.push(self.current_style()); + } + core::mem::take(&mut self.text) } } diff --git a/parley/src/tests/test_builders.rs b/parley/src/tests/test_builders.rs index 9c48aefdc..f098cfe17 100644 --- a/parley/src/tests/test_builders.rs +++ b/parley/src/tests/test_builders.rs @@ -202,7 +202,6 @@ fn assert_builders_produce_same_result<'b>( root_style: &TextStyle<'b, 'b, ColorBrush>, with_ranged_builder: impl Fn(&mut RangedBuilder<'_, ColorBrush>), with_tree_builder: impl Fn(&mut TreeBuilder<'_, ColorBrush>), - expect_empty: bool, ) { let mut fcx = create_font_context(); @@ -226,33 +225,17 @@ fn assert_builders_produce_same_result<'b>( // Source of truth - ranged builder from a clean layout context let layout_truth = build_layout_with_ranged(&mut fcx, &mut lcx_a, &ropts, &with_ranged_builder); - assert!( - layout_truth.data.runs.is_empty() == expect_empty, - "expected runs to exist for lcx_a_rb_one" - ); // Testing idempotence of ranged builder creation let layout = build_layout_with_ranged(&mut fcx, &mut lcx_a, &ropts, &with_ranged_builder); - assert!( - layout.data.runs.is_empty() == expect_empty, - "expected runs to exist for lcx_a_rb_two" - ); assert_eq_layout_data(&layout_truth.data, &layout.data, "lcx_a_rb_two"); // Basic builder compatibility - tree builder from a clean layout context let layout = build_layout_with_tree(&mut fcx, &mut lcx_b, &topts, &with_tree_builder); - assert!( - layout.data.runs.is_empty() == expect_empty, - "expected runs to exist for lcx_b_tb_one" - ); assert_eq_layout_data(&layout_truth.data, &layout.data, "lcx_b_tb_one"); // Testing idempotence of tree builder creation let layout = build_layout_with_tree(&mut fcx, &mut lcx_b, &topts, &with_tree_builder); - assert!( - layout.data.runs.is_empty() == expect_empty, - "expected runs to exist for lcx_b_tb_two" - ); assert_eq_layout_data(&layout_truth.data, &layout.data, "lcx_b_tb_two"); // Priming a fresh layout context with ranged builder creation @@ -260,10 +243,6 @@ fn assert_builders_produce_same_result<'b>( // Testing tree builder creation with a dirty layout context let layout = build_layout_with_tree(&mut fcx, &mut lcx_c, &topts, &with_tree_builder); - assert!( - layout.data.runs.is_empty() == expect_empty, - "expected runs to exist for lcx_c_tb_one" - ); assert_eq_layout_data(&layout_truth.data, &layout.data, "lcx_c_tb_one"); // Priming a fresh layout context with tree builder creation @@ -271,10 +250,6 @@ fn assert_builders_produce_same_result<'b>( // Testing ranged builder creation with a dirty layout context let layout = build_layout_with_ranged(&mut fcx, &mut lcx_d, &ropts, &with_ranged_builder); - assert!( - layout.data.runs.is_empty() == expect_empty, - "expected runs to exist for lcx_d_rb_one" - ); assert_eq_layout_data(&layout_truth.data, &layout.data, "lcx_d_rb_one"); } @@ -368,7 +343,6 @@ fn builders_default() { &root_style, with_ranged_builder, with_tree_builder, - false, ); } @@ -513,7 +487,6 @@ fn builders_root_only() { &root_style, with_ranged_builder, with_tree_builder, - false, ); } @@ -526,7 +499,7 @@ fn builders_empty() { let max_advance = Some(50.); let root_style = create_root_style(); - let with_ranged_builder = |_rb: &mut RangedBuilder<'_, ColorBrush>| {}; + let with_ranged_builder = |rb: &mut RangedBuilder<'_, ColorBrush>| set_root_style(rb); let with_tree_builder = |_tb: &mut TreeBuilder<'_, ColorBrush>| {}; assert_builders_produce_same_result( @@ -537,7 +510,6 @@ fn builders_empty() { &root_style, with_ranged_builder, with_tree_builder, - true, ); } @@ -592,7 +564,6 @@ fn builders_mixed_styles() { &root_style, with_ranged_builder, with_tree_builder, - false, ); }