Merge pull request #3641 from pat26-dotcom/fix/3623-pdf-import-word-gap

Fix #3623: a word gap between two sizes reads as a space in PDF import
This commit is contained in:
BFlat
2026-10-22 23:41:26 +07:00
committed by GitHub
3 changed files with 32 additions and 4 deletions
+8 -3
View File
@@ -1,6 +1,6 @@
//! Text as editable point type: glyphs drawn one after another on the same baseline make one
//! point text object of their Unicode text, a run per font, size and paint (a gap wider than a
//! fifth of the size reads as a space). Fonts are named from the file's base font name (its
//! fifth of the smaller size beside it reads as a space). Fonts are named from the file's base font name (its
//! subset prefix dropped, the family matched against the fonts available); a font that isn't
//! available keeps its name, and the text shows in the fallback font until it is.
//!
@@ -124,7 +124,8 @@ struct Gap {
at: usize,
/// From the end of the glyph before to the start of the next (points).
width: f64,
/// The larger of the two glyphs' sizes (points).
/// The smaller of the two glyphs' sizes (points): a word gap beside a much larger glyph is
/// narrow for the large size (#3623).
em: f64,
/// Along the line, not turned along a curve.
on_line: bool,
@@ -156,6 +157,8 @@ pub(crate) struct TextLine {
visible: Point,
/// The last glyph's baseline direction.
last_dir: Vec2,
/// The last glyph's size (points).
last_size: f64,
/// Each glyph's baseline origin, then where the last one ends.
baseline: Vec<Point>,
runs: Vec<(Look, String)>,
@@ -207,6 +210,7 @@ impl TextLine {
last: at.origin,
visible: at.origin,
last_dir: at.dir,
last_size: at.size,
baseline: vec![],
runs: vec![],
upright: None,
@@ -297,7 +301,7 @@ impl TextLine {
return false;
}
if !run.ends_with(' ') && !text.starts_with(' ') {
self.gaps.push(Gap { at: bytes, width: gap, em: size, on_line });
self.gaps.push(Gap { at: bytes, width: gap, em: self.last_size.min(at.size), on_line });
}
// A space the file draws between two glyphs spans the gaps on both sides of it.
let drawn = self.drawn_space.take();
@@ -322,6 +326,7 @@ impl TextLine {
}
self.last = at.origin;
self.last_dir = at.dir;
self.last_size = at.size;
self.baseline.push(at.origin);
self.advances.push(advance);
true
+21
View File
@@ -164,6 +164,27 @@ fn evenly_wide_gaps_stay_spaces() {
}
}
/// A word set beside a much larger one, apart by a gap and no space glyph (as a browser prints a
/// margin between two sizes), reads as two words: the gap is measured against the smaller size
/// (#3623). Set close, the two sizes stay one word.
#[test]
fn a_gap_between_two_sizes_is_measured_against_the_smaller() {
let font = vectorcraft_text::FontDb::global().find_postscript("SourceSans3-Regular").unwrap();
let advance = |c: char, size: f32| font.advance(font.glyph_for(c)) as f32 / font.units_per_em() as f32 * size;
// 3.4 pt: 0.17 em of the 20 pt "4", 0.43 em of the 8 pt "hours".
for (gap, want) in [(3.4_f32, "4 hours"), (0.4, "4hours")] {
let lines = [(20.0, 60.0, 20.0, "4"), (20.0 + advance('4', 20.0) + gap, 60.0, 8.0, "hours")];
let d = import_with_report(&text_pdf(&lines, false), &ImportOptions::default()).unwrap().document;
let t = texts(&d);
assert_eq!(t.len(), 1, "gap {gap} pt: {t:?}");
assert_eq!(t[0].plain_text(), want, "gap {gap} pt");
}
// The small size first.
let lines = [(20.0, 60.0, 8.0, "ca"), (20.0 + advance('c', 8.0) + advance('a', 8.0) + 3.4, 60.0, 20.0, "4")];
let t = texts(&import_with_report(&text_pdf(&lines, false), &ImportOptions::default()).unwrap().document);
assert_eq!(t.iter().map(TextObject::plain_text).collect::<Vec<_>>(), ["ca 4"]);
}
/// Embedded font data is untrusted: anything that isn't a CID-keyed CFF is no table, never a panic.
#[test]
fn cid_tables_ignore_fonts_that_are_not_cid_keyed_cff() {
File diff suppressed because one or more lines are too long