mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-29 04:28:20 -06:00
M7: keep a word on its line when its spacing differs by less than a thousandth of an em
Joining runs into one line compared their character and word spacing to 1e-6. Word documents exported through macOS Quartz write 0.0001 Tc on some words, so a line like "for company AEROSOL" split into "for company A" and "EROSOL", two paragraphs that edit separately. Spacing below a thousandth of an em, the unit glyph widths are given in, can't be seen. Compare character and word spacing to that; scale and rise still have to match exactly, and visible spacing still keeps runs apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4164951453
commit
475f375ff4
@@ -5218,3 +5218,27 @@ fn a_clipping_copy_on_the_same_baseline_is_refused() {
|
||||
assert!(matches!(&block, Err(EditError::Invalid(m)) if m.contains("clipping path")), "{block:?}");
|
||||
assert_eq!(text::text_lines(&doc, 0).unwrap().len(), 2, "refused means untouched");
|
||||
}
|
||||
|
||||
/// A producer wrote `0.0001 Tc` on one word of a line (Word through macOS Quartz). That spacing
|
||||
/// can't be seen, so the word stays on its line; spacing that can be seen still keeps runs apart.
|
||||
#[test]
|
||||
fn a_word_with_invisible_extra_spacing_stays_on_its_line() {
|
||||
let page = |spacing: &str| {
|
||||
text_page(&format!(
|
||||
"BT /F1 12 Tf 1 0 0 1 72 700 Tm (for company ) Tj ET \
|
||||
BT /F1 12 Tf 1 0 0 1 141.36 700 Tm (A) Tj ET \
|
||||
BT {spacing} Tc /F1 12 Tf 1 0 0 1 149.364 700 Tm (EROSOL) Tj ET"
|
||||
))
|
||||
};
|
||||
let blocks = text::text_blocks(&page("0.0001"), 0).unwrap();
|
||||
assert_eq!(blocks.iter().map(|b| b.text.as_str()).collect::<Vec<_>>(), ["for company AEROSOL"]);
|
||||
|
||||
let mut doc = page("0.0001");
|
||||
text::rewrite_block(&mut doc, 0, 0, Some("for company AEROSOL LLC"), &BlockStyle::default()).unwrap();
|
||||
let lines = text::text_lines(&reopen(&doc), 0).unwrap();
|
||||
assert_eq!(lines.iter().map(|l| l.text.as_str()).collect::<Vec<_>>(), ["for company AEROSOL LLC"]);
|
||||
assert!((lines[0].rect[0] - 72.0).abs() < 0.01, "{:?}", lines[0].rect);
|
||||
|
||||
let blocks = text::text_blocks(&page("0.5"), 0).unwrap();
|
||||
assert_eq!(blocks.iter().map(|b| b.text.as_str()).collect::<Vec<_>>(), ["for company A", "EROSOL"]);
|
||||
}
|
||||
|
||||
@@ -1203,12 +1203,17 @@ pub fn text_lines(doc: &Document, page: usize) -> Result<Vec<TextLine>, EditErro
|
||||
/// Joining independent text objects is safe only when they use the same text and graphics
|
||||
/// transforms and spacing. In particular, a nearby rotated label isn't part of this line.
|
||||
fn compatible_word(line: &TextLine, shown: &Shown) -> bool {
|
||||
let same = |a: &[f64], b: &[f64]| a.iter().zip(b).all(|(a, b)| (a - b).abs() < 1e-6);
|
||||
let within = |a: &[f64], b: &[f64], tolerance: f64| a.iter().zip(b).all(|(a, b)| (a - b).abs() < tolerance);
|
||||
let same = |a: &[f64], b: &[f64]| within(a, b, 1e-6);
|
||||
let (a, b) = (&line.origin.state, TextState::of(&shown.state));
|
||||
// Spacing that differs by less than a thousandth of an em, the unit glyph widths are given in,
|
||||
// can't be seen. Some producers still write it (`0.0001 Tc` on one word of a line).
|
||||
let em = shown.state.size.abs() / 1000.0;
|
||||
line.scope == shown.scope
|
||||
&& same(&line.origin.tm[..4], &shown.tm.0[..4])
|
||||
&& same(&line.origin.ctm, &shown.state.ctm.0)
|
||||
&& same(&[a.char_spacing, a.word_spacing, a.scale, a.rise], &[b.char_spacing, b.word_spacing, b.scale, b.rise])
|
||||
&& within(&[a.char_spacing, a.word_spacing], &[b.char_spacing, b.word_spacing], em.max(1e-6))
|
||||
&& same(&[a.scale, a.rise], &[b.scale, b.rise])
|
||||
&& a.fill.len() == b.fill.len()
|
||||
&& a.fill.iter().zip(&b.fill).all(|(a, b)| a.op == b.op && a.operands == b.operands)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user