Skip to content

Read a measure written as a calculation, and place 16 522 more fields - #12

Merged
tannevaled merged 2 commits into
mainfrom
slice5-equals-measures
Sep 3, 2026
Merged

Read a measure written as a calculation, and place 16 522 more fields#12
tannevaled merged 2 commits into
mainfrom
slice5-equals-measures

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

71 230 of 81 750 body fields, on 2 737 sheets

Slice 4 placed 54 708. The disagreement with the prediction goes first, and it reconciles to the field.

h="=0mm" was the largest blocker at 16 936. That was the count of fields whose first blocker was =0mm, and — as slice 4 said of its own 44 246 — it is not a prediction of what removing it places. It places 16 522.

what became of a field whose first blocker was =0mm
placed 16 687
taller than a whole content area, and cannot be broken 249
16 936

189 fields that slice 4 placed are now reported unplaced, and that is the honest direction. Each sits in a container whose height slice 4 could not compute: the stack placed the child it had reached and stopped there, so part of the container went onto the sheet. Now the container measures — and measures taller than a whole content area, which cannot be placed without splitting it across a sheet. 128 of the 189 are one form, us-opm__sf144a; the rest are ca-cra__rc325-fill-26e (41) and ca-cra__rc343-fill-26e (20). 24 more fields were freed elsewhere by containers that now measure.

16 687 + 24 − 189 = 16 522, and 54 708 + 16 522 = 71 230.

slice 4 slice 5
placed 54 708 71 230
draws placed with them 106 181 130 846
sheets 2 268 2 737
below a height written as =0mm 16 936 0
a place computed and nowhere left to put it 5 794 6 208
under lr-tb 4 303 4 303
anchored by a corner, with no size of its own 9 9

The rule is pdfium's, and pdf.js agreeing here is worth little

pdf.js places all 16 936 by accident: getMeasurement's pattern /([+-]?\d+\.?\d*)(.*)/ is unanchored, so it finds the 0mm inside =0mm having never noticed the = (utils.js:83-87). That is a property of a regular expression, not a statement about XFA — the shape in which px was nearly settled wrongly in slice 1.

pdfium's CXFA_Measurement strips a leading = deliberately (cxfa_measurement.cpp, SetString) and then parses the rest leniently. Read past SetString as asked, the surrounding behaviour is:

  • FXSYS_wcstof skips leading ' ' and nothing else (fx_extension.cpp:41-45), so a tab stops the number before it starts;
  • the value is the longest number beginning what is left, parsed by fast_float in general | allow_leading_plus. Two details matter: a number needs a digit but not one before the point (.5mm), and an e with no digits after it rolls back to the mantissa rather than failing, because general is fixed|scientific — which is why ="5em" is five ems and not a syntax error;
  • a non-finite value is forced to nought;
  • the unit is the whole of the tail, matched exactly and with its caseGetUnitFromString compares with EqualsASCII, and pdfium's own tests assert "CM", "Cm" and "cM" are units it does not know.

There is no case for inf/nan, which fast_float reads and this does not, because the answer cannot differ: pdfium forces a non-finite value to nought, so it contributes nought points whatever unit follows, and not reading it leaves nought in a unit nothing knows — nought points as well. That is stated in the doc comment rather than left to be discovered.

An expression is not evaluated, deliberately. ="Foo.h * 2" has nothing numeric beginning it, so under pdfium's rule it is nought in a unit pdfium does not know — and a length in a unit it does not know is nought points, because ToUnitInternal has no arm for one and ToUnit turns "cannot convert" into nought. Nought is the reference's answer, not a shortfall standing in for one. The doc comment says so, and says why, so that nobody turns this into a script engine.

Two answers part company with the ordinary reading, and both are named in the doc comment. A calculated number with no unit is nought — ="5" is not five points, where "5" is — and the unit is matched with its case. Both are pdfium's; neither is written anywhere in the corpus, which writes exactly one calculated shape, h="=0mm", 955 times on 101 of its 560 forms and nothing else with a leading = at all.

Checked against pdfium's own unit tests, not against pdf.js

TestPdfiumsOwnMeasurementTests is cxfa_measurement_unittest.cpp run against this package: EqualsPrefix, NoPrefix, InvalidValues, and every case of GetUnitFromString including the three that differ only in case. Where pdfium states a value and a unit and this reports points, the points are what the same measurement comes to inside pdfium, because TryMeasureAsFloat converts with ToUnit(XFA_Unit::Pt) (cjx_object.cpp:429-436). The one line that is not a port is marked: NoPrefix's bare "5", which this package reads as points by its own settled rule.

The judge had a fault of its own, the same shape as slice 4's

Four draws of us-ssa__ss-5-ar-inst are written w="-0.106in" — a negative width — and became reachable for the first time here. The check called all four defects: boxes placed left of the container holding them.

