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.
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.
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.
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.
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.
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.
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.
Text layout broke a word wider than its line by measuring the whole growing
piece after every character, and counting its characters again each time.
A long unbroken word in a wide box with a small font made that quadratic.
The piece's advance is kept as a running sum of character widths instead.
The packet reader decoded the stream of every /XFA array entry and
charged only the decoded output to its 64 MB budget, so one stream that
decodes to almost nothing (Flate over hex whitespace), listed many
times, repeated a large decode for free. A stream reference already
read is skipped.
Nested repeating subforms multiplied their instances (up to 50 per level
over 64 levels), and an empty instance neither measured nor placed
anything, so it spent none of the measurement or item budgets. Each
instance now counts as a measurement, and layout stops with TooLarge
once the budget is spent.
#3515 measures a growing non-interactive field at its text's scale but
with a widget's 4pt of padding, while the layout draws its text without
it. Once #3521 fits lines by their ink, the narrower measuring width
wrapped to one more line than the drawn text, leaving a blank line
below it. The measurement leaves out the padding the draw doesn't have.
crates/xfa/src/data.rs: #2993's month and weekday helpers and date_tokens doc, with date_tokens kept pub(crate) for #3782's formatted_value and #3397's data pictures.
crates/xfa/src/tests.rs: union of fixture imports (tiny_bmp, tiny_tiff).
parity/acrobat-features.toml: form.xfa and the XFA image entry: evidence lists unioned, notes take both sides' insertions.
ROADMAP.md, docs/roadmap.md: all status lines and progress-log lines kept; M12 row takes both 'Done since' insertions.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
ROADMAP.md, docs/roadmap.md: both status lines and progress-log lines kept; M12 row takes both 'Done since' insertions.
crates/automation/tests/automation.rs, crates/xfa/src/fixtures.rs: union of #2624's and #2619's tests and fixtures.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
crates/engine/src/tests.rs: union of #2624's tests and #3408's test.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
crates/xfa/src/script.rs: template_field and named_in moved to crate::data in #3397; field_grows_with_text calls crate::data::template_field.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
crates/xfa/src/layout.rs: both sides added doc comments to value_height, caption_reserve, field_height, unique_name and draw_field; kept HEAD's, with #3542's natural_width.
crates/engine/src/tests.rs: union of #3180's new test and #3397's doc comment on the next test.
crates/xfa/src/layout.rs: kept #3782's formatted_value (it calls date_pattern, now built on date_letters) and button_action doc.
crates/xfa/src/parse.rs: parse_field doc comment from #3397 (names the data picture).
crates/xfa/src/script.rs: field() doc from #3582 with #3397's "as scripts read it".
crates/xfa/src/tests.rs: union of fixture imports and of the new tests; one doc comment each for the date-conversion and data-fill tests.
2026-1-2 isn't an ISO date and stands as it is. A test also pins that
parentheses hold a picture's locale (date(en_US){YYYYMMDD} formats) and
that a clause without braces has no pattern.
A date the field's data picture writes as 20261018 reaches scripts as
2026-10-18, the rawValue another viewer gives them, so a script that
checks String(rawValue).length == 10 works (DA Form 31's 10b TO).
Doc comments on the functions this branch touches.
Fields keep their <format><picture>. A date display picture turns the
ISO value into its pattern for scripts' formattedValue (JavaScript and
FormCalc), so date arithmetic on formattedValue works as forms expect.
Once a script sets the value, formattedValue gives that value. Number
and text display pictures still give the raw value.
A button was given its content area (inside its margins) as its widget
rectangle, though its border belongs to the field: DA Form 31's
Instructions button, with a 1.25 pt top margin, came out shorter than
the Attachments Menu button beside it. Its box is now the whole field;
the margins pad the caption.
A button with no height was sized like a text field in its field font,
with text-field padding, so DA Form 31's Instructions button (10 pt
field font, 8 pt caption) came out about 17 pt tall and over the form
below it. It is now as tall as its caption, never below its minimum.
A draw or a button with no width took all the width available, so a
right-aligned label slid to the far side of its subform and a button
spread over what lay beside it: DA Form 31's Instructions button covered
its header and the email line, and its PERSONNEL/FINANCE line ran into
the field after it. They now grow to their text, unwrapped, within what
is available and never below their minimum width, as containers do.
A word of spaces alone draws nothing, but after a word whose own
trailing space ran past the box it failed the fit test and opened a
blank line, so the text measured a line taller than it draws. Such
words now always stay on the line, keeping their width.
The layout draws a non-interactive field's text itself, condensed, but
measured its height at full width, so a condensed value reserved lines
it didn't use. Fields now measure and draw their text in one face:
scaled when the layout draws it, as designed when the forms layer does
(the over-page fallback included).
Words keep the space after them, and the line-fit test counted it, so a
label written with a trailing space wrapped when its box was a fraction
of a point narrower than the label plus that space: DA Form 31's 'a.
DATE ' and 'b. TIME ' put their second word on top of the field below.
A line now fits by what it draws, and alignment ignores every trailing
space, not just one.
fontHorizontalScale on a font, and xfa-font-horizontal-scale in rich text,
condense the glyphs; PdfCraft ignored both, so condensed labels were laid
out at full width and overflowed or wrapped where the form fits them on
one line. Text is now measured at its scale and written with Tz, in a
graphics state of its own. Field values keep full width: the forms layer
draws them, and its appearances have no horizontal scaling.
A two-digit year reads back as 1950 to 2049, so 2050 written as 50 came
back as 1950. A date is now written in the data picture only when it
reads back from it unchanged.
The template field behind a SOM path is now found by its number, as the
form numbers same-named siblings and repeated instances; the live form's
value refresh uses the same lookup. A data picture without a year, month
and day (date{YYYYMM}) keeps ISO dates in the data, since it can't hold
the date the field shows.