Open the page area an <overflow target> names, on the container that failed - #20
Merged
Conversation
…failed This package implemented none of <overflow>: `grep -rn overflow *.go` found the word only in comments. Three forms of the corpus therefore never opened page areas that both references open — us-uscis__i-956, i-956g and g-1055 put content on Page2 that pdfium and pdf.js put on Page3, and the two page areas draw different furniture, so it is a difference on paper. The rule was read off a pdfium trace rather than inferred. The probe at /Users/Shared/xfapdfium/probe now instruments the content-layout side as well: every child that failed to fit, every ProcessOverflow(.., bCreatePage=true) with the site it was reached from and the overflow node in scope, and every candidate break with create_over_flow_page_. Two things it settled. WHEN. All four ProcessOverflow(.., true) sites (cxfa_contentlayoutprocessor.cpp :2714, :2763, :2793, :2818) sit in the tail of InsertFlowedItem, past the branch that takes a child which fits (:2611-2654). So the condition is a child that FAILED TO FIT, which is what pdf.js says in one line at template.js:5180-5183. It is asked here at the two seams where the flow gives up on the room it has, placer.whole and placer.turnTo, and nowhere else. WHICH. The innermost container writing an <overflow> AT ALL, beginning with the element being placed — not the innermost writing a TARGET. One without a target SHADOWS an outer one that has one. That is the whole of the difference from the prototype recorded on the issue: us-uscis__i-956 writes <overflow target="Page3"> on P1 and a TARGETLESS <overflow leader="..."> on ten of P1's children, and pdfium opens Page3 for none of those ten. Measured. Corpus 3090 -> 3086 sheets, three forms moving. Against pdfium the pairing population does not move — 177676 paired body leaves, 9157 ambiguous keys dropped, both before and after — x is unchanged at 173806, and sheet+y goes 161937 -> 162018. Exactly one form moves on the judge and it improves, us-uscis__i-956g at 37 -> 118 of 538; no form is worse on either measure; us-irs__f4797, the fifth form carrying an overflow target, stays 255/255. The page-area SEQUENCE lands on pdfium's shape for i-956g (Page2, Page3xN, Page4, Page5xM, where Page3 and Page5 were never opened before) and for g-1055. It does NOT land for i-956, and the sheet COUNT lands for none of the three. Both are said rather than tuned: the trace shows pdfium never has i-956's sfPart3 fail to fit, so the extra page area there is a pre-existing pagination error this rule now costs a page area instead of nothing — that form already agreed with pdfium on sheet+y for 42 of 491 leaves before this change, and 42 after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tannevaled
added a commit
that referenced
this pull request
Sep 5, 2026
… more leaves (#21) * Cut a positioned container where its author permits it, and place 182 more leaves pdf.js refuses to split a container at `layout === "position"` BEFORE it reads `keep.intact`, so `<keep intact="none"/>` — the author's explicit "you may break this" — is unreachable in it. This package followed pdf.js and put 1 423 body leaves off the paper across 39 forms where pdfium put 25 across 6 (#7). pdfium reads the permission. `FindSplitPos` calls `FindLayoutItemSplitPos` (cxfa_contentlayoutprocessor.cpp:503-588), which switches on `CXFA_Node::GetIntact()`; and `GetIntact` (cxfa_node.cpp:1536-1591) answers ContentArea for a positioned subform — the cut is driven to nought and the container moves whole — UNLESS a `<keep>` is written, in which case `GetIntactFromKeep` returns what the author wrote. So the rule is NARROWER than the diagnosis on #7 stated. pdfium does not split positioned containers in general; it splits one exactly where the permission is written. The previous comment read the exemption off `bCalculateMargin = eLayout != Position`, which governs the margin arithmetic and not the gate. The population: 58 positioned subforms across 17 of the corpus's 560 forms. Not the 39 forms whose off-paper leaves the position clause dominates. ## The mechanism, which is pdfium's and not pdf.js's pdfium's split is a post-hoc slice of an already-laid-out item tree, so [placer.cuttable] is a different question from [placer.splittable] and answered by a different function. The container is never put on the flowing chain: it is laid out whole and then cut, and each part's children keep the y the template wrote for them, less however much of the container is on the sheets before — SplitLayoutItem's `pChildItem->s_pos_.y -= fSplitPos`. Letting the flow split it would have placed every child by the cursor instead. The cut is walked up out of whatever it lands inside, which is pdfium's guard at both ends. One difference, deliberate: pdfium recurses into a child whose intact is "none" — a field of a positioned container is one — and, finding nothing finer, CUTS IT IN HALF, emitting two layout items for the one node. A [Box] is one rectangle for one occurrence and [Layout] promises every body leaf appears exactly once, so the cut is REFUSED there and the container moves whole, which is also what pdfium does when FindSplitPos comes back at nought. ## Measured, against pdfium over 559 forms before after paired body leaves 177676 177676 ambiguous keys dropped 9157 9157 agree on x 173806 173806 (97.82%, unmoved) agree on sheet+y 162018 -> 162200 (91.19% -> 91.29%) forms perfect on x 465 465 forms perfect on sheet+y 403 403 Exactly two forms move and NO form is worse on either measure: us-uscis__i-956 42 -> 168 of 491 us-uscis__i-956g 118 -> 174 of 538 The judge's pairing was re-audited because this moves boxes onto sheets they were not on: the population is identical, 177676 paired and 9157 dropped before and after. Nothing became ambiguous, which is what the bisect refusal buys. i-956's page-area sequence goes from `Page2 x2, Page3 x10` to `Page2 x13, Page3` — pdfium's shape, `Page2 x16, Page3` — which repairs the regression #20 recorded rather than tuned away. Sheets: corpus 3086 -> 3088, i-956 12 -> 14 (pdfium 17). Off the paper: 1423 -> 1378 boxes across 39 -> 37 forms. ## The cut positions are pdfium's, to four decimals An instrumented build over all 560 forms attempts the cut FIVE times on four containers. i-956's sfPart2: behind=0.0000 room=162.1843 at=162.1843 5 children behind=162.1843 room=648.0000 at=744.1824 19 children behind=744.1824 rest 1 child pdfium's own trace: splitPos=162.1843, then 581.9981 from 162.1843, and three items of 162.1843 + 581.9981 + 222.0010. Independent arithmetic, same numbers, same partition of the 25 children. Two of the five attempts are refused because the cut would bisect a field: i-956's sfPart7_2 and i-956g's sfCertification. Both still move whole, and that is the named cost. Gate: 100% statement coverage, go vet, gofmt clean against Go 1.27.1, -race, thirteen cross-compile targets, CGO_ENABLED=0, GOWORK=off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Cover a positioned subform's own intact without the corpus The intact table left out the two arms that answer for a container carrying no <keep> — a positioned subform and a row, which pdfium's GetIntact reads the same way. The local run reported 100% because XFACORPUS is set here and the corpus reaches that line through a real form; CI, which sets no corpus, does not. A check that passed for the wrong reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- 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.
This package implemented none of
<overflow>—grep -rn overflow *.gofound the word only in comments — and three forms of the corpus therefore never opened page areas that both references open.us-uscis__i-956,i-956gandg-1055put content on Page2 that pdfium and pdf.js put on Page3, and the two page areas draw different furniture, so it is a difference on paper rather than a bookkeeping one.Follows the diagnosis on #7. That comment established the selector —
<overflow target>— and left the firing condition open, with a measured prototype that got the page-area sequence right and the sheet count wrong. This is the condition, read off a trace rather than inferred.The instrumentation
The probe at
/Users/Shared/xfapdfium/probenow covers the content-layout side too:NOTFITfor every child that failed to fit,OVERFLOWFIREfor everyProcessOverflow(.., bCreatePage=true)with the site it was reached from, the overflow node in scope andbUseInherited, andBREAKOVERFLOWfor every candidate target withcreate_over_flow_page_. Control that it observes rather than perturbs: the three forms still dump 17 / 24 / 96 sheets.What it settled
WHEN. All four
ProcessOverflow(.., true)sites (cxfa_contentlayoutprocessor.cpp:2714, :2763, :2793, :2818) are in the tail ofInsertFlowedItem, past the branch that takes a child which fits (:2611-2654). The condition is a child that FAILED to fit — pdf.js says the same thing in one line attemplate.js:5180-5183. It is asked here at the two seams where the flow gives up on the room it has,placer.wholeandplacer.turnTo, and nowhere else.WHICH. The innermost container writing an
<overflow>at all, beginning with the element being placed — not the innermost writing a target. One without a target shadows an outer one that has one. That is the whole of the difference from the prototype:i-956writes<overflow target="Page3">onP1and a targetless<overflow leader="…">on ten ofP1's children, and pdfium opens Page3 for none of those ten.Measured
Corpus 3090 → 3086 sheets; three forms move (
i-95614→12,i-956g16→15,i-956h13→12).Against pdfium, 559 forms, the audited judge. The pairing population does not move — 177 676 paired body leaves and 9 157 ambiguous keys dropped, identical before and after — so it needs no re-audit here.
Exactly one form moves on the judge and it improves:
i-956g, 37 → 118 of 538. No form is worse on either measure.us-irs__f4797, the fifth form carrying an overflow target, stays 255/255 — the control that the change reaches only what it should.Page-area sequence:
i-956i-956gg-1055What does NOT land, said rather than tuned
The sequence lands for
i-956gandg-1055— Page3 and Page5 were never opened before. It does not land fori-956, and the sheet count lands for none of the three.i-956is the one form that gets worse, and the trace says why it is not the rule: pdfium fires there forsfPart13only, and never hassfPart3fail to fit —NOTFITnames eleven distinct children over the whole form andsfPart3is not among them, in either layout pass. So the page break atsfPart3is a pre-existing pagination error that opening the right page area now costs a page area instead of nothing. Independently measurable:i-956agreed with pdfium on sheet+y for 42 of 491 paired leaves before this change, and 42 after.Nothing here is adjusted to bring a count closer.
Gate
100% statement coverage,
go vet,gofmt -lempty,-race, twelve cross-compile targets,CGO_ENABLED=0,GOWORK=off, all against Go 1.27.1.🤖 Generated with Claude Code