Offer a page set's pages again when it starts again, and place the last 1 887 - #15
Merged
Conversation
A page area's <occur max> bounds how many sheets it makes in ONE RUN of the page set holding it, not how many the form gets. Restarting the set offers them afresh. Both references say so and neither quite carries it out, and reading the max as a bound on the form left 1 887 fields on 10 forms off the paper. pdfium says it in a line. FindPageAreaFromPageSet_Ordered walks a set from its first child and sets cur_page_count_ = 1 on the page area it settles on (cxfa_viewlayoutprocessor.cpp:1240-1244), and cur_page_count_ is the counter GetNextAvailPageArea tests the max against (:1414-1425). What bounds the whole run is the SET's own max, read against page_set_map_ (:1196-1215), which nothing resets. pdf.js means the same and cannot reach it. Its $cleanPage (template.js: 4160-4167) is exactly that reset, but it sits in the LAST branch of PageSet[$getNextPage] (:4230-4231), below the branch that restarts a usable set (:4222-4227) — and a set with no <occur> is usable for ever ($isUsable, :4169-4174, whose first clause is !this.occur). 559 of the 560 corpus forms write a page set with no <occur> at all. So the restart fires, is handed back a page area whose own max is spent, and recurses until the stack is gone. That is the seven-form crash slice 3 guarded rather than answered, and guarding a recursion is not the same as answering it: the sequence now terminates because it has a page to give. Second, an absent max bounds nothing whatever the min says. Occur[$clean] reads as though a written min with no max pins the max to the min (template.js: 3925-3932), but that branch cannot fire on an attribute nobody wrote: the constructor tests attributes.max !== "" (:3896-3903) and a missing attribute is undefined rather than "" (_mkAttributes, parser.js:79-111), so getInteger's default of -1 is taken and $clean's this.max === "" is already false. That is pdf.js arriving somewhere by accident, so it is not read from pdf.js — pdfium asks with the default suppressed, TryInteger(Max, /*bUseDefault=*/false), and takes -1 too. The two agree by different routes. It decides one form outright: us-ssa__ssa-3371-bk writes <occur min="1"/> on its only page area, and pdf.js's own dump for it is nine sheets. Third, a page set whose own <occur> is spent with no page set above it is the end of the form. pdf.js's answer there is its second infinite loop; pdfium returns nullptr (:1450-1470). Without it a page set's <occur> would bound nothing at all, and it is the one branch that still says the form ran out. Last, a form starts with the page set's pageSetIndex at -1 rather than at pdf.js's 0 (template.js:5487), which said a nested page set had been offered when none had. It changed no answer while the restart did not clean; it does now. pdfium looks at the siblings after the spent page area, descending into a nested set as it meets one (:1444-1447, :1249-1258). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…orms TestTheSevenFormsPdfjsRecursesForeverOn builds the page set of each of the seven corpus forms pdf.js exhausts its stack on — ca-cra__rc1-fill-11-25e, ca-cra__t2042-fill-24e and -25e, us-ssa__ha-4631, us-ssa__ssa-372, us-ssa__ssa-5062, us-ssa__ssa-766 — transcribed attribute for attribute, since no corpus document enters this repository, and asks each for more sheets than it has page areas. Every one of them is one shape: a page set with no <occur> over page areas that all write a max. TestTheEighthFormIsNotTheSeven is us-ssa__ssa-3371-bk, which pdf.js DOES lay out, on nine sheets, from an <occur min="1"/> and nothing else. TestAPageSetIsStartedAgainAtMostOncePerSheetAsked keeps the guard honest by naming the one shape that still needs it once the restart cleans: a set holding no page area of its own and one nested set that may run once, so the two hand back and forth with no page between them. The tests that were about a form running out of pages now cap the page SET rather than the page area, because a capped page area in an uncapped set no longer ends anything — which is the change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reconciled field by field against main, 81 750 dispositions paired on (form, path, occurrence) with the same multiset of keys: 1 887 placed, 0 lost, 0 fields placed both times on a different sheet. Third slice running with no loss anywhere. 1 760 of the 1 887 are on forms pdf.js cannot lay out at all — the seven it recurses to death on, plus ca-cra__t2121-fill-24e and -25e, which have the same page set and die of its font defect first. The remaining 127 are us-ssa__ssa-3371-bk, the only external check this change has: the form goes from one sheet to nine, its 190 body boxes all pair, and every one lands on the sheet pdf.js put it on. The pairing re-audited rather than the rate, because more sheets means more boxes: of 162 353 body boxes on the 473 agreeing forms, 150 982 paired, 6 781 unnamed and so never keyed, 4 590 keyed but absent from pdf.js's dump (1 900 presence="hidden"), and 0 dropped because the two sides counted a key differently. The same audit on main gives 6 781, 4 590 and 1 900 — identical — so the newly comparable form contributed 190 boxes and every one paired. 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.
[2026-09-04 15:02:00 CEST]81 738 of the 81 750 body fields placed, on 3 088 sheets. Slice 7 placed
79 851 on 3 040. The whole of the 1 887 that had not moved since slice 5.
The disagreement first: the bucket was its label, and the label was pdf.js's crash
1 887 is what "the page set gives no page after the last one" reported, and
classified before any code was written, that is exactly what it was — with a
twist that changes what the slice is.
All ten forms carry page areas whose
<occur>writes amax, under a pageset with no
<occur>at all. That is precisely the shape pdf.js exhausts itsstack on. Nine of the ten are among the 77 forms pdf.js cannot lay out:
ca-cra__t2121-fill-24e,-25eus-ssa__ssa-3371-bkSo slice 3's guard was the right shape and the wrong result. It terminated
by saying the form had run out of pages. It had not.
A page area's
<occur max>bounds ONE RUN of the set, not the formRestarting a page set offers its page areas again. Both references say so and
neither quite carries it out.
pdfium says it in a line.
FindPageAreaFromPageSet_Orderedwalks a set fromits first child and sets
cur_page_count_ = 1on the page area it settles on(
cxfa_viewlayoutprocessor.cpp:1240-1244) — andcur_page_count_is thecounter
GetNextAvailPageAreatests the max against (:1414-1425). What boundsthe whole run is the set's own max, read against
page_set_map_(
:1196-1215), which nothing resets.pdf.js means the same and cannot reach it.
$cleanPage(
template.js:4160-4167) is that reset, but it sits in the LAST branch ofPageSet[$getNextPage](:4230-4231), below the branch that restarts a usableset (
:4222-4227) — and a set with no<occur>is usable for ever(
$isUsable,:4169-4174, first clause!this.occur). 559 of the 560 corpusforms write a page set with no
<occur>. So the restart fires, is handed backa page area whose max is spent, and recurses until the stack is gone.
Cleaning the page areas on the restart is the change. Termination is then not a
counter but an answer: the restart has a page to give.
Demonstrated on the seven by name, not asserted.
TestTheSevenFormsPdfjsRecursesForeverOntranscribes each form's page setattribute for attribute — no corpus document enters the repository — and asks
each for more sheets than it has page areas.
An absent
maxbounds nothing, whatever theminsaysOccur[$clean]reads as though a writtenminwith nomaxpins the max tothe min (
template.js:3925-3932). That branch cannot fire on an attributenobody wrote: the constructor tests
attributes.max !== ""(:3896-3903), anda missing attribute is
undefinedrather than""(_mkAttributes,parser.js:79-111) — sogetInteger's default of-1is taken and$clean'sthis.max === ""is already false. Onlymax=""written out reaches it, andnothing in the corpus writes one.
That is pdf.js arriving somewhere by accident, so it is not read from pdf.js.
pdfium asks the same question with the default suppressed —
TryInteger(XFA_Attribute::Max, /*bUseDefault=*/false)— and takes-1. Thetwo agree on the number by different routes.
us-ssa__ssa-3371-bkwrites<occur min="1"/>on its only page area. Read as amax of one it gives one sheet and 127 fields fall off; pdf.js's dump says nine.
The reconciliation, field by field
81 750 dispositions from
mainand from the branch, paired on(form, path, occurrence), same multiset of keys:79 851 + 1 887 = 81 738. Third slice running with no loss anywhere.
The judges, and the pairing re-audited rather than the rate
The one external check this change has is
us-ssa__ssa-3371-bk, and it is agood one: 9 sheets against pdf.js's 9, 0 unplaced, 190 body boxes, 190 paired,
190 on the sheet pdf.js put them on.
The pairing, audited because more sheets means more boxes and different
keys. Of 162 353 body boxes on the 473 agreeing forms: 150 982 paired,
6 781 unnamed and so never keyed, 4 590 keyed but absent from pdf.js's dump
(1 900
presence="hidden"), and 0 dropped because the two sides counted a keydifferently. The same audit against
maingives 6 781, 4 590 and 1 900 —identical — so the one newly comparable form contributed 190 boxes and every
one of them paired.
One correction that came with it, and it is measured by one test
pdf.js starts a form with
pageSetIndex: 0(template.js:5487) on the page setthe first sheet came from, which says a nested page set has been offered when
none has. It changed no answer while the restart did not clean, because the
restart offered the nested set on its second pass. It does now, so it is
-1here — which is what pdfium does, looking at the siblings after the spent page
area and descending into a nested set as it meets one (
:1444-1447,:1249-1258). No corpus form nests a page set — 560 forms, 560 page sets —so this is measured by
TestTheSequenceDescendsIntoANestedPageSetand bynothing else, and it is stated rather than buried.
A fourth branch also changed: a page set whose own
<occur>is spent, with nopage set above it, is the end of the form. pdf.js's answer there is its second
infinite loop (
:4230-4231cleans the page areas below and calls itself,leaving its own
pageIndexat the end of the list); pdfium returnsnullptr(
:1450-1470). Without it a page set's<occur>would bound nothing at all,and it is now the one branch that still says the form ran out of pages.
The two open items are not this, and I checked before assuming so
breakBefore/positioned sheet-count disagreements —us-opm__sf813,us-opm__sf39a,us-uscis__i-600a,us-uscis__i-821— arethe same four, unchanged. None of them writes an
<occur>on any pagearea, so neither half of this change can reach them.
us-uscis__i-956andi-956g, are likewise unchanged and likewise write no<occur>.Both stay open.
What is left
Twelve fields of 81 750. What is left is not a bucket any more: it is the
positioned fit check (
layout.js:355-364), thebreakBeforedrift, and thei-956 overshoot — three sheet-count questions, all of them judged.
🤖 Generated with Claude Code