diff --git a/crates/pdf/src/import_text.rs b/crates/pdf/src/import_text.rs index b6458de60..062d202c9 100644 --- a/crates/pdf/src/import_text.rs +++ b/crates/pdf/src/import_text.rs @@ -131,8 +131,8 @@ struct Gap { } /// The spacing a line's letters share (letter-spaced type), in ems: the gap a third of the way up -/// from the narrowest, of three or more along the line. Never below zero, and at most half an em: -/// glyphs set wider apart than that are words or columns, not tracked letters. +/// from the narrowest, of three or more along the line. Never below zero, and none at all past +/// half an em: glyphs set wider apart than that are words or columns, not tracked letters. fn common_gap(gaps: &[Gap]) -> f64 { let mut widths: Vec = gaps.iter().filter(|g| g.on_line).map(|g| g.width / g.em).filter(|w| w.is_finite()).collect(); if widths.len() < 3 { @@ -140,7 +140,7 @@ fn common_gap(gaps: &[Gap]) -> f64 { } let pivot = widths.len() / 3; let (_, common, _) = widths.select_nth_unstable_by(pivot, f64::total_cmp); - common.clamp(0.0, 0.5) + if *common > 0.5 { 0.0 } else { common.max(0.0) } } /// A line of type being gathered: runs of glyphs on one baseline. @@ -386,7 +386,8 @@ impl TextLine { t.runs.extend(runs); // A gap wider than a fifth of an em, past the spacing the line's letters share, reads as a // space. Letter spacing is read only in text left in the file's order. - let common = if t.para.direction.is_none() { common_gap(&self.gaps) } else { 0.0 }; + let directed = vectorcraft_text::logical_order(&t.plain_text()).is_some(); + let common = if directed { 0.0 } else { common_gap(&self.gaps) }; let mut spaces = vec![]; for gap in &self.gaps { // A word space set at exactly a fifth of an em still counts, whatever the rounding. diff --git a/crates/pdf/src/tests_live_text.rs b/crates/pdf/src/tests_live_text.rs index ba48de415..cf61b6179 100644 --- a/crates/pdf/src/tests_live_text.rs +++ b/crates/pdf/src/tests_live_text.rs @@ -127,15 +127,21 @@ fn common_tracking_does_not_turn_gaps_into_spaces() { } } -/// Glyphs set an em or more apart, each on its own, are words or columns: every gap stays a space, -/// however evenly they are spaced. +/// Glyphs set more than half an em apart, each on its own, are words or columns: every gap stays a +/// space, however evenly they are spaced. #[test] fn evenly_wide_gaps_stay_spaces() { - let lines: Vec<_> = ["1", "2", "3", "4"].iter().enumerate().map(|(i, text)| (20.0 + i as f32 * 18.0, 60.0, 12.0, *text)).collect(); - let d = import_with_report(&text_pdf(&lines, false), &ImportOptions::default()).unwrap().document; - let t = texts(&d); - assert_eq!(t.len(), 1, "{t:?}"); - assert_eq!(t[0].plain_text(), "1 2 3 4"); + let font = vectorcraft_text::FontDb::global().find_postscript("SourceSans3-Regular").unwrap(); + let size = 12.0_f32; + let one = font.advance(font.glyph_for('1')) as f32 / font.units_per_em() as f32 * size; + for gap in [0.55_f32, 0.75, 1.0] { + let lines: Vec<_> = + ["1", "2", "3", "4"].iter().enumerate().map(|(i, text)| (20.0 + i as f32 * (one + gap * size), 60.0, size, *text)).collect(); + let d = import_with_report(&text_pdf(&lines, false), &ImportOptions::default()).unwrap().document; + let t = texts(&d); + assert_eq!(t.len(), 1, "gap {gap} em: {t:?}"); + assert_eq!(t[0].plain_text(), "1 2 3 4", "gap {gap} em"); + } } /// Embedded font data is untrusted: anything that isn't a CID-keyed CFF is no table, never a panic.