Skip to content

Settle t657 against pdfium, stop turning the sheet for a hidden container's break, and place the last 24 - #18

Merged
tannevaled merged 2 commits into
mainfrom
t657-and-corner-anchors
Sep 4, 2026
Merged

Settle t657 against pdfium, stop turning the sheet for a hidden container's break, and place the last 24#18
tannevaled merged 2 commits into
mainfrom
t657-and-corner-anchors

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

What I could not settle

Why pdfium's own pagination is what it is for the twenty subforms it piles up. I settled that it does it and why the form makes it do it — the mechanism is three lines of pdfium quoted below — but I ran no controlled pdfium experiment. The dump was read, not re-produced under a variant, so the causal chain rests on pdfium's source and on the geometry agreeing with it, not on a subject I moved.

Where a hidden container's leaves belong. pdfium places them at the cursor, off the paper if that is where the cursor is, and us-irs__f4797's six agree with it to the point. Whether that is right — as against reporting them unplaced — is not something either reference settles, and this changes nothing about it.

The remaining nine sheet-count disagreements with pdfium are untouched and undiagnosed. us-uscis__g-1055 (64 against 96) is the loudest of them now.

t657: not a layout disagreement at all

Ours 27, pdf.js 27, pdfium 7 — and two references agreeing settled nothing. What settled it was that the body leaf sets are identical: 2 300 (kind, path) pairs each side, diff empty, and every one agrees on x to the point. The only count difference is 20 extra copies of the page area's own furniture field, one per extra sheet.

So it is neither a repeat count nor a fit. form1 is layout="tb" with 27 page-sized children, each exactly the content area. pdfium's sheets:

sheet what is on it y
0–3 Page1Page4 18
4 QuerySheet 18
4 Sub2015aSub2024ball twenty 774
5, 6 Page26, Page27 18

774 is the bottom edge of the content area. The 1 701 leaves under those twenty are at exactly our y + 756.0, every one, reaching y=1530 on a 792-point sheet.

Every one of those twenty, and no other top-level subform, carries an initialize script that writes form1.Sub20NNx.presence = "hidden". Page1Page4, QuerySheet, Page26 and Page27 have zero .presence assignments anywhere in the template. pdfium is built with pdf_enable_v8 and runs them; hidden then does three things:

  • PresenceRequiresSpace() is true only for visible and invisible (cxfa_node.cpp:5348-5353);
  • ProcessBreakBeforeOrAfter returns std::nullopt when the break's container does not require space (cxfa_viewlayoutprocessor.cpp:922-928), so nineteen <breakBefore targetType="pageArea" startNew="1"/> never create a page;
  • the forced page end is gated the same way (cxfa_contentlayoutprocessor.cpp:1922-1927), so a cursor already past the bottom does not end the sheet either.

Cursor never advances, breaks never fire. 7 is right for the document as its scripts leave it; 27 is right for the document with scripts unrun, which is what this package and pdf.js lay out. Nothing to change in the placer for t657.

The control the corpus handed back: one form, no scripts needed

Censused: exactly one container in 560 templates writes presence="hidden" and carries a live break. us-irs__f4797, three page-sized subforms under a tb root, the third presence="hidden" with <breakBefore targetType="pageArea" startNew="1"/>. pdfium: 2 sheets. This package: 3. pdf.js: cannot lay the form out at all, so pdfium alone.

Followed: pdfium, by the standard that decided px, mp, =0mm, the per-run occur bound and the root's absent layout. It is also the other half of a rule this package already kept — a hidden container is given no room by the flow above it, and a container that takes no room has no reason to turn the page.

After: 2 sheets, and all six leaves under the hidden subform agree with pdfium on x AND y to the point — 810.5301, 810.0000, 821.9990, 858.4554, 857.9991, 882.0000 on an 11-inch sheet.

Corner anchors: 24 of 24, and the number is 72 → 48

Not a new capability after all. heightOf already computes exactly pdfium's number — its positioned arm is max(y + h) over the children and measure adds the margin, which is DoLayoutPositionedContainer's fContentCalculatedHeight (cxfa_contentlayoutprocessor.cpp:1192-1204) handed to CalculateContainerComponentSizeFromContentSize (:543-560). It was never asked for at the anchor.

before after
unplaced over 560 forms 72 48
anchored by a corner 24 0
page area never used 48 48
fields placed 85 446 85 455
draws placed 155 995 156 010
sheets 3 091 3 090

