mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-31 05:28:09 -06:00
fix(forms): cache field appearances drawn for display, redraw only what changed (#3470)
Review follow-up (CodeRabbit on #3475). A form edit refreshes the display copy on every keystroke, and with /NeedAppearances that redrew every field of the form: 68 ms per edit on a 1000-field form against 7 ms on main. FieldAppearanceCache keeps each widget's drawing with the objects it was drawn from: the widget and its field ancestors, the form dictionary and the fonts of its /DR, each with the objects its entries refer to. An entry is used only while each of them is still the same object (Arc::ptr_eq); the document replaces an object it edits, so a changed input is a miss, never a stale hit. The engine keeps one per editor. A field edit on that form now takes 13 ms. The repair note on a failed display write now names field appearances too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b661ba4584
commit
da351cda8f
@@ -316,6 +316,10 @@ struct Editor {
|
||||
/// runs (content edits leave every annotation dictionary alone). Cleared on any refresh
|
||||
/// that may change annotations; see [`AppearanceCache`].
|
||||
appearances: AppearanceCache,
|
||||
/// Display appearances drawn for form fields the file leaves to the viewer (#3470), reused
|
||||
/// for each widget while the objects it was drawn from are unchanged; see
|
||||
/// [`pdfcraft_forms::FieldAppearanceCache`].
|
||||
field_appearances: pdfcraft_forms::FieldAppearanceCache,
|
||||
/// The text blocks last computed for one page, reused while the document is unchanged.
|
||||
text_blocks: TextBlocksCache,
|
||||
}
|
||||
@@ -882,13 +886,14 @@ fn is_placeholder_title(title: &str) -> bool {
|
||||
fn display_bytes(editor: Option<&mut Editor>, bytes: &Arc<Vec<u8>>) -> Arc<Vec<u8>> {
|
||||
let Some(e) = editor else { return bytes.clone() };
|
||||
let comments = pdfcraft_annot::with_missing_appearances_cached(&e.cos, &mut e.appearances);
|
||||
let Some(display) = with_field_appearances(comments, &e.cos) else {
|
||||
let fields = pdfcraft_forms::with_field_appearances_cached(comments.as_ref().unwrap_or(&e.cos), &mut e.field_appearances);
|
||||
let Some(display) = fields.or(comments) else {
|
||||
return bytes.clone();
|
||||
};
|
||||
match write_incremental(&display, &SaveOptions::default()) {
|
||||
Ok(written) => Arc::new(written),
|
||||
Err(err) => {
|
||||
e.cos.note_repair(format!("missing comment appearances could not be drawn ({err})"));
|
||||
e.cos.note_repair(format!("missing comment or field appearances could not be drawn ({err})"));
|
||||
bytes.clone()
|
||||
}
|
||||
}
|
||||
@@ -3576,6 +3581,7 @@ impl Session {
|
||||
redo: Vec::new(),
|
||||
keys,
|
||||
appearances: AppearanceCache::default(),
|
||||
field_appearances: pdfcraft_forms::FieldAppearanceCache::default(),
|
||||
text_blocks: TextBlocksCache::default(),
|
||||
}),
|
||||
None,
|
||||
|
||||
@@ -6160,6 +6160,11 @@ fn fields_left_to_the_viewer_are_displayed_from_their_values() {
|
||||
let n = widget("a").as_dict().and_then(|d| d.get(b"AP").and_then(|ap| saved.resolve(ap).as_dict().and_then(|ap| ap.reference(b"N")))).unwrap();
|
||||
let pdfcraft_cos::Object::Stream(n) = &*saved.get(n) else { panic!("A's appearance is a stream") };
|
||||
assert_eq!(n.decoded().unwrap().trim_ascii_end(), b"q\nQ", "A keeps its empty appearance");
|
||||
// Typing in one field redraws that field only, not the whole form, on each keystroke.
|
||||
let builds = |s: &Session| s.get(id).unwrap().editor.as_ref().unwrap().field_appearances.builds();
|
||||
let before = builds(&s);
|
||||
s.apply(id, Edit::SetFieldValue { name: "a".into(), value: pdfcraft_forms::FieldValue::Text("Typed".into()) }).unwrap();
|
||||
assert_eq!(builds(&s), before + 1);
|
||||
|
||||
// Without the flag, the empty appearance is the file's own, and only B is drawn.
|
||||
let id = s.open("form.pdf", None, Arc::new(pdf(false)), None).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user