From 54a7b51ec945e57be6a231e2963e14620321a1f3 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 13:02:03 -0800 Subject: [PATCH] Type: a tabbed right-to-left line whose last break read precedes a forced line break ends after it When the last of the bounded breaks such a line reads came right before U+2028, the line ended at that break and the next line held only the forced break, drawing an empty line. The line now reads on to the forced break and ends there when it fits as drawn. --- crates/text/src/layout/breaking.rs | 8 +++++--- crates/text/src/layout/wrapping_tests.rs | 26 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/crates/text/src/layout/breaking.rs b/crates/text/src/layout/breaking.rs index cfe5fa0eb6..989ef9ea95 100644 --- a/crates/text/src/layout/breaking.rs +++ b/crates/text/src/layout/breaking.rs @@ -379,8 +379,9 @@ pub(super) fn word_width(g: &[SGlyph], i: usize, line_end: &[f64], kinsoku: Kins /// [`MAX_DRAWN_CHECKS_STEPPING_BACK`] when a glyph of the line has a negative advance, the earlier /// ones without a hyphen are bisected); hyphenation points are measured the same way. Once such a /// line holds a glyph with a negative advance and has been wider than `width` in the order of its -/// text, it reads at most [`MAX_DRAWN_CHECKS_STEPPING_BACK`] legal breaks further and ends at the -/// latest of the breaks read that fits as drawn. +/// text, it reads at most [`MAX_DRAWN_CHECKS_STEPPING_BACK`] legal breaks further, and a forced +/// line break right after the last of them, and ends at the latest of the breaks read that fits as +/// drawn. #[allow(clippy::too_many_arguments)] pub(super) fn break_line( text: &str, @@ -538,8 +539,9 @@ pub(super) fn break_line( // breaks past where it first overflowed in the order of its text, and ends at the // latest break read that fits as drawn: each of those breaks is measured one by one. // A soft hyphen ending the text draws no hyphen there, as at the end of the text below. + // A break right before a forced line break reads on to it: the line ends there. breaks_past_overflow += usize::from(overflowed && back > 0.0); - if breaks_past_overflow >= MAX_DRAWN_CHECKS_STEPPING_BACK { + if breaks_past_overflow >= MAX_DRAWN_CHECKS_STEPPING_BACK && g.get(cluster_end).is_none_or(|n| n.ch != '\u{2028}') { return settle(tabbed, checks(back), (cluster_end, hyphenated && cluster_end < g.len()), &breaks); } } diff --git a/crates/text/src/layout/wrapping_tests.rs b/crates/text/src/layout/wrapping_tests.rs index 89d40386b5..7dd05a9357 100644 --- a/crates/text/src/layout/wrapping_tests.rs +++ b/crates/text/src/layout/wrapping_tests.rs @@ -745,6 +745,32 @@ fn a_tabbed_line_stepping_back_ends_at_a_final_soft_hyphen_without_a_hyphen() { assert_eq!(ended, (len, false)); } +/// A tabbed line with glyphs stepping back whose last break read comes right before a forced line +/// break ends after the forced break, so the next line does not start with it and draw empty. +#[test] +fn a_tabbed_line_stepping_back_keeps_a_forced_break_after_its_last_break_read() { + // The 1,024th space is the 1,024th break past the overflow, and glyph 3075 after it is U+2028. + let text = format!("a\ty{}\u{2028}bbb", "bz ".repeat(1024)); + let specs: Vec<_> = text + .char_indices() + .map(|(byte, ch)| { + let adv = match ch { + 'y' | 'z' => -1.0, + ' ' | '\u{2028}' => 0.0, + _ => 1.0, + }; + (byte, ch.len_utf8(), adv, ch) + }) + .collect(); + let glyphs = synthetic_glyphs(&specs); + assert_eq!(glyphs[3075].ch, '\u{2028}'); + let drawn_fits = |end: usize, _hyphenated: bool| end <= 10 || end >= 3075; + let fits: Option<&dyn Fn(usize, bool) -> bool> = Some(&drawn_fits); + let ended = + break_line(&text, &glyphs, 0, 30.0, &[], 0.0, None, Burasagari::None, Kinsoku::None, KinsokuShoriType::PushOutOnly, false, false, &[], fits); + assert_eq!(ended, (3076, false)); +} + /// A tab to a default or left stop does not read the text after it, which only right, center and /// decimal stops align: the first tab of a long paragraph does not read the paragraph to its end. #[test]