TextBox::fits checked only the height of a wrapped multi-line value.
Long words break by character, so a single character wider than the
box still overflows it, and the appearance reports Overflow::Wide for
a value fits had accepted. The multi-line branch now also requires
every wrapped line to fit the width.
crates/forms/src/tests.rs: append conflict, kept both tests.
parity/acrobat-features.toml: form.fill-text-field evidence is the union; notes keep HEAD's text plus the comb-cells sentence.
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).
The on-screen drop-down button for combo boxes drawn display-only, the
narrowed text area beside it, and the Print as Image copy without it
(with_printed_field_appearances, printed_display_bytes) are removed.
Open PR #3567 draws the arrow for every combo box in the page view.
Check boxes, radio buttons, push buttons and comb lines are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
A form that leaves its appearances to the viewer had its text and choice
fields drawn for display (#3475), but not its buttons. Now, also for
display only, without changing the file:
- Check boxes and radio buttons with no normal appearance for their
state show their /MK background and border (Table 192), and when on,
the /MK /CA caption as a standard-14 ZapfDingbats character
(12.7.5.2.3), 4 or l by default, in the /DA colour, auto-sized for
0 Tf.
- Push buttons with none show their /MK background, their /BS border
(beveled per Table 168) and their centred caption.
- Comb fields with a border colour draw the MaxLen - 1 lines between
their positions (Table 228), up to 500 cells. This also applies to the
appearances PdfCraft saves for comb fields.
- A combo box drawn for display shows a drop-down button on screen, with
its text kept clear of it. Saved appearances and Print as Image leave
it out (with_printed_field_appearances, printed_display_bytes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
* 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>
The actions chained after a trigger's first one (/Next) are kept
whatever replaces it, not only another script, and the row reads
"…, then N more actions". Edit is disabled on every row while a
script is open, so it can't be overwritten.
Edit on a Run a JavaScript action loads its script into the editor;
Update replaces it on the same trigger, Discard leaves it. Saving the
tab now keeps actions that did not change as they are, and an edited
script keeps the actions chained after it (/Next).
* M6: calculations read their notation only from a comment, and a regex after a condition as a regex
Two findings from CodeRabbit's review of #3550, both reproduced first:
- parse_calculate and calculation_sites found BVCALC and EVCALC anywhere
in the script, so `var s = "BVCALC a EVCALC";` read as the notation `a`:
renaming `a` rewrote the string, and the form calculated `a` instead of
running the script. The lexer now keeps the text of each comment, and
the notation is read from the first comment that holds BVCALC and the
EVCALC after it (notation_range), by both the Calculate tab and a
rename. A line comment counts, and a comment that never ends runs to
the end of the script, as before.
- A `/` after any `)` divided, so after the condition of `if`, `while`,
`for` or `with` a regular expression was read as code: renaming `a`
rewrote `if (flag) /getField("a")/.test(x)`, and in
`if (flag) /"/.test(x)` the quote started a string that hid the
script's real getField calls. The lexer now notes which `(` opened
such a condition (not a method of that name, `o.if(x)`), and a `/`
after its `)` starts a regular expression.
af::tests::notation_is_read_only_from_a_comment,
af::tests::a_regex_after_a_condition_is_not_read_as_code and
tests::a_script_that_spells_the_markers_in_a_string_runs_as_a_script
fail without the fix. Parity, ROADMAP.md and docs/roadmap.md updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: form.props-calculate notes that a regex right after a brace is still read as code
From CodeRabbit's review of this PR. The lexer still divides after `}`,
which ends a block as often as an object literal, so in
`if (x) { } /"/.test(s); event.value = getField("a").value;` the quote
starts a string and the getField is hidden from a rename and from the
calculation order. Checked with a scratch test before writing it down.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: a line comment ends at any line terminator, so code after `\r` is still read
From CodeRabbit's review of this PR. A `//` comment ran to the next `\n`,
so with `\r` line endings it hid the rest of the script: a rename missed
the getField calls after it (since #3550), and a string after it that
spells BVCALC ... EVCALC read as the notation. A `/` taken for the start
of a regular expression likewise looked past `\r` for its end, and could
swallow the next line's code.
Both now stop at any of JavaScript's line terminators (`\n`, `\r`,
U+2028, U+2029; line_ends_at), and U+2028 and U+2029 between tokens are
skipped rather than read as the start of a word.
af::tests::a_line_comment_ends_at_any_line_terminator fails without each
of the three changes: with `\r`, the string read as notation `a`; with
the comment fixed but not the regex scan, `i++ / 2;\r` swallowed the
getField after it; without skipping U+2028, the getField after it was
not a call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: a word, number or regex's flags end at U+2028 and U+2029
From CodeRabbit's review of this PR. The scans for an identifier, a
number and a regular expression's flags took every byte past ASCII as
part of a word, U+2028 and U+2029 included, so in `x\u{2028}getField("a")`
the getField was part of one word and not a call: a rename missed it, and
the calculation order didn't read it.
`word` now takes a position and is false where line_ends_at is true, so
the three scans share one rule. The arm that skips U+2028 and U+2029
between tokens stays: without it, the punctuation arm would step one byte
into the character.
af::tests::a_token_ends_at_a_line_separator fails without the fix, for a
word, a number and a regex's flags alike; it and
a_line_comment_ends_at_any_line_terminator fail without the skipping arm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: the `while` that ends a `do` loop counts as a condition, said and tested
A `/` after the `)` of a do-while starts the next statement, so it starts a
regular expression there too. The comment on BEFORE_CONDITION says so, and
a_regex_after_a_condition_is_not_read_as_code covers it (from review).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Redact: refuse Redact marks written in place; remove replies and pop-ups listed on
other pages; delete form fields by object, not name; resolve Do by its last operand
like the renderer; sanitize drops PCAdded and flattens widgets before hidden-content
cleanup. Sign: freed signed objects and edits to an already-signed widget are
modifications. Cos: only reading trusts /Type /XRef to skip encryption.
Review follow-up on #2957:
- free_name picked a default name (Text1) that a parent field left empty
by a move still held at the top of /Fields, and attach then refused it
for every new field of that kind. The own names (/T) of the top-level
/Fields nodes are now skipped too.
- the editor sends nested fields' full names, so renaming address.city to
address.town took the move path: it copied the inherited keys onto the
field and moved it to the end of the parent's /Kids. A full name whose
parents are the field's current parents now renames only its own /T.
Field tree per ISO 32000-2 12.7.4 (Arlington Field*.tsv: /Parent, /Kids,
/T; FT, Ff, V, DV, DA, MaxLen inheritable).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
Review follow-ups on filling a field converting inline /DR fonts:
- A signature field whose /V sits on its parent counted as unsigned, so
filling another field rewrote /DR in a signed form. The check now
follows the /Parent chain (bounded at 64, like the field walk).
- A catalog holding /AcroForm as a direct dictionary was skipped; its
inline fonts are now converted and written back into the catalog.
Both tests failed before the change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
Review on #3943, point 1. has_password_values ran a full fields() walk
on every save and recovery snapshot, and clear_password_values walked
every field's ancestors again for each ancestor of each target.
has_password_flag now answers first, reading only /Ff, /Kids and /Parent:
the field tree from /Fields (seen set, 32 levels) and, as fields() does,
the widgets on the pages with the fields above them. A form without such
a field returns before any Field is built. clear_password_values finds
each field's ancestors once.
The test puts a page-only widget, a flag inherited from an unlisted
parent and a nested field whose widget is on no page each alone in a
document. Mutants that drop the page scan, the parent chain, the tree
walk or the descent into kids each fail it, as does treating every
ancestor as exclusive. Dropping the seen set or the early-out changes
only speed (equivalent).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review on #3943. A black-box test of Acrobat (synthetic form, Save As and
Ctrl+S, every decoded object searched) showed how it saves a Password
field: the typed value is nowhere in the file; a field with /DV is saved
holding /DV, one without is left empty; the asterisks are drawn from the
saved value (5 for a 5-letter default, not 13 for the typed password);
/DV and /AA are kept.
clear_password_values now does the same: it restores the (inherited)
/DV as the value, or leaves the field empty, and redraws the widgets, so
the appearance no longer shows how long the typed password was. A field
already at its default no longer counts as holding a password, so such
a form needs no cleaned copy. The parity note for form.fill-text says so
and lists the two limits Acrobat shares (a script copying the password
into an ordinary field, and an earlier revision kept by an incremental
save).
Mutants, each failing a test: no /DV restore, no redraw, the default
ignored in password_fields, emptiness ignored (an empty string in an
array). Dropping the empty-default filter was equivalent, so the filter
is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up: the helpers checked only the field dictionary's own /V. A value
inherited through /Parent or carried by a separate widget was missed, so the
document could be written as it was. Detection now uses the resolved value and
the widgets; clearing removes /V from the field, its widgets and every ancestor
only password fields inherit from, and gives the field an empty value of its own
where a shared ancestor value has to stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A text field with the Password flag kept its typed value in /V, so Save, a full
rewrite, Reduce File Size and the recovery snapshots all wrote it in plain text.
ISO 32000-2 §12.7.5.3, Table 231 (note to the Password flag): PDF processors
should never store the value in the PDF file. Each of those paths now writes a
copy without the values; the open document keeps them in memory, and the masked
appearance is kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up (CodeRabbit on #3488): a form an earlier PdfCraft made
should work in Acrobat once it has been filled here too, not only once a
field is added or edited. set_value now runs the same conversion (shared
with ensure_form through inline_fonts_as_objects and store_form_fonts);
it adds nothing, only changes where the existing fonts are stored.
Not in a form a signature covers (a signature field with /V): there the
file stays as the signer saw it apart from the value. PdfCraft's own
change check counts a /DR change after a form-fill certification as form
fill, but another validator may not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Review on #2957, point 1. Both entry points already trim the whole name
before splitting it, and a space beside a period stays in its part,
which matches black-box observation of Acrobat and ISO 32000-2 12.7.4.2
(only a period is barred from a partial name). The test pins that down;
three mutants (no trim in add_field, no trim in set_props, untrimmed
empty-part check) each fail it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now renames a field in the calculations that read it. After rebasing,
that runs for a dotted name too, which moves the field under new parents: the
sum and the script that read `a` read `Group.a`, and still compute after a
save and reopen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review on #2957: own_inherited wrote what the field inherits onto it before
check_place could refuse the move, so a refused move still changed the field.
What it inherits is now read first, counted as its own by the check, and
written only once the move is allowed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review on #2957: set_props took the field out of the tree before attach
checked the destination, so a refused move (a parent holding /MaxLen, say)
left it out of /Fields for a direct caller of the forms API. The read-only
check is now its own function, run before the field is detached.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review on #2957. The field list holds only terminal fields, so a parent
left empty by a move wasn't seen and a second field of its name could be
made beside it, by adding or by renaming. And a new field joining a parent
that holds /V, /DV or /MaxLen inherited them, with no neutral value of its
own to turn them off: such a parent now takes no field that doesn't hold
its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A field's own (partial) name can't contain a period (ISO 32000-2
§12.7.4.2), so Prepare a form refused MASTER.first. Acrobat reads such a
name as a full name: the field `first` under a parent field `MASTER`.
- add_field: a dotted name makes or joins the parent fields it names.
- set_props: a dotted name is the full name and moves the field there,
copying what it inherited onto it first; a plain name still renames
the field within its parent. Either way the new full name may not be
taken by, or hold or sit inside, another field.
- A field joining an existing parent doesn't take its Ff or Q.
- Field Properties and the Prepare a form name tags show the full name
instead of the last part.
- Names are capped at 32 parts; empty parts are refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A /Kids array listing one widget many times, or shared by several fields,
built a Widget for every listing, so a small file expanded into fields times
listings widgets. A widget now belongs to the first field that lists it, and a
page widget already owned is not adopted again.