mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
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
5.0 KiB
5.0 KiB
pdfcraft-forms
Interactive forms (AcroForm, ISO 32000-2 §12.7). Layer L3; depends on pdfcraft-cos and
pdfcraft-fonts.
API
let all: Vec<Field> = fields(&doc); // terminal fields with widgets, options, flags, /DA
let snapshot = fields_with_adopted(&doc); // fields and page-only adoption count in one pass
let all = snapshot.items;
let adopted = snapshot.adopted_page_fields;
set_value(&mut doc, "name", &FieldValue::Text("Ada".into()))?;
set_value(&mut doc, "agree", &FieldValue::Check(true))?;
set_value(&mut doc, "size", &FieldValue::Radio(Some("L".into())))?;
set_value(&mut doc, "toppings", &FieldValue::Choice(vec!["Ham".into()]))?;
reset(&mut doc, None)?; // Clear form (back to /DV)
// Prepare a form
let name = add_field(&mut doc, 0, [72.0, 700.0, 216.0, 722.0], &NewField::Text { multiline: false }, None)?; // "Text1"
set_props(&mut doc, &name, &FieldProps { name: Some("email".into()), required: Some(true), ..Default::default() })?;
// A partial appearance edit keeps each widget's other styles and custom /DA font.
set_props(&mut doc, "email", &FieldProps {
appearance: Some(LookPatch { width: Some(3.0), fill: Some(None), ..Default::default() }),
..Default::default()
})?;
delete_field(&mut doc, "email")?;
Behaviour
- Inheritable attributes (
/FT,/Ff,/V,/DV,/DA,/Q,/MaxLen) are resolved down the field tree; the form's/DAand/Qare the last fallback. - Text and choice fields get new appearance streams (
/Tx BMC … EMC): auto-size (0 Tf), comb, multiline wrapping, password masking, quadding,/MKbackground and border, list-box selection. The/DAfont is used when/DRhas it as a simple font or a composite font with a predefined Unicode CMap; otherwise Helvetica (WinAnsi). A value or button caption the font can't hold (Chinese in a Helvetica field, a character beyond U+FFFF in a UCS-2 CMap) is kept, as Acrobat keeps it:/Vis stored as typed, the appearance draws?for those characters, the widget is marked with the first one (pdfcraft_fonts::UNDRAWN_MARK, cleared once all of it can be drawn) and the form sets/NeedAppearances, so other viewers draw the field from its value with fonts that have them. Not in a document that declares PDF/A, which forbids that flag (declares_pdfa).undrawn_fieldslists the marked fields; the engine reports each edit that draws one, and flattening refuses to make that?permanent. - Displaying a form that leaves its appearances to the viewer (
/NeedAppearances, or a text or choice widget with no/AP /N):with_field_appearancesreturns a copy with those widgets drawn from their values, as typing them would draw them; the engine shows and prints that copy and saves the file as it was (#3470).with_field_appearances_cachedkeeps each widget's drawing while the objects it was drawn from are the same (FieldAppearanceCache), so typing in one field redraws that field, not the form. - Check boxes and radio buttons switch
/Vand each widget's/AS. Widgets with no appearances get PdfCraft's own (a drawn check mark or dot; no symbol font needed). - Values are validated (options,
MaxLen, single vs multi-select, NoToggleToOff, read-only) and a failed call changes nothing. FieldProps::appearanceapplies aLookPatchindependently to each widget.Nonekeeps a property;Some(None)removes a border/fill colour. Unchanged/DA, custom font resources, widget-specific/DAoverrides, indirect/MKand/BScontents, dash arrays and unknown dictionary keys are preserved. It is mutually exclusive with the completelookreplacement. Use the engine'sEdit::Batchto change several fields atomically in one undo step./RV(rich text) is removed when a value is set so it can't contradict/V.- New fields follow Acrobat: names Text1, Check Box1, Group1 (radio groups), Dropdown1, List Box1,
Button1, Date1, Signature1;
/Helvis added to/DR; appearances are drawn at once. Radio buttons joining a group become kid widgets with their own export state. Renaming changes only the last part of a dotted name. Widget::captionreads a push button's normal label (/MK /CA, including indirect values).FieldProps { caption: Some((widget_index, text)), ..Default::default() }changes or clears that label, independently of the field name. The index is 0-based. A label-only edit redraws just that widget, copying shared/indirect/MKand appearance dictionaries before editing. Other widgets, actions, icons, rotation and unknown entries are preserved. Existing icon-only layouts keep hiding the caption; caption rendering retains the existing Helvetica/WinAnsi limits and reports unavailable characters as above.
Not yet
Rich-text values and exact appearance metrics/layout remain incomplete. JavaScript
execution and FDF/XFDF exchange are integrated through the engine; this crate provides
field actions and native AF rules. /MK /R rotation (0, 90, 180, 270) is drawn into the
appearance.