From fbcb217789bece8678eef9dab8e45989b9b59501 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 16 Oct 2026 20:43:42 -0700 Subject: [PATCH] Part of #2384: imported PDF text keeps its place after wide gaps between words PDF import reads a gap wider than a fifth of an em between two glyphs as a space, and a space that the line's tracking cannot place gets tracking of its own. That tracking was clamped to 1000 thousandths of an em, while type takes tracking from -1000 to 10000. A gap wider than an em plus the width of a space came in short by the excess, so the words after it sat to the left of where the file put them, and each further wide gap added to the shift. The tracking that import sets for such a space, and for the line around it, now has the same range as type's. --- crates/pdf/src/import_text.rs | 2 +- crates/pdf/src/tests_import_lines.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/pdf/src/import_text.rs b/crates/pdf/src/import_text.rs index 04d25c0b0..04c5b3129 100644 --- a/crates/pdf/src/import_text.rs +++ b/crates/pdf/src/import_text.rs @@ -494,7 +494,7 @@ fn spacing(laid: &TextLayout, gaps: &[(usize, f64)], extra: f64, chars: usize, s tracking = if rest >= 1.0 { (extra - own.iter().map(|(_, need)| need).sum::()) / rest } else { 0.0 }; } own.sort_by_key(|(b, _)| *b); - let em = |pts: f64| (pts / size * 1000.0).clamp(-1000.0, 1000.0).round(); + let em = |pts: f64| (pts / size * 1000.0).clamp(-1000.0, 10000.0).round(); (em(tracking), own.into_iter().map(|(b, need)| (b, em(need - tracking))).collect()) } diff --git a/crates/pdf/src/tests_import_lines.rs b/crates/pdf/src/tests_import_lines.rs index fddb46221..898dcaa23 100644 --- a/crates/pdf/src/tests_import_lines.rs +++ b/crates/pdf/src/tests_import_lines.rs @@ -191,6 +191,20 @@ fn spaces_read_from_wide_gaps_keep_the_glyphs_after_them_in_place() { assert!((at(9) - f64::from(x2)).abs() < 0.05, "the third group starts where the file has it: {} vs {x2}", at(9)); } +#[test] +fn words_set_ems_apart_keep_their_place() { + // Gaps of about 1.75 and 2.6 em (#2384): each space's own tracking is more than an em. + let (x1, x2) = (48.0, 84.0); + let bytes = source_sans(&[(20.0, 50.0, 10.0, "ab"), (x1, 50.0, 10.0, "cd"), (x2, 50.0, 10.0, "ef")]); + let t = texts(&import(&bytes).unwrap()); + assert_eq!(t.len(), 1); + assert_eq!(t[0].plain_text(), "ab cd ef"); + let laid = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), &t[0]); + let at = |byte: usize| (t[0].xf * laid.glyphs.iter().find(|g| g.byte == byte).unwrap().origin).x; + assert!((at(3) - f64::from(x1)).abs() < 0.05, "the second word starts where the file has it: {} vs {x1}", at(3)); + assert!((at(6) - f64::from(x2)).abs() < 0.05, "the third word starts where the file has it: {} vs {x2}", at(6)); +} + /// A document of area type `text` (Source Sans 3, 10 pt) `width` wide at (20, 30). fn area_doc(text: &str, justify: Justify, width: f64, space_before: f64) -> Document { let style = CharStyle { size: 10.0, fill: Paint::solid(Color::BLACK), ..Default::default() };