Read a measure written as a calculation, and place 16 522 more fields - #12
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.=0mm189 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 areca-cra__rc325-fill-26e(41) andca-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.
=0mmlr-tbThe 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 the0mminside=0mmhaving never noticed the=(utils.js:83-87). That is a property of a regular expression, not a statement about XFA — the shape in whichpxwas nearly settled wrongly in slice 1.pdfium's
CXFA_Measurementstrips a leading=deliberately (cxfa_measurement.cpp,SetString) and then parses the rest leniently. Read pastSetStringas asked, the surrounding behaviour is:FXSYS_wcstofskips leading' 'and nothing else (fx_extension.cpp:41-45), so a tab stops the number before it starts;general | allow_leading_plus. Two details matter: a number needs a digit but not one before the point (.5mm), and anewith no digits after it rolls back to the mantissa rather than failing, becausegeneralisfixed|scientific— which is why="5em"is five ems and not a syntax error;GetUnitFromStringcompares withEqualsASCII, 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, becauseToUnitInternalhas no arm for one andToUnitturns "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
TestPdfiumsOwnMeasurementTestsiscxfa_measurement_unittest.cpprun against this package:EqualsPrefix,NoPrefix,InvalidValues, and every case ofGetUnitFromStringincluding 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, becauseTryMeasureAsFloatconverts withToUnit(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-instare writtenw="-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 atcxfa_fffield.cpp:293andcxfa_ffwidget.cpp:288), and so does this package. pdf.js does not, because CSS cannot — it emitswidth:-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, andapartnormalises too so that a non-flowed box cannot hit it later.The judges, on the larger population this places
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.TestPlacementOverTheCorpusfails the run if the two do not add up, form by form, and it does add up over all 560.What earlier slices used
=0mmforFive test files used
="0mm"as their canonical "written and unreadable" fixture, because it was the corpus's real one. It reads now, so they use96px— a length this package refuses on purpose, settled in slice 1.What is left
lr-tb, which wraps its children onto linesBreaking 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 -lempty,-race, exactly 100% statement coverage, nine cross-compile targets,CGO_ENABLED=0,GOWORK=off, all against Go 1.27.1 viaGOTOOLCHAIN.