Skip to content

Reach a page area a break names, whose <occur max> is spent - #22

Merged
tannevaled merged 2 commits into
mainfrom
offpaper-diag
Sep 5, 2026
Merged

Reach a page area a break names, whose <occur max> is spent#22
tannevaled merged 2 commits into
mainfrom
offpaper-diag

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Six forms of the corpus lay their body on sheets pointing the wrong way, and it is one rule.

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 — the landscape page area, 612 points tall where the form wants 792 — so 403 leaves fell off the bottom.

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; the only max it reads is the page SET's (:1329-1341). An instrumented pdfium says the same at runtime.

So a page area's max bounds how often the SEQUENCE runs into it, 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, pager.cleanKids — and pager.reach is that same restart, asked for by a target.

What is not changed is the break that names nothing. Two thousand of the corpus's two and a half thousand breaks write no target, and there pdfium's scan accepts any page area, 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, 559 forms, hidden excluded from both sides

before after
body leaves off the paper 1378 1000
forms with one 37 34
sheets whose SIZE disagrees with pdfium 13 7
...of those, an ORIENTATION difference 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 the finding about the instrument: it compares x, and sheet+y. 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 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.

Also

doc.go's corpus census quoted 81 734 fields on 3 090 sheets; both went stale before v0.16.0. It is now what TestPlacementOverTheCorpus reports: 81 750 placed, 151 323 draws, 3 088 sheets, none left over, and the 12 furniture elements that belong to a page area no sheet of their form ever is. Its own commit.

Gate

gofmt clean, go vet clean, -race green, 100% statement coverage with XFACORPUS unset, CGO_ENABLED=0, GOWORK=off, Go 1.27.1 via GOTOOLCHAIN, and 13 cross-compile targets. The full suite is green against the corpus, pdf.js and pdfium references, before and after.

Two tests changed their expectation with the rule and are renamed for it; 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 reaches by id and which has no page set to start again.

Diagnosis for issue #7 is in the issue comment; findings and scripts at /Users/Shared/xfa-offpaper/.

🤖 Generated with Claude Code

tannevaled and others added 2 commits September 5, 2026 17:55
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>
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>
@tannevaled
tannevaled merged commit b4e9365 into main Sep 5, 2026
1 check passed
@tannevaled
tannevaled deleted the offpaper-diag branch September 5, 2026 15:58
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