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]