mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 04:22:20 +00:00
fix(edit): an OCR word edit can't land on the wrong word (refs #3313)
A rewrite also takes away copies of its words. With a copy earlier on the page, the words after it moved up the list, so the next change rewrote the word after the one it meant: deleting John removed Smith and left John. Before each change, the words up to the next one that stays must still be where they were. If they aren't, the edit falls back to the whole-phrase rewrite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9f5166f297
commit
19450d7cdf
2 files changed
+29
-1
No files matched your search
@@ -183,7 +183,8 @@ fn drawn_right(before: &[TextLine], indices: &[usize], after: &[TextLine]) -> Re
|
||||
}
|
||||
|
||||
/// Rewrite each change of `plan` in `doc`, last first: a rewrite goes where its words were or at
|
||||
/// the end of the page, so the words before it keep their indexes. Gives how far right each drew.
|
||||
/// the end of the page, so the words before it keep their indexes (an error if they don't). Gives
|
||||
/// how far right each drew.
|
||||
fn apply(
|
||||
doc: &mut Document,
|
||||
page: usize,
|
||||
@@ -198,9 +199,17 @@ fn apply(
|
||||
let mut style = BlockStyle { align: Some(crate::added::Align::Left), changed_words_only: false, ..style.clone() };
|
||||
let mut rights = vec![None; plan.len()];
|
||||
let mut substituted = None;
|
||||
let original = word_lines(doc, page)?;
|
||||
for (at, change) in plan.iter().enumerate().rev() {
|
||||
let indices = members.get(change.runs.clone()).ok_or_else(|| EditError::Invalid("the phrase has no such words".into()))?;
|
||||
let before = word_lines(doc, page)?;
|
||||
// The words up to the next that stays must still be where they were. A rewrite also takes
|
||||
// away copies of its words, and a copy earlier on the page moves these up the list.
|
||||
let up_to = change.runs.end.saturating_add(1).min(members.len());
|
||||
let same = |index: &usize| before.get(*index).map(key) == original.get(*index).map(key);
|
||||
if !members.get(..up_to).unwrap_or_default().iter().all(same) {
|
||||
return Err(EditError::Invalid("rewriting these words alone would lose track of the words before them".into()));
|
||||
}
|
||||
let part = stretch(phrase, &before, indices)?;
|
||||
// Relative to the scale the words have, which a layer may have narrowed already.
|
||||
let own_scale = indices.first().and_then(|i| before.get(*i)).map_or(1.0, |line| line.origin.state.scale);
|
||||
|
||||
@@ -527,6 +527,25 @@ fn spacing_that_has_to_be_drawn_redraws_the_phrase() {
|
||||
assert!(drawn.join("").contains("Jon Smy"), "the typed spaces are there: {drawn:?}");
|
||||
}
|
||||
|
||||
/// A word drawn twice, its copy earlier in the layer: rewriting the word takes the copy with it, and
|
||||
/// the words after the copy move up the list. The next change must not land on the wrong word.
|
||||
#[test]
|
||||
fn a_change_lands_on_its_own_word_when_a_rewrite_removes_an_earlier_copy() {
|
||||
let bytes = under_scan(
|
||||
"BT /F1 12 Tf 3 Tr 1 0 0 1 84 44 Tm (Jones) Tj 1 0 0 1 20 44 Tm (John) Tj 1 0 0 1 50 44 Tm (Smith) Tj 1 0 0 1 84 44 Tm (Jones) Tj ET",
|
||||
);
|
||||
let mut s = Session::new();
|
||||
let id = s.open("copy.pdf", None, bytes, None).unwrap();
|
||||
let block = texts(&s, id).iter().position(|t| t == "John Smith Jones").expect("the phrase, after its copy");
|
||||
// John goes. A deletion draws nothing, so nothing else would notice it landing on Smith.
|
||||
edit_block(&mut s, id, block, "Smith Joness");
|
||||
let text = s.get(id).unwrap().text_lines(0).iter().map(|l| l.text.as_str()).collect::<Vec<_>>().join(" ");
|
||||
let words: Vec<&str> = text.split_whitespace().collect();
|
||||
assert!(words.contains(&"Smith"), "Smith wasn't changed: {words:?}");
|
||||
assert!(!words.contains(&"John"), "John is the word that goes: {words:?}");
|
||||
assert!(words.contains(&"Joness"), "{words:?}");
|
||||
}
|
||||
|
||||
/// A layer whose words are already narrowed (50 Tz): squeezing a replacement narrows it further,
|
||||
/// rather than setting a scale wider than the one it had and running it into the next word.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user