They are not. A negative extent is not a box reaching left of where it was put: pdfium normalises a widget's rectangle before it uses it (CFX_RectF::Normalize, called at cxfa_fffield.cpp:293 and cxfa_ffwidget.cpp:288), and so does this package. pdf.js does not, because CSS cannot — it emits width:-0.11px, which a browser ignores. The judge was comparing our normalised left edge against pdf.js's unnormalised origin: the same box, counted two ways. Normalising both sides puts it back to 0, and apart normalises too so that a non-flowed box cannot hit it later.

The judges, on the larger population this places

container heights
heights this package computes, paired with pdf.js's 7 764
agree to within 1/100 pt 7 764 100.00%
disagree 0 0.00%
more paired but written outright in the template — no check in agreeing 5 289
this package cannot measure, so has no number to compare 33
where the boxes went
boxes where pdf.js emits a real place 176
agree exactly 176 100.00%
boxes pdf.js placed by flexbox 152 446
... at the flow container's own origin, where a first child goes 149 488
... below or to the right of it, where the rest go 2 958
... above or to the left of it, which would be outside it 0 0.00%
which sheet they went on
forms where this package placed every element of the body 431
of those, agreeing with pdf.js on the number of sheets 428 99.3%
boxes paired on those forms 117 378
on the same sheet as pdf.js put them 117 378 100.00%
on another sheet 0 0.00%
forms placing fewer elements, and so using MORE sheets — a defect 0 0.00%

The three sheet-count disagreements are the same three as slice 4 — us-opm__sf39a, us-opm__sf813, us-uscis__i-821 — and are not this slice's: two have a positioned outermost subform, whose fit-check this package has never done (layout.js:355-364).

What none of it covers, unchanged: where inside a container the children ended up; borders, margins and insets on positioned layouts; anything under a rotated ancestor; breaking one container in two across a sheet; and real per-glyph advances, which neither side has. Still 77 of 560 forms pdf.js cannot lay out.

The invariant

Every field and every draw of the body is in exactly one of a page's boxes or Layout.Unplaced, each unplaced one with a reason. TestPlacementOverTheCorpus fails the run if the two do not add up, form by form, and it does add up over all 560.

What earlier slices used =0mm for

Five test files used ="0mm" as their canonical "written and unreadable" fixture, because it was the corpus's real one. It reads now, so they use 96px — a length this package refuses on purpose, settled in slice 1.

What is left

placed 71 230
a place computed and nowhere left to put it — mostly a container to break across a sheet 6 208
under lr-tb, which wraps its children onto lines 4 303
anchored by a corner, with no size of its own 9

Breaking one container across a sheet is now the largest, and lr-tb — which has the text measurement it needs since slice 4 — is the next.

Gate

go vet, gofmt -l empty, -race, exactly 100% statement coverage, nine cross-compile targets, CGO_ENABLED=0, GOWORK=off, all against Go 1.27.1 via GOTOOLCHAIN.

tannevaled and others added 2 commits September 3, 2026 23:20
The corpus writes h="=0mm" on 955 draws across 101 of its 560 forms, and
that one shape held up 16 936 fields: a stack cannot say where its next
child begins while a height above it is unreadable.

pdfium's CXFA_Measurement strips a leading "=" deliberately
(cxfa_measurement.cpp, SetString) and then parses the rest leniently —
the longest number beginning it, the unit from the whole of the tail, a
non-finite value forced to nought, and no failure to report anywhere.
That is Foxit's implementation, the closest to Adobe's, and its own unit
tests say so: L"=5" is five and L"=" is nought in no unit.

pdf.js reaches the same answer for "=0mm" and does not mean to: its
pattern is unanchored and finds the "0mm" inside the string having never
seen the "=" (utils.js:83-87). Agreement with it is therefore weak
evidence here, so the tests are a port of
cxfa_measurement_unittest.cpp instead.

An expression is NOT evaluated. ="Foo.h * 2" parses to nought under
pdfium's rule, and nought is the reference's answer rather than a
shortfall standing in for one; the doc comment says so, so that nobody
turns this into a script engine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
71 230 of 81 750 body fields on 2 737 sheets, from 54 708 on 2 268. The
16 936 whose first blocker was "=0mm" reconcile to the field: 16 687
placed, 249 now taller than a whole content area — and 189 that slice 4
placed are reported unplaced, because a container whose height could not
be computed had part of itself on the sheet and now measures taller than
any content area.

The judge counted four negative-width draws of us-ssa__ss-5-ar-inst as
boxes outside their container: it compared our normalised left edge with
pdf.js's unnormalised origin. Both sides are normalised now, as pdfium
normalises a widget rectangle before it uses it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit bc6038c into main Sep 3, 2026
1 check passed
@tannevaled
tannevaled deleted the slice5-equals-measures branch September 3, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant