Commit Graph
296 Commits
Author SHA1 Message Date
Erkan DogruandFredApps f6c59c0b93 fix: integrate persistent AcroForm script workflows with native prompts and appearance writeback (#5699)
* AcroForm: integrate persistent timesheet script workflows

* AcroForm: address review caching, worker stacks and live metadata

* M6: address second review of AcroForm script workflows

* M6: handle top-level ASI boundaries and verify cyclic calculation results

* M6: bound script workers and isolate pending completions across edits

* M6: make form resets visible before subsequent script reads

* M6: include calculated dependents in reset comparison baselines

* M6: bound reset writeback work before native completion

* M6: charge expanded reset selections and field name bytes

* M6: bound string conversion bytes and charge reset widgets

* M6: preserve unread defaults and bound prompt text conversion

* M6: bound reset calls early and preserve calculated field identity

* AcroForm: address grouped calculations, field identity and widget ownership

* AcroForm: retire field instances and address native review edge cases

* AcroForm: route comparison Save As and Print to the source document

* M6: dispatch text widget mouse-up scripts

* M6: honor fit bounds for form button icons

* AcroForm: bound calculation work and preserve reset and save integrity

* Harden AcroForm script requests and worker recovery

* Keep form callbacks and focus consistent across edits

* AcroForm: preserve reset ordering and bound edit callbacks

* Fix reset appearance ordering and XFA focus after row removal

* Keep XFA focus indexes and stopped reset appearances consistent

* Document form script execution and reset helpers

* Keep script workers bounded and reject partial callback edits

* Keep form events available after a document script reaches VM limits

* Keep main's first-value Simple calculation and restore the calculateNow helper test

AFSimple_Calculate (JavaScript) and the native Simple calculation count each field's first
value again, as #5304 landed them; summing every selection of a multiple-selection list is
a separate behaviour change. Restores the document-helper calculateNow test and the
AFSimple_Calculate doc comment, and adds native and JavaScript multiple-selection cases.

* Patch the callback field snapshot in place instead of rebuilding it

Each field callback rebuilt all field states (3,129 on the benchmark form), and the
retained realm held the previous snapshot, so stamping instance tokens copied it again.
The runner now keeps its last snapshot and patches only the values and flags that
changed; ScriptSession::stamp_fields stamps it before dispatch, the worker copies only
when a token changes, and the realm drops the job's snapshot when the job ends (field
identity is checked against the cached keys instead).

* An oversized job script is that script's error, not a refused edit

Realm::run refused a script too long or too deep to run as an execution refusal, which
made the engine reject the whole edit (setting a 300 KiB calculation script failed). As on
main it is now a plain script error that keeps the value; the realm is not touched.

* Read a leading #! line as a comment in the chain scan; follow main's refusal and conditional limits in tests

The nesting guard from #5079 now checks brackets, prefixes and conditionals; the document
worker's preflight still bounds arrow-function and right-associative chains and skips a
leading #! line as the guard does. A refused script is an error, not a refused edit, so
the preflight test now checks the edit goes through with the error reported.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-11 08:42:04 +01:00
Harrison BoothandFredApps 832a51e6d7 Fix #5211: persist JavaScript field colours (#5499)
* Fix #5211: persist JavaScript field colours

* Read current document state in field scripts

* Redraw form fields after validation changes

* Format validation redraw regression test

* Keep field list alive in redraw test

* Refresh fields only after same-field scripts

* Format conditional field refresh

* Borrow accepted field value for storage

* Redraw calculated fields from current appearance

* Format JavaScript field state merge

* Restore JavaScript runner document state

* Group JavaScript runner form inputs

* Read current field display state in scripts

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-10 23:43:44 +01:00
Harrison BoothandFredApps d0b17ef95f Add JavaScript push-button caption methods (#5505)
* Add push-button caption script methods

* Apply scripted caption with other field changes

* Complete caption field property initializers

* Apply scripted captions to every button widget

* Format caption regression test

* Cover caption getter errors and redo state

* Assert button caption undo state

* Reject button caption writes during format events

* Test empty push-button captions

* Validate push-button caption updates

* Keep no-widget caption test data alive

* Check caption clipping marker

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-10 23:21:48 +01:00
Harrison BoothandFredApps c73a53705b Fix #5211: make calculateNow synchronous (#5304)
* Fix #5211: make calculateNow synchronous

* Bound and cache calculation execution

* fix(engine): invalidate field calculation cache

* perf(js): build calculation scripts lazily

* Refresh calculation cache after page insertion

* Load calculations for document script helpers

* Assert imported calculation result

* Initialize formatted field-change state

* Update formatted calculation test expectations

* Include the referenced field in calculation test

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-10 22:48:50 +01:00
mtlcl410 d9ec80243b M12: XFA forms with many repeated rows lay out in linear time (form.xfa-dynamic) (#6776) 2026-11-10 10:42:57 +07:00
ljkx 0d853dace7 M6: the Calculate tab shows and edits a field's custom calculation script (#6630) (#6717) 2026-11-10 10:00:52 +07:00
bflatastic c45ac1a873 Merge main into merge/ui-print-optimize2
# Conflicts:
#	crates/ui-egui/src/app/files.rs
#	crates/ui-egui/src/tools/forms_ui.rs
2026-11-09 22:03:08 +07:00
bflatastic 2af11131ef Merge main into merge/xfa2
# Conflicts:
#	crates/automation/src/tools.rs
#	crates/automation/tests/it/automation.rs
#	crates/forms/src/tests.rs
#	crates/ui-egui/src/tools/forms_ui.rs
#	parity/acrobat-features.toml
2026-11-09 20:57:05 +07:00
bflatastic 2ce2d3cf81 Merge main into merge/ui-print-optimize2
# Conflicts:
#	crates/automation/src/redact.rs
#	crates/automation/src/tools.rs
#	crates/print/src/lib.rs
#	crates/render/src/inspect.rs
#	crates/render/src/lib.rs
2026-11-09 20:56:15 +07:00
bflatastic 74cc90dc50 Merge #6098: fix(forms): AFSimple_Calculate folds field values as it reads them, within an allowance
# Conflicts:
#	crates/forms/src/af/tests.rs
2026-11-09 18:28:11 +07:00
bflatastic c1d9dd1db1 Merge #4791: M12: a dynamic XFA form runs all its opening scripts and is laid out once, so a save no longer keeps a half-initialized form (#4790)
# Conflicts:
#	crates/engine/src/xfa.rs
#	crates/forms/src/tests.rs
#	crates/xfa/src/data.rs
#	crates/xfa/src/lib.rs
#	crates/xfa/src/tests.rs
#	parity/acrobat-features.toml
2026-11-09 18:26:29 +07:00
bflatastic a0b791853c Merge #4784: M6: a radio option whose export value is a number selects its own button, and its XFA data holds the item (form.fill-checkbox-radio) 2026-11-09 18:24:54 +07:00
bflatastic e917cc2cc6 Fit the landed changes together 2026-11-09 18:24:27 +07:00
bflatastic 3b3721405c Merge #4401: M6: a turned field keeps its place and its text runs along the turn (#4296) 2026-11-09 18:23:10 +07:00
bflatastic de333340a5 Merge #4532: M6: list boxes show and pick the option /I names, and a single-select list shows one value (#4531)
# Conflicts:
#	crates/automation/src/tools.rs
#	crates/forms/src/lib.rs
#	crates/forms/src/tests.rs
#	crates/forms/src/values.rs
#	crates/ui-egui/src/tools/forms_ui.rs
#	crates/ui-egui/tests/it/forms.rs
2026-11-09 18:17:35 +07:00
BFlat 9ee213eff7 Fix main's build and tests after the latest merges (#6706) 2026-11-09 19:12:07 +08:00
mtlcl410 4f71f96058 Merge remote-tracking branch 'upstream/main' into lane9-4784b
# Conflicts:
#	crates/automation/src/tools.rs
#	crates/automation/tests/it/automation.rs
#	crates/xfa/src/tests.rs
#	parity/acrobat-features.toml
2026-11-09 18:57:44 +08:00
mtlcl410 7096266c48 M6: give the custom-validation test's ScriptResult its value_set field 2026-11-09 18:41:47 +08:00
mtlcl410 7917ecb83f Merge remote-tracking branch 'upstream/main' into lane7-4496b
# Conflicts:
#	crates/automation/src/tools.rs
#	crates/automation/tests/it/automation.rs
#	crates/forms/src/lib.rs
#	crates/forms/src/tests.rs
#	crates/forms/src/values.rs
#	crates/ui-egui/src/tools/forms_ui.rs
#	crates/ui-egui/tests/it/forms.rs
#	parity/acrobat-features.toml
2026-11-09 18:23:59 +08:00
mtlcl410 e1a664220a Merge remote-tracking branch 'upstream/main' into lane4-4401b
# Conflicts:
#	crates/ui-egui/src/tools/forms_ui.rs
2026-11-09 18:20:39 +08:00
Dylan M. Taylor f64c853a3a fix(forms): comb detection ignores repeated boxes (#6077) 2026-11-09 18:19:30 +08:00
jonathanwxh-cell 0e199bb602 fix(forms): harden tab order, AF script parsing, and null/inherited tree entries (#5614) 2026-11-09 18:15:28 +08:00
Eric Kaden 7fa6f10720 M12: an XFA date field keeps what its patterns don't read as typed, and reads its own patterns first (#4511) 2026-11-09 18:13:08 +08:00
mtlcl410 7a19802a77 M6: radio buttons sharing an on state are exclusive unless RadiosInUnison (form.fill-checkbox-radio) (#4489) 2026-11-09 18:12:29 +08:00
mtlcl410 d920c8bdf3 Merge main into fix/listbox-selected-indices
crates/forms/src/values.rs: the read imports take both listed_indices and
selected_options (list selection) and main's listing_fields_named (radio
fields sharing a name).
2026-11-09 12:20:42 +08:00
mtlcl410 9619cc2e1e Merge upstream/main into fix/xfa-open-scripts-complete 2026-11-09 12:00:34 +08:00
mtlcl410 4e2235cf1b Merge upstream/main into fix/checkbox-kid-on-states 2026-11-09 11:57:54 +08:00
mtlcl410andClaude Opus 5.5 ea541c6076 Merge upstream/main into fix/radio-numeric-export
xfa data.rs: keep main's fields_with_ids re-export and the PR's radio
value re-exports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 11:34:06 +08:00
mtlcl410andClaude Opus 5.5 a60d78240c Merge upstream/main into fix/listbox-selected-indices
values.rs: keep the position-tracking choice matching and match each value
with main's option_named (export value first, then display text).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 11:24:32 +08:00
bflatastic 02fbca9fd3 Name a kid from its /Parent as written, and draw a referenced CMYK entry as the reference gives it 2026-11-09 09:59:06 +07:00
mtlcl410andClaude Opus 5.5 2d8b7048a2 Merge upstream/main into fix/checkbox-kid-on-states
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 10:57:10 +08:00
bflatastic 08c0a59212 Merge main into merge/redact-forms-misc
# Conflicts:
#	ROADMAP.md
#	crates/render/src/text/tests.rs
2026-11-09 09:56:59 +07:00
mtlcl410andClaude Opus 5.5 f996aef12f Merge upstream/main into fix/xfa-open-scripts-complete
Main's DataOp::Value now replaces a multi-select list's value-group picks in the
packet and in the data copy, so the reset writes through it and the separate
DataOp::Reset (and Want::picks) is dropped. The reset keeps the PR's indexed
targets and its pass over fields without widgets, beside main's floating-field
pass (which the unshown pass leaves embedded fields to), and finish() lays out
again for either an owed relayout or a changed embedded value. Parity notes and
evidence take both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 10:39:06 +08:00
Unhumanje ab7af72347 fix(forms): name tree nodes sharing one long /Kids array no longer exhaust memory (#5121) 2026-11-09 10:11:18 +08:00
jonathanwxh-cell a22bede984 fix(forms): resolve field and widget references in ResetForm and Hide actions (#5364) 2026-11-09 10:05:03 +08:00
bflatastic eb47b57b4f Merge #6610: M6: a field whose normal appearance gives no stream is drawn from its value, /MK and /BS (view.render-annotation-appearances)
# Conflicts:
#	parity/acrobat-features.toml
2026-11-09 08:19:52 +07:00
bflatastic 5e1d511216 Merge #6570: M6: a named kid is named up its own /Parent chain, and a radio option of any same-named field is chosen by the name (form.field-list)
# Conflicts:
#	crates/forms/src/read.rs
#	crates/render/src/inspect/annotations.rs
#	crates/render/src/inspect/tests.rs
#	parity/acrobat-features.toml
2026-11-09 08:19:22 +07:00
mtlcl410 d65ae4268e M6: a calculate script that sets its own field through the field object keeps that value (form.custom-scripts) (#6616) 2026-11-09 08:59:19 +08:00
mtlcl410 b9ffb68b12 M6: a product of empty fields is 0, not 1 (form.af-simple-calculate) (#6567) 2026-11-09 08:57:28 +08:00
mtlcl410 e83c200598 M6: a field whose /Kids loop back or name a root field keeps its other kids (form.field-list) (#6446) 2026-11-09 08:50:11 +08:00
mtlcl410 608ff62dae M6: a widget whose /Parent names a missing object is a field of its own (form.field-list) (#6445) 2026-11-09 08:49:56 +08:00
Harrison Booth 3b731f9458 Expose widget rectangles to field scripts (#6093) 2026-11-09 08:42:39 +08:00
b07a84f003 M5: a comment whose colour is written [r g b null] is drawn, as CMYK with the null read as 0 (view.render-annotation-appearances) (#6503)
* M5: a comment whose colour is written [r g b null] is drawn, as CMYK with the null read as 0 (view.render-annotation-appearances)

ISO 32000-2 Table 166 /C (and /IC, Tables 178, 180, 181; /MK /BC and /BG,
Table 192) is an array of 0, 1, 3 or 4 numbers whose length picks the
colour space. A four-entry array with a non-number in it, such as
[1 0 0 null] (PDFBOX-2699), made the /C parser give up, so a shape
without an appearance stream was not drawn at all. PDFium, MuPDF and
Poppler all read such an array as CMYK with the non-number as 0.

- annot: a four-entry /C or /IC reads non-numbers as 0; other lengths
  still need every entry to be a number.
- annot: the properties' fill uses the same parser (it read
  [r g b null] as RGB before), and /C no longer drops a non-number
  to make three numbers.
- forms: /MK /BC and /BG read a four-entry array the same way instead
  of dropping the non-number and drawing RGB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn

* M5: comment properties report the stroke colour as drawn; a reference among four colour entries is not read as 0 (view.render-annotation-appearances)

props() reads a non-FreeText /C through the appearance parser, so a
[1 0 0 null] stroke reports CMYK cyan as drawn, and grey and CMYK
strokes report their converted colour. A reference among the four
entries of a /C array (ISO 32000-2 7.3.10) is left unreadable instead
of standing in as 0, so /C [1 0 0 6 0 R] is never drawn cyan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn

* M5: re-run CI (fill_sign Escape test depends on the clock's second, passes locally)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn

* M5: the comment list reports a comment's colour as drawn (view.render-annotation-appearances)

summaries() accepted only a three-entry /C, so a Square with /C [1 0 0 null],
drawn cyan and reported cyan by props(), had no colour in the comment list;
grey and CMYK colours were dropped the same way. Non-FreeText comments now
read /C through drawn_color (the array itself followed when it is given by
reference), keeping the three-number fallback and the FreeText /DA handling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn

* M5: the render inspector lists a comment's colour as drawn (view.render-annotation-appearances)

The inspector accepted only a three-entry /C, with a referenced entry read
as 0, so a /C [1 0 0 null], a grey or a CMYK colour listed none, unlike the
comment list and the drawing. Non-FreeText comments now read /C by the
appearance builder's rule (one entry grey, four CMYK with a non-number as 0
and a reference unreadable, finite and clamped), with the three-number
fallback reading referenced entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn

---------

Co-authored-by: mtlcl410 <chuanlin@niar.org.tw>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 08:29:31 +08:00
mtlcl410 91b0933933 Merge remote-tracking branch 'upstream/main' into fix/radio-numeric-export
# Conflicts:
#	crates/automation/src/tools.rs
2026-11-09 03:43:46 +08:00
mtlcl410 850f6f8e04 Merge remote-tracking branch 'upstream/main' into fix/checkbox-kid-on-states
# Conflicts:
#	crates/automation/src/tools.rs
2026-11-09 03:34:49 +08:00
BFlat 43f24aa169 Draw Thai in Fill & Sign, text boxes, form fields, headers, footers and watermarks (#6632)
Refs #2354. Thai typed into these tools is shaped and drawn from the Thai face through Add text's writer (now pdfcraft_fonts::shaped_text), searchable and copyable, instead of being written as ?????; a build without a Thai face refuses it with a message.
2026-11-09 02:56:34 +08:00
mtlcl410andClaude Opus 5.5 ccc2cd0dee M6: a field whose normal appearance gives no stream is drawn from its value, /MK and /BS (view.render-annotation-appearances)
A widget's /AP /N that is neither a stream nor a state subdictionary with
a stream for /AS draws nothing (ISO 32000-2 12.5.5, Table 170), yet the
display copy treated any /N as the file's own appearance. Text, choice and
push-button widgets whose /N is null, a form XObject dictionary without its
stream data, or a subdictionary without /AS now get the drawn appearance a
widget without /N gets, and check boxes and radio buttons whose state entry
is not a stream are drawn from /MK. The test is the same one the renderer
uses to pick the stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 17:29:41 +08:00
mtlcl410andClaude Opus 5.5 8bb2d920a0 M6: a /Parent given through an object that is only a reference is followed in the field name (form.field-list)
qualified_name reads each ancestor through Document::resolve, which follows an object that is
nothing but a reference to another, as document inspection does, so both name the kid the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 12:49:40 +08:00
mtlcl410andClaude Opus 5.5 eee81da38b M6: a radio choice by the name is refused when any radio field of it is read-only, a claimed widget is read once, and inspection lists widgets beside named kids (form.field-list)
- fill: the first same-named field's read-only flag still applies when a later one draws the
  choice, and no radio field of the name is written when any of them is read-only.
- enumerate: a nameless /Fields widget its parent already claimed is not read again as a nameless
  field; one its parent's /Kids omits is still read on its own.
- walk: only kids whose /Subtype is /Widget replace a merged field's own widget.
- inspection: a kid whose /Parent is another field is named from that chain even under a direct
  listing field, and widget annotations beside named kids are listed as a field of the parent's
  name (ISO 32000-2 §12.7.4), as the form reader lists them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 11:21:59 +08:00
mtlcl410andClaude Opus 5.5 916ca60e80 M6: a field merged with a widget reads its widget kids as its buttons, and nameless widgets of an unlisted field are options of its name (form.field-list)
A field dictionary that also carries a zero-area widget and lists widget
kids now has the kids as its buttons (ISO 32000-2 §12.7.4: field and
widget merge only for a single widget). Widget annotations listed beside
named kids, and nameless /Fields entries whose /Parent /Fields doesn't
list, are read as buttons of that parent's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 06:33:46 +08:00