Commit Graph
126 Commits
Author SHA1 Message Date
aiongg 0988209798 refactor(xfa): split pdf.rs into pdf/
New modules: helpers, strip.
2026-10-26 22:18:50 +00:00
aiongg 3da49321f2 refactor(xfa): split layout.rs into layout/
New modules: items, helpers, pages, flow, measure, draw, field.
2026-10-26 22:18:50 +00:00
aiongg 4e278cb5fc refactor(xfa): split data.rs into data/
New modules: path, dates, values, build, merge, write, import, passwords.
2026-10-26 22:18:50 +00:00
mtlcl410andClaude Opus 5.5 6918d0be21 M6: a static XFA form whose data fills thousands of fields opens in seconds (#4638)
Opening a static XFA form gives its fields the values in its datasets
(a form filled in by another application). Each value went through
forms::set_value, which reads the whole field tree to find the field,
writes it, and recalculates, reading the tree again. With n values that
is about 2n readings of n fields: quadratic. A public 136-page form with
about 6,000 fields took 261 s to open, a 95-page one with about 4,000
took 112 s; 8,000 synthetic fields took over ten minutes.

forms::set_values fills a batch as set_value fills each in turn, reading
the form once and recalculating once after the last value (the
calculation order, ISO 32000-2 Table 224 /CO, runs once over the final
values). Fields with the same fully qualified name (12.7.4.2) still share
the value. A name the batch already wrote, or a twin of it, makes the
form be read again first, so each write starts from what the earlier one
left, as one by one. Each value reports its own result; a failed
recalculation fails the batch, as it fails set_value. The engine collects
the changed values and fills them with set_values, finding fields by name
through a map rather than a scan.

The two forms now open in 4 s and 3 s, with the same fields, values,
warnings and document information as before; 8,000 synthetic fields open
in 1.5 s.

Tests: set_values matches set_value one by one (values, twins, the
calculated total, every appearance, saved; refusals for an unknown name,
a read-only field, an unknown option and clearing a no-toggle-off radio
group the batch selected); it reads the form a fixed number of times; the
engine takes 59 data values on a 60-field static form exactly as filling
them one by one does (the read-only one refused); and opening 800 fields
takes under 16 times as long as 100 (about 4x now, about 48x before).


Claude-Session: https://claude.ai/code/session_01CBVyQEwtmEw3mS1MoAiwrK

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:02:35 -04:00
Eric Kaden 3f5d8d06fe M9: name a laid-out XFA form's signature fields by their SOM path, so other viewers find the signature (#4507)
* M9: name a laid-out XFA form's signature fields by their SOM path, so other viewers find the signature

* M9: doc comments on the functions the signature naming touches

* M9: say other viewers in the signature naming's comments

* M9: a new layout keeps a signature a generated signature box holds

* M9: the re-layout test checks the signature's picture is kept too

* M9: a signature field named by its SOM path doesn't repeat a name a field already has

* M9: every name a field of the pass takes is known before a signature field is named, in either order

* M9: the signed-box test's doc comment is back on its test

A merge had left it above the next test, which has its own.
2026-10-25 10:01:53 -04:00
NetterSilverandClaude Haiku 5.5 29e08fadce Reject non-hex characters in XFA image hash parsing (#4630)
Found while testing crates/xfa/src/pdf.rs:50-52 (parse_hex32)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-25 09:59:51 -04:00
jonathanwxh-cell 24edde6563 forms: clear typed XFA password field values from datasets on save (#3944) (#4506) 2026-10-24 21:08:21 -04:00
Dylan M. Taylor f4f91d0d5c fix(xfa): require full save when stripping XFA packets (#4568) 2026-10-24 21:03:25 -04:00
9aef0dfb78 refactor(xfa): explicit model imports (Sonar S2208) (#4403)
* refactor(xfa): replace model wildcard imports with explicit names (Sonar S2208)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(xfa): complete explicit model imports

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(xfa): drop unused model imports

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Import Corner, which main's parse.rs now uses

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-24 10:31:05 -04:00
Eric Kaden 96cf936b31 M12: XFA check boxes sit where their margins, caption and alignment put them (#4300)
* M12: XFA check boxes sit where their margins, caption and alignment put them

* M12: a check box's automatic caption room and auto height follow the box as drawn

* M12: a caption reserve of zero sizes the caption to its content, as no reserve does
2026-10-24 10:30:11 -04:00
aiongg 2208dc16c8 Integration: cargo fmt 2026-10-24 07:32:52 +00:00
aiongg 9705e7a6c1 Merge track r3-t03 2026-10-24 06:39:33 +00:00
aiongg c53757939f Merge PR #4338: M12: flowed XFA subforms are drawn with their border and fill
crates/xfa/src/tests.rs: both sides' appended tests kept.
parity/acrobat-features.toml: form.xfa-dynamic evidence and notes: both sides' insertions.
2026-10-24 06:24:41 +00:00
aiongg a637c39435 Merge PR #4319: M12: XFA borders and rectangles with rounded corners are drawn rounded
parity/acrobat-features.toml: form.xfa-dynamic evidence: both sides' entries; notes: both sides' insertions.
2026-10-24 06:24:30 +00:00
aiongg 541c82d59f Merge PR #4318: M12: links in XFA rich text become clickable links
crates/xfa/src/tests.rs: both sides' appended tests kept.
parity/acrobat-features.toml: form.xfa-dynamic evidence and notes: both sides' insertions (word-level merge).
2026-10-24 06:24:04 +00:00
aiongg 761353775b Merge PR #4317: M12: round XFA check buttons draw a round background and border 2026-10-24 06:23:48 +00:00
aiongg bcbd9cbf4e Merge PR #4287: M12: XFA text is measured at its standard-14 font's own widths
parity/acrobat-features.toml: form.xfa-dynamic evidence: both sides' entries; notes: PR's metrics sentence replaces 'metrics are approximate' on top of main's text (word-level merge).
2026-10-24 06:22:55 +00:00
aiongg 2a1ceebb4f Merge PR #4339: M6: fields that don't scroll stop taking text when full
parity/acrobat-features.toml: form.fill-text-field evidence is the union of both sides; notes keep both sides' sentences (DoNotScroll clause and the Password sentence).
2026-10-24 06:22:02 +00:00
aiongg c0687edbbe Merge PR #3552: M12: a date typed by another of an XFA field's edit patterns is kept in its first 2026-10-24 06:22:01 +00:00
aiongg 1fd37592d4 Merge PR #3401: M12: laying out a dynamic XFA form takes off the usage rights it voids
parity/acrobat-features.toml: form.xfa-dynamic evidence list: both sides' entries; notes: both sides' insertions (word-level merge).
2026-10-24 06:22:01 +00:00
aiongg 0cfde22386 Merge PR #3488: fix(forms): write each /DR font as its own object, so Acrobat keeps typed text (#3441)
crates/forms/src/tests.rs: append conflict, kept both tests.
2026-10-24 06:21:22 +00:00
a047c6ba09 chore: resolve SonarCloud maintainability issues (#4356)
* chore: resolve SonarCloud maintainability issues

* fix(packaging): use verify_sha256 helper function

* style: cargo fmt

* fix: restore wildcard imports and fix method-reference type errors

Revert the glob-import expansions (they missed SomIndices and left unused
imports), revert char::is_ascii_digit where the iterator yields char by
value, and pass the content slice to prune_doomed_xobjects as &[Vec<u8>].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep XObject decodability check without binding content in interp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
2026-10-23 23:28:18 -04:00
Eric Kaden 2fc251193e M12: a test that a section whose first child moves to the next page takes its border along 2026-10-23 23:16:10 -04:00
Eric Kaden 4d190aaff8 M12: the parity note and test say which sections lose their border 2026-10-23 22:52:53 -04:00
Eric Kaden cafd507f29 M12: page breaks of a section's own keep the border around it, a leader is given room with its row, and what runs off a page costs its section the border 2026-10-23 22:51:54 -04:00
Eric Kaden 35716c6282 M12: a flowed table a page break cuts between its rows keeps its border on each page 2026-10-23 22:29:04 -04:00
Eric Kaden 3cf6b0e589 M12: docstrings for the layout helpers and test the flowed-border change touches 2026-10-23 22:21:38 -04:00
Eric Kaden cc6b042f0d M12: a flowed subform's repeated top margins give way to a child that fits a fresh content area only without them 2026-10-23 22:15:07 -04:00
Eric Kaden a7d86dea76 M12: a flowed subform's last part keeps its bottom margin inside the content area 2026-10-23 22:05:46 -04:00
Eric Kaden d7da5dfe29 M12: a flowed XFA subform a page break cuts gets a closed border on each page, its margins repeated 2026-10-23 21:54:55 -04:00
Eric Kaden f5e74991ed M12: flowed XFA subforms are drawn with their border and fill 2026-10-23 19:30:43 -04:00
Eric Kaden 64bdea4e61 M6: fields that don't scroll stop taking text when full 2026-10-23 19:25:32 -04:00
Eric Kaden 352ebb1477 M12: a backslash ends a web link's host, as web addresses read it 2026-10-23 17:28:22 -04:00
Eric Kaden 3355edad42 M12: only a link's own underline joins its pieces, and web links need a host 2026-10-23 17:19:25 -04:00
Eric Kaden 3cf6e5f716 M12: a link's pieces don't join across a rule, picture or field between them 2026-10-23 17:05:18 -04:00
Eric Kaden d91028a990 M12: a link's address is shared by its runs, words and spans, not copied 2026-10-23 16:47:19 -04:00
Eric Kaden 0008a18345 M12: XFA borders and rectangles with rounded corners are drawn rounded 2026-10-23 16:41:22 -04:00
Eric Kaden 1c4a481ea3 M12: round XFA check buttons draw a round background and border 2026-10-23 16:19:43 -04:00
Eric Kaden 0b8758eda3 M12: links in XFA rich text become clickable links 2026-10-23 16:16:40 -04:00
Eric Kaden 441d9005e3 M12: XFA text is measured at its standard-14 font's own widths 2026-10-23 14:08:42 -04:00
mtlcl410andClaude Opus 5.5 941077715a fix(forms): write each /DR font as its own object, so Acrobat keeps typed text (#3441)
A text field PdfCraft added showed its typed text in Acrobat only while
the field had focus: as soon as the user clicked elsewhere the text
vanished (it was still stored, and printed). PDF-XChange Editor and
PDFgear were fine, and the same field made by MuPDF was fine in Acrobat.

Bisected in Acrobat with one-change variants of the same file: only the
variants whose /DR fonts are objects (or that have no /DR) keep the text.
The appearance stream's own fonts, /P, the appearance layout and /DA 0
made no difference. PdfCraft wrote Helv, TiRo and Cour inline in /DR.

ensure_form now adds them as objects, and turns fonts an earlier save
wrote inline into objects too (same dictionaries). An indirect /DR or
/Font stays the object it is. The XFA-to-AcroForm writer does the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:38:36 +08:00
Eric Kaden 91df3c01a8 Merge remote-tracking branch 'upstream/main' into fix/xfa-edit-picture-alternatives
# Conflicts:
#	crates/xfa/src/tests.rs
2026-10-22 21:54:56 -04:00
Dylan M. Taylor d31a96f46f Merge pull request #4121 from aiongg/fix/coderabbit-c1-js-xfa
fix(js, xfa): bound the text and work that form scripts and XFA data can multiply
2026-10-22 21:39:08 -04:00
aiongg 1e2ac1d85a fix(xfa): copy embedded field values out only as far as the text budget
The text layout cut each embedded value to the bytes its block had left, but
the resolver cloned the whole value first. With a byte or two left and a
value starting with a multibyte character, every later run cloned the full
value and kept none of it. The resolver is given the bytes left and returns
only a prefix that fits.
2026-10-22 20:21:21 +00:00
aiongg 668d8f777e fix(xfa): every drawn subform instance spends the layout budget
A positioned root draws its children instead of flowing them, so nested
repeating subforms under it multiplied their instances without spending
the measurement budget that flowed instances spend. Drawing a subform
instance spends it too.
2026-10-22 20:08:33 +00:00
aiongg ee33337627 fix(xfa): bound the name bytes of data nodes a write creates
Writing a value at a high index creates every missing sibling before it,
each with its own copy of the name. The node budget counted siblings but not
their names, so a long name with a high index made thousands of copies of
it. The write's budget also counts the bytes of the names it creates; a
path past it is skipped with the existing warning.
2026-10-22 17:22:50 +00:00
aiongg bac0e610db fix(xfa): bound the bytes a data import copies out of its file
Converting an imported data file copied each element's and attribute's
resolved namespace URI, so a long namespace declared once on the root was
copied into every element that inherits it; only the node count was
limited. The import counts the names, namespaces, attributes and text it
copies against a byte budget of four times the file-size limit, and refuses
the file as too large past it.
2026-10-22 17:22:50 +00:00
aiongg a07ad60d52 fix(xfa): queue each added data instance once per pass
write_data_ops expanded every instance-count op into one queued node per
instance, then deduplicated the pass. A script adding rows one at a time
asks for one more instance each time, so n rows queued about n*n/2 nodes
before the deduplication. Each op queues only the instances the pass has
not queued yet, and a budget bounds the instances queued across passes
that removals separate, with a warning past it.
2026-10-22 17:22:50 +00:00
aiongg 1dbfa25100 fix(xfa): bound the script text the live form's events hold
Every instance of a repeated subform records its own copy of each script,
and only the number of events was capped. A long script inside nested
repeating subforms was copied once per instance before any script ran. The
events hold at most as much script text as the XFA packets may; past that
the live form is marked truncated, as it is past the event cap.
2026-10-22 17:22:50 +00:00
aiongg 3412437191 fix(xfa): bound the embedded field text one text block expands
Rich text resolved every embedded-field reference to a fresh copy of the
field's value, so a template repeating one reference to a long value many
times laid out a copy per reference. One text block expands at most as many
bytes of embedded values as a text of its own may hold; past that the
values are cut.
2026-10-22 17:22:50 +00:00