All 24 are ca-cra__t183-fill-25e's <subform name="Part_A" anchorType="bottomLeft" y="55.034mm"> with no w, no h. Children reach 22.225mm = 63pt; 55.034mm = 156pt; 156 − 63 = 93, plus the content area's 18 = 111. pdfium puts Part_A_Heading at 111.0019. So does this — and so do the other 23, on sheet, x, y, w and h, to the last place a float carries.

Width is still refused. Only an anchor along the middle or the right edge moves a box by its width, no container of the corpus is one, and nothing here computes a container's content width.

The judge, re-audited because the population moved

24 boxes entered it. The pdfium control goes 167 632 → 167 651 paired and 163 753 → 163 772 agreeing: +19 and +19, reconciling exactly.

The other five pair only by hand. pdfium spells them Part_A[0].SIN_Line4[1][1], indexing a name that also occurs deeper in the same subtree, which somPath's rewrite does not reproduce. The judge drops such keys rather than mis-pairing them, so the agreement rate was never wrong — only blind to those five. Checked by hand: all five agree with pdfium to float precision.

Every judge still passes: pdf.js placement 176/176 exact, container heights 7 794/7 794, pagination 151 309 boxes all on the same sheet across 476 forms, pdfium x under a table row 19 522/19 522 and under a wrapping container 959/959.

Gate

100% of statements, go vet, gofmt -l empty, -race, nine cross-compile targets — all under Go 1.27.1 via GOTOOLCHAIN, CGO_ENABLED=0, GOWORK=off.

🤖 Generated with Claude Code

tannevaled and others added 2 commits September 4, 2026 23:23
pdfium gates the whole of break handling on presence: ProcessBreakBeforeOrAfter
returns nullopt when the break's container does not require space
(cxfa_viewlayoutprocessor.cpp:922-928), and the forced page end that would
otherwise catch an overflowing child is gated the same way
(cxfa_contentlayoutprocessor.cpp:1922-1927). PresenceRequiresSpace is true only
for "visible" and "invisible" (cxfa_node.cpp:5348-5353).

It is the other half of a rule this package already kept: a hidden container is
given no room by the flow above it, and a container that takes no room has no
reason to turn the page.

One instance in 560 templates, and it is a control rather than a theory.
us-irs__f4797 writes three page-sized subforms under a tb root, the third
presence="hidden" carrying <breakBefore targetType="pageArea" startNew="1"/>.
pdfium lays it out on 2 sheets; this package made 3. After this, 2 — and all
six leaves under the hidden subform agree with pdfium on x AND y to the point,
at y=810.5301, 810.0000, 821.9990, 858.4554, 857.9991 and 882.0000 on an
11-inch sheet. pdf.js is no help here: it cannot lay the form out at all.

Corpus: 3091 sheets to 3090. No other form moves, no field changes hands, and
the 72 unplaced are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…st 24

A container anchored by a corner other than its top left is moved by its own
size, and a container that writes no size is as tall as its contents. That
measurement was named and left in v0.13.0 as a new capability rather than a
fix. It is not new: [placer.heightOf] already computes exactly the number
pdfium uses. Its positioned arm is max(y + h) over the children and
[placer.measure] adds the container's own margin, which is
DoLayoutPositionedContainer's fContentCalculatedHeight
(cxfa_contentlayoutprocessor.cpp:1192-1204) handed to
CalculateContainerComponentSizeFromContentSize (:543-560). It was only never
asked for at the anchor. CalculatePositionedContainerPos (:588-650) then takes
the anchor off it, which [transformedBBox] already did.

Width is a different quantity and is still refused. Only an anchor along the
middle or the right edge moves a box by its width, no container of the corpus
is one, and nothing here computes a container's content width — so it is named
rather than guessed at. A turned container is refused before its size is asked
for at all, which is the true reason where both apply.

## What it is worth

    unplaced over the 560 forms   72 -> 48
    anchored by a corner           24 -> 0
    page area never used           48 -> 48 (unchanged)
    fields placed              85 446 -> 85 455
    draws placed              155 995 -> 156 010

All 24 are one node: ca-cra__t183-fill-25e's
<subform name="Part_A" anchorType="bottomLeft" y="55.034mm"> with no w and no
h. Its children reach 13.758mm + 8.467mm = 22.225mm = 63pt; 55.034mm is 156pt;
156 - 63 = 93, and the content area's 18 makes 111.

pdfium puts Part_A_Heading at y=111.0019. So does this, and so do the other 23:
every one of the 24 agrees with pdfium's dump on the sheet, x, y, w and h, to
the last place a float carries. Five of them pair only by hand — pdfium spells
them Part_A[0].SIN_Line4[1] and so on, indexing a name that also occurs deeper
in the same subtree, which the judge's somPath rewrite does not reproduce; it
drops such keys rather than mis-pairing them, so the agreement rate was never
wrong, only blind to these five.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit fdb222f into main Sep 4, 2026
1 check passed
@tannevaled
tannevaled deleted the t657-and-corner-anchors branch September 4, 2026 21:34
tannevaled added a commit that referenced this pull request Sep 5, 2026
* Bring the corpus census back to what the corpus says

doc.go's census block quoted 81 734 fields placed on 3 090 sheets, and both
numbers went stale before v0.16.0: every field of every body has been placed
since #18, and the corpus has come to 3 088 sheets since #21. The third line
counted the 16 fields that were left over, and there are none.

Measured by TestPlacementOverTheCorpus on the 560-template corpus, which is
where all four numbers come from and which reports them itself. The line that
replaces the leftover count is the one thing in the block that is still not in
one-to-one correspondence with the paper: 12 elements of the page areas' own
furniture belong to a page area no sheet of their form ever is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Reach a page area a break names, whose <occur max> is spent

Six forms of the corpus lay their body on sheets pointing the wrong way.
ca-cra t2042, t2121 and t2125, each in two years, write a portrait Pg1 with
<occur max="1"/> and a landscape page area beside it, and every body subform
carries <breakBefore targetType="pageArea" target="Pg1" startNew="1"/>. This
package refused the target once Pg1's max was spent and let the sequence answer
instead, which is the landscape page area — 612 points tall where the form
wants 792 — so 403 leaves fell off the bottom of the paper.

pdfium reads a page area's <occur max> in exactly one place: the branch of
GetNextAvailPageArea that REPEATS the page area in hand
(cxfa_viewlayoutprocessor.cpp:1552-1578). Every other route runs through
FindPageAreaFromPageSet_Ordered, which scans the siblings after the page area
in hand FOR THE TARGET and, finding none, starts the page set again from its
first child and takes the target there (:1582-1607). That function never reads
a page area's occur at all; the only max it reads is the page SET's
(:1329-1341). An instrumented pdfium says the same at runtime, on t2125:

  BREAKNODE breakBefore on subform:Page2 target="Pg1" resolved=pageArea:Pg1
  | GNAPA advancing: scan siblings after pageArea:Pg1
  | | FPAS_ORD startAfter=pageArea:Pg1 target=pageArea:Pg1     (no match)
  | | FPAS_ORD startAfter=-             target=pageArea:Pg1     (restart)
  | | GNAPA -> pageArea:Pg1 (RESTART of page set pageSet:)

So a page area's max bounds how often the SEQUENCE runs into it and not how
often a break may ask for it by name. This package already held the other half
of that rule — starting a page set again forgets the counts of the page areas
below it, see pager.cleanKids — and pager.reach is that same restart, asked for
by a target rather than by running off the end of the set.

What is NOT changed is the break that names nothing. Two thousand of the
corpus's two and a half thousand breaks are <breakBefore targetType="pageArea"
startNew="1"/>, whose page area is only the one in hand standing in for a
target; pdfium's scan accepts any page area there, so the sequence moves on and
a max still means something. Getting that wrong first cost 2 367 leaves on four
other forms, which is why breakTo now carries whether the template NAMED it.

Measured over the 559 forms pdfium lays out, hidden excluded from both sides:

                              before   after
  body leaves off the paper     1378    1000
  forms with one                  37      34
  sheets whose SIZE disagrees      13       7
  ...of those, orientation          6       0
  agree on x            173806/177676  unchanged
  agree on sheet+y      162200/177676  unchanged
  corpus sheets                   3088   3088

Six forms move and none is worse. t2125-24e 129 -> 0, t2042-24e 94 -> 0,
t2121-24e 17 -> 0, t2125-25e 178 -> 66, t2121-25e 96 -> 71, t2042-25e 67 -> 66;
the remainder on the 25e forms is this package's text measurement, not this
rule. t2125-25e's page-area sequence is now pdfium's sheet for sheet.

The judge did not move by one leaf, and that is worth writing down: it compares
x and sheet+y, so a leaf at the same place on a sheet of the wrong ORIENTATION
counts as full agreement. Nothing in this repository compares a sheet's size.

Nine forms in all carry a pageArea <occur max> a break names — the six above,
ca-cra t1135 in two years and us-uscis i-612 — and 60 carry one at all.

Two tests changed their expectation with the rule and are renamed for it, and
three are added: the untargeted break that must still move on, a page set whose
own <occur> is spent, and a <pageArea> written outside every page set, which a
target can reach by id and which has no page set to start again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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