From 6dc8ad933b8c6b2b661b6f85353986c4665e61e2 Mon Sep 17 00:00:00 2001 From: Eric Kaden <608341+EricKaden@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:53:28 -0400 Subject: [PATCH] M12: spaces alone stay on an XFA text line A word of spaces alone draws nothing, but after a word whose own trailing space ran past the box it failed the fit test and opened a blank line, so the text measured a line taller than it draws. Such words now always stay on the line, keeping their width. --- crates/xfa/src/tests.rs | 4 ++++ crates/xfa/src/text.rs | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs index c54028c26..9784986fe 100644 --- a/crates/xfa/src/tests.rs +++ b/crates/xfa/src/tests.rs @@ -2097,6 +2097,10 @@ fn a_space_at_the_end_of_a_line_does_not_count_when_fitting_it() { let spans = draw_spans(&one_draw("a. DATE ", w, "left")); let baselines: std::collections::BTreeSet = spans.iter().map(|s| (s.baseline * 100.0).round() as i64).collect(); assert_eq!(baselines.len(), 1, "{spans:?}"); + // Nor do they add a blank line: the text measures as tall as without them. + let face = text::Face::of(&Font { typeface: "Arial".into(), size: 10.0, ..Font::default() }); + let height = |t: &str| text::measure_height(&text::plain(t), w, &Para::default(), &face, &|_| String::new()); + assert_eq!(height("a. DATE "), height("a. DATE")); // Text that is wider than the box still wraps. let spans = draw_spans(&one_draw("a. DATES ", w, "left")); let baselines: std::collections::BTreeSet = spans.iter().map(|s| (s.baseline * 100.0).round() as i64).collect(); diff --git a/crates/xfa/src/text.rs b/crates/xfa/src/text.rs index 77b9bdd49..cbfd9fbc5 100644 --- a/crates/xfa/src/text.rs +++ b/crates/xfa/src/text.rs @@ -204,9 +204,12 @@ pub fn layout(rich: &RichText, rect: Rect, para: &Para, base: &Face, embed: &dyn let mut first_in_para = true; let indent = |first: bool| if first { para.text_indent.max(0.0) } else { 0.0 }; for w in ws { - // A word fits when what it draws does: the spaces after it would end the line. + // A word fits when what it draws does: the spaces after it would end the line. Spaces + // alone draw nothing, so they always stay on the line (keeping their width, so a word + // after them still starts past them). let ink = ink_width(&w); - let fits = line.width + ink <= avail - indent(line.first) || line.words.is_empty() && ink <= avail - indent(line.first); + let blank = w.text.trim_end_matches(' ').is_empty(); + let fits = blank || line.width + ink <= avail - indent(line.first) || line.words.is_empty() && ink <= avail - indent(line.first); if !fits && !line.words.is_empty() { let done = std::mem::replace(&mut line, Line { words: Vec::new(), width: 0.0, size: base.size, first: false }); lines.push((done, p.h_align));