From b14831d964b56eba6f7d53ba0b4122650207c043 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 11 Nov 2026 16:20:48 -0800 Subject: [PATCH] Type: a soft hyphen inside a No Break run does not end the first word When layout looked for room for a line's first word in a frame that is not a rectangle, a soft hyphen ended the word even inside a No Break run, although the line does not break there. The word is now measured across a soft hyphen where kinsoku or a No Break run keeps the line from breaking after it. --- crates/text/src/layout/breaking.rs | 2 +- crates/text/src/layout/wrapping_tests.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/text/src/layout/breaking.rs b/crates/text/src/layout/breaking.rs index f5e7b1d45..303278e56 100644 --- a/crates/text/src/layout/breaking.rs +++ b/crates/text/src/layout/breaking.rs @@ -265,7 +265,7 @@ pub(super) fn word_width(g: &[SGlyph], i: usize, line_end: &[f64], kinsoku: Kins if gl.is_space() && (end == start || !splits_no_break(g, end)) { return set(w, trailing, ink); } - if gl.is_soft_hyphen() { + if gl.is_soft_hyphen() && kinsoku_allows(g, end, kinsoku) && !splits_no_break(g, end + 1) { return w + hyphen_glyph(gl).adv; } w += gl.adv; diff --git a/crates/text/src/layout/wrapping_tests.rs b/crates/text/src/layout/wrapping_tests.rs index 123d043ff..a2646852b 100644 --- a/crates/text/src/layout/wrapping_tests.rs +++ b/crates/text/src/layout/wrapping_tests.rs @@ -269,6 +269,24 @@ fn no_break_keeps_marked_characters_on_one_line() { assert!(breaks.iter().all(|b| b.end != 3), "no break between the marked characters: {breaks:?}"); } +/// A soft hyphen inside a No Break run is not a place a line may break, so the word is measured +/// across it, as wide as [`break_line`] keeps it. +#[test] +fn word_width_measures_across_a_soft_hyphen_inside_a_no_break_run() { + let mut glyphs = synthetic_glyphs(&[(0, 1, 10.0, 'A'), (1, 2, 0.0, '\u{00ad}'), (3, 1, 10.0, 'B'), (4, 1, 10.0, 'C')]); + // The hyphen a soft hyphen adds comes from the face, scaled to well under 10 pt. + for g in &mut glyphs { + g.sx = 0.001; + } + let plain = breaking::word_width(&glyphs, 0, &[], Kinsoku::None); + assert!(plain < 30.0, "without the mark the word ends at the soft hyphen: {plain}"); + for g in &mut glyphs { + g.no_break = true; + } + let marked = breaking::word_width(&glyphs, 0, &[], Kinsoku::None); + assert!((marked - 30.0).abs() < 1e-9, "the marked run is measured whole: {marked}"); +} + /// Synthetic full-width characters `text`, each 10 pt wide on a 10 pt em. fn full_width(text: &str) -> Vec { let specs: Vec<(usize, usize, f64, char)> = text.char_indices().map(|(byte, ch)| (byte, ch.len_utf8(), 10.0, ch)).collect();