Skip to content

Open the page area an <overflow target> names, on the container that failed - #20

Merged
tannevaled merged 1 commit into
mainfrom
overflow-firing-condition
Sep 5, 2026
Merged

Open the page area an <overflow target> names, on the container that failed#20
tannevaled merged 1 commit into
mainfrom
overflow-firing-condition

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

This package implemented none of <overflow>grep -rn overflow *.go found 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-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 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/probe now covers the content-layout side too: NOTFIT for every child that failed to fit, OVERFLOWFIRE for every ProcessOverflow(.., bCreatePage=true) with the site it was reached from, the overflow node in scope and bUseInherited, and BREAKOVERFLOW for every candidate target with create_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 of InsertFlowedItem, 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 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: 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 move (i-956 14→12, i-956g 16→15, i-956h 13→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.

before after
agree on x 173806/177676 (97.82%) 173806/177676 (97.82%)
agree on sheet+y 161937/177676 (91.14%) 162018/177676 (91.19%)
forms perfect on x 465/559 465/559
forms perfect on sheet+y 403/559 403/559

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:

form before after pdfium
i-956 Page2 ×14 Page2 ×2, Page3 ×10 Page2 ×16, Page3
i-956g Page2 ×11, Page4 ×5 Page2, Page3 ×9, Page4, Page5 ×4 Page2, Page3 ×11, Page4, Page5 ×11
g-1055 Page2 ×4, Page3 ×33, Page4 ×3, Page3 ×5, Page4 ×19 Page2, Page3 ×35, Page4, Page3 ×7, Page4, Page3 ×11, Page4, Page3 ×7 Page2, Page4, Page3 ×50, Page4, Page3 ×9, Page4, Page3 ×18, Page4, Page3 ×14

What does NOT land, said rather than tuned

The sequence lands for i-956g and g-1055 — Page3 and Page5 were never opened before. It does not land for i-956, and the sheet count lands for none of the three.

i-956 is the one form that gets worse, and the trace says why it is not the rule: pdfium fires there for sfPart13 only, and never has sfPart3 fail to fitNOTFIT names eleven distinct children over the whole form and sfPart3 is not among them, in either layout pass. So the page break at sfPart3 is a pre-existing pagination error that opening the right page area now costs a page area instead of nothing. Independently measurable: i-956 agreed 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 -l empty, -race, twelve cross-compile targets, CGO_ENABLED=0, GOWORK=off, all against Go 1.27.1.

🤖 Generated with Claude Code

…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
tannevaled merged commit 14d15e7 into main Sep 5, 2026
1 check passed
@tannevaled
tannevaled deleted the overflow-firing-condition branch September 5, 2026 12:14
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>
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