diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index fa75899a2..8637ea1cc 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -1607,7 +1607,9 @@ fn hyphen_breaks(text: &str, g: &[SGlyph], ws: usize, we: usize, opts: &Hyphenat } /// Break candidates for the every-line composer: hyphenation points under `hyphenate` (the -/// paragraph's Hyphenation options) when it is given. +/// paragraph's Hyphenation options) when it is given. No break comes before a space: a line that +/// may end after a dash, a Thai word or a space ends after the spaces that follow it, as in +/// [`break_line`]. fn candidates(text: &str, g: &[SGlyph], hyphenate: Option<&HyphenationOptions>, kinsoku: Kinsoku) -> Vec { let mut v = vec![]; let mut ws = 0; @@ -1622,7 +1624,7 @@ fn candidates(text: &str, g: &[SGlyph], hyphenate: Option<&HyphenationOptions>, } } let hy = if gl.is_soft_hyphen() { hyphen_glyph(gl).adv } else { 0.0 }; - if j + 1 < g.len() && g[j + 1].byte != gl.byte && kinsoku_allows(g, j, kinsoku) { + if j + 1 < g.len() && g[j + 1].byte != gl.byte && !g[j + 1].is_space() && kinsoku_allows(g, j, kinsoku) { v.push(Breakpoint { end: j + 1, hyphen: hy }); } ws = j + 1; diff --git a/crates/text/src/tests_combos.rs b/crates/text/src/tests_combos.rs index 73ecbf848..d61dccc51 100644 --- a/crates/text/src/tests_combos.rs +++ b/crates/text/src/tests_combos.rs @@ -387,6 +387,33 @@ fn composer_x_mixed_line_metrics() { assert!(wrong.is_empty(), "{wrong:?}"); } +#[test] +fn composer_x_lines_start_after_the_spaces() { + // A line may end after a dash or between two spaces, but the every-line composer breaks after + // the spaces that follow, as the single-line composer does: no line starts with a space, in + // uniform 12 pt text and with a 24 pt word in it. + let st = sans(12.0); + let runs = |x: CharStyle| { + vec![run("Destroy all creatures \u{2014} with toughness ", st.clone()), run("X", x), run(" or less \u{2014} then draw a card.", st.clone())] + }; + let mut wrong = vec![]; + for x in [sans(12.0), sans(24.0)] { + for justify in [Justify::Left, Justify::JustifyLeft] { + for w in (40..400).step_by(4) { + let mut t = area(runs(x.clone()), Rect::new(0.0, 0.0, f64::from(w), 2000.0)); + t.para.composer = Composer::EveryLine; + t.para.justify = justify; + let text = t.plain_text(); + let l = layout(db(), &t); + if l.lines.iter().any(|li| text[li.start..].starts_with(' ')) { + wrong.push((x.size, justify, w, line_texts(&t, &l))); + } + } + } + } + assert!(wrong.is_empty(), "{wrong:?}"); +} + const INLINE: char = vectorcraft_doc::text::INLINE_CHAR; // ---------- vertical alignment × … ----------