Cut a positioned container where its author permits it, and place 182 more leaves - #21
Merged
Conversation
… 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>
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>
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>
tannevaled
added a commit
that referenced
this pull request
Sep 5, 2026
…red (#24) Two measurements this repository could not make, and one number it published that a sharper instrument contradicts. THE SHEET. Every judge here compares a box — its x, and the sheet it landed on with its y — and none of them compares the sheet itself. v0.18.0 moved 378 leaves onto correctly oriented sheets and not one figure in corpus_test.go changed by a unit, because a leaf at the same (sheet, x, y) on a 612x792 sheet and on a 792x612 one counted as agreement. TestSheetsAgainstPdfium asks both halves. The SIZE is the weak one: 528 of the 559 forms give every page area of a form the same medium, so a sheet opened on the wrong page area is still the right size. Over the corpus the size agrees on 3 035 of 3 035 sheets, the largest disagreement in either dimension being 0.0000 pt — and v0.18.0's reported residue of "7 sheets whose size disagrees" was 7 FORMS tying at the fourth decimal, 1008.0 against 1008.0001, not a disagreement about paper at all. Which PAGE AREA a sheet was opened on has no such blind spot, and it finds ten sheets on three forms: us-uscis__g-1055 6, i-956g 3, i-956 1 — the three forms #21 and #23 left open, counted for the first time. placer.sheetAreas records it here and the probe reads it from GetPage(i)'s form node there, so it is known even on the 91 sheets of g-1055 whose page area draws no furniture. Neither side reads it off the geometry being judged. THE LINE COUNT. doc.go's "same line count as pdfium 25.9% / 82.5%" was not a line count: it was (our height less pdfium's) over OUR line height, bucketed. The proxy flatters, because a leaf that disagrees on the number of lines AND on how tall a line is can come to the same height. pdfium's probe now reports its own line count per leaf, and the answer is 23.2% and 76.3%. 1 002 leaves the proxy scored as agreeing take a different number of lines — fw9's Page4.Col2.F among them, four lines of 9 pt here against pdfium's three of 12. Where the counts do agree the whole residue is the FIRST line: 5 372 of 5 373. Our height for every line after the first is pdfium's to a hundredth of a point on 1 177 of 1 178. pdfium makes the first line the font size; pdf.js makes it the face's line height less its gap, and this follows pdf.js — so that one is a disagreement with pdfium and not a defect against the reference. Nothing in the measurement is changed for it. The layout does not move: all 560 forms are byte-identical before and after. 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.
Closes nothing; it is one slice of #7.
pdf.js refuses to split a container at
layout === "position"before itreads
keep.intact, so<keep intact="none"/>— the author's explicit "you maybreak 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.
The rule is narrower than #7's diagnosis said, and that is the first finding
FindSplitPoscallsFindLayoutItemSplitPos(
cxfa_contentlayoutprocessor.cpp:503-588), which switches onCXFA_Node::GetIntact():and
GetIntact(cxfa_node.cpp:1536-1591) answers ContentArea for a subformwhose layout is position or row — so the proposed cut is driven to nought,
FindSplitPosreturns nought, andInsertFlowedItem'sif (fSplitPos > kXFALayoutPrecision)is false: the container moves whole —unless a
<keep>is written, whereGetIntactFromKeepreturns what theauthor wrote.
So pdfium does not cut positioned containers in general. It cuts one exactly
where
<keep intact="none"/>is written on it. #7's previous comment read theexemption off
bCalculateMargin = eLayout != Position, which governs the marginarithmetic and not the gate.
The population is 58 positioned subforms across 17 of the corpus's 560 forms
— not the 39 whose off-paper leaves the position clause dominates. Recorded
before measuring anything, so the measurement is not read against the wrong
expectation.
The mechanism
pdfium's split is a post-hoc slice of an already-laid-out item tree, so
cuttableis a different question fromsplittableand answered by a differentfunction. 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'spChildItem->s_pos_.y -= fSplitPos. Letting the flow splitit would have placed every child by the cursor instead, which is the design
constraint #7 named.
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
Boxis onerectangle for one occurrence and
Layoutpromises every body leaf appearsexactly once, so the cut is refused there and the container moves whole,
which is also what pdfium does when
FindSplitPoscomes back at nought.Measured, against pdfium over 559 forms
Exactly two forms move and no form is worse on either measure:
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 — that is what the bisect refusal buys.
i-956's page-area sequence goes fromPage2 ×2, Page3 ×10toPage2 ×13, Page3— pdfium's shape isPage2 ×16, Page3— which repairs theregression #20 recorded rather than tuned away. Sheets: corpus 3086 → 3088,
i-95612 → 14 (pdfium 17). Off the paper: 1423 → 1378 boxes across 39 → 37forms.
The cut positions are pdfium's, to four decimals
An instrumented build over all 560 forms attempts the cut five times on four
containers, all on
i-956andi-956g.i-956'ssfPart2:pdfium's own trace:
splitPos=162.1843, then581.9981from 162.1843, and threeitems of 162.1843 + 581.9981 + 222.0010. Independent arithmetic, the same two
numbers, the same partition of the 25 children. The second is
sfP2_Line13'swritten y, reached because
GetIntact()answers ContentArea for a positionedsubform with no keep.
What it does not do, and what it costs
i-956'ssfPart7_2andi-956g'ssfCertification. Both still move whole.never exercised on them. The rule is real, correct and rare.
42.1%. The label was a correlation over whole forms; the reason it could not be
the prediction is above, and was written down before the measurement.
corpus writes neither, so nothing would say whether it were right.
Gate: 100% statement coverage including every error branch,
go vet,gofmt -lempty against Go 1.27.1 via
GOTOOLCHAIN,-race, thirteen cross-compiletargets,
CGO_ENABLED=0,GOWORK=off.🤖 Generated with Claude Code