Skip to content

Break a container's children onto lines, and place 4 361 more fields - #14

Merged
tannevaled merged 3 commits into
mainfrom
slice7-lr-tb-line-breaking
Sep 4, 2026
Merged

Break a container's children onto lines, and place 4 361 more fields#14
tannevaled merged 3 commits into
mainfrom
slice7-lr-tb-line-breaking

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

The number, and the disagreement first

79 851 of the 81 750 body fields, on 3 040 sheets. v0.9.0 placed 75 490 on
2 864. Nothing that was on the paper came off it.

Two forms now come out on MORE sheets than pdf.js, and none ever has before.
us-uscis__i-956 is on 14 against pdf.js's 12 and us-uscis__i-956g on 16
against 15. Both were far SHORT before — 3 against 12 and 1 against 15 — so
this is a wall coming out from behind another one rather than a regression, but
it is an overshoot and the judge counts it as a defect. Traced to the
container: form1.P1.sfPart4 is an lr-tb whose lines begin at 300 points down
our second sheet and would have to begin at 252 or less for pdf.js's answer, so
something ABOVE it is 48 points taller here. Not diagnosed to the line, and I
will not name a cause without a witness.

The bucket was not what the label said

Classified before writing anything, as slice 6 was. Of the 4 303 fields whose
first blocker was lr-tb:

inside an lr-tb container 959
not inside one — blocked because a stack could not get past an lr-tb SIBLING's height 3 344

rl-tb and rl-row do not appear in the corpus at all — zero occurrences
in 560 templates — so the half of this change that fills a line from the right
moves no field and nothing measures whether it is right. It is written because
the two are one arithmetic in pdf.js and differ only in a CSS class.

790 wrapping containers exist in the expanded forms, holding 1 027 body fields
and 2 751 draws. Only 49 had ever been reached: a stack rejects everything after
the first child whose height it cannot compute, so every later wrapping sibling
was behind the first one.

What it does

addHTML (layout.js:107-129) and checkDimensions (layout.js:279-338) read
together. The two-attempt protocol between them IS the line breaking: attempt 0
tries the line in hand, a refusal raises it to 1, which opens a fresh line,
resets numberInLine and drops straight back to line mode. The height
arithmetic differs between the branches and that difference is the whole of it.

Packing needs a quantity this package has never computed: how wide a node
comes out
. widthOf is the mirror of heightOf, memoised per node and per
width for the same reason, and contentWidth is addHTML's width column for
each of the five layouts. 2 239 of the 2 374 children of a wrapping container
write a width outright; the 135 that do not are all subforms and all on one
form, which is why the container arithmetic had to be real rather than refused.

Clause 4 of $isSplittable is live. pdf.js splits an lr-tb —
"lr-tb".includes("row") is false (template.js:4952-4955) — so clause 2 admits
it, which puts a wrapping container in the flowing chain, which is the only
place numberInLine exists. Over the corpus the clause is asked 440 times and
refuses 4, all on us-uscis__g-1055.

What it does not do, and says so

  • The break is decided on width alone. checkDimensions would also move a
    child too tall for the room left onto the next line; the room left is not a
    quantity a measurement that runs before the layout carries. Same limit
    contentHeight has for tb.
  • A container is split at a line boundary and never inside one, and a child
    that shares a line moves whole even where clause 4 would allow it: splitting
    it would leave the rest of its line to be placed on a sheet the first half of
    it is not on. 137 splittable children of a line in the corpus, 136 of them
    alone on it.
  • A child wider than a whole line is refused rather than guessed, because
    pdf.js's answer there turns on $isThereMoreWidth — whether a container
    above is mid-line — which is layout state a measurement does not have. It
    fires nowhere on the corpus.
  • turnTo moves a whole line where pdf.js moves a child. pdf.js accepts a
    child of unwritten height on "more than two points remain" and takes the
    acceptance back when the child's own contents fail; there is no failure to
    take back here. Measured: taking pdf.js's literal test costs 33 fields of
    us-uscis__i-956h and gains none, and i-956h is one of the 77 forms pdf.js
    cannot lay out, so no judge can say which is right there.

The reconciliation, field by field

Every field's disposition dumped from origin/main and from this branch and
paired on (form, path, occurrence) — 81 750 of them, same multiset of keys:

under lr-tb, in a tb stack → placed 3 795
under lr-tb, in a table stack → placed 226
inside an lr-tb, after the first child → placed 282
no room inside a container that moves whole → placed 58
placed → unplaced 0
net +4 361

75 490 + 4 361 = 79 851. Second slice running with no loss. 34 fields
placed both times are on another sheet.

The judges, and the pairing re-audited

v0.9.0 here
container heights paired that this package computes 7 764 7 794
agree to within 1/100 pt 100.00% 100.00%
heights it cannot measure 33 0
forms where it placed every element 473 476
of those, agreeing on the number of sheets 469 472
boxes paired on those forms 149 433 150 792
on the same sheet as pdf.js 100.00% 100.00%
forms placing fewer elements and using MORE sheets 0 2

The pairing, since line breaking changes which boxes exist and in what
order.
Of 162 163 body boxes on the 472 agreeing forms: 150 792 paired,
6 781 unnamed and so never keyed, 4 590 keyed but absent from pdf.js's dump
(1 900 of them presence="hidden"), and 0 dropped because the two sides
counted a key differently
. The same audit on origin/main gives 4 522 absent
of 1 872 hidden and 0 dropped, so the absent are pre-existing in the same
proportion and not this change's.

What the judge cannot see here, and it is most of the chain half. pdf.js
emits no coordinate inside a line at all — createLine wraps the children in a
flexbox div — so where along a line a box went is unjudged in both directions,
exactly as stacking was for tb. And the 212 field-sheet changes the chain half
makes fall on us-uscis__g-1055 (6), us-uscis__i-956h (199) and
us-uscis__i-956 (7): the first two are among the 77 forms pdf.js cannot lay
out at all and the third is not fully placed, so no judge sees any of them.
Splitting a wrapping container across a sheet is checked by its own tests and
by nothing external.

Four sheet-count disagreements remain and are the same four as v0.9.0:
us-opm__sf813 and us-opm__sf39a (a positioned outermost subform, whose fit
check has never been done) and us-uscis__i-600a and us-uscis__i-821 (one
sheet behind after a breakBefore, undiagnosed).

What is left

past the last sheet the page set gives 1 887
anchored by a corner, with no size of their own 9
no room inside a container that moves in one piece 3

Gate: 100% statement coverage, go vet, gofmt -l empty, -race, nine
cross-compile targets, CGO_ENABLED=0, GOWORK=off, Go 1.27.1 via
GOTOOLCHAIN.

🤖 Generated with Claude Code

tannevaled and others added 3 commits September 4, 2026 13:50
…a node is

pdf.js's lr-tb and rl-tb wrap their children onto lines, and the rule is two
functions read together: addHTML says where a child goes once it is accepted
(layout.js:107-129) and checkDimensions says whether it is accepted at all
(layout.js:279-338). The two-attempt protocol between them IS the line
breaking: attempt 0 tries the line in hand, a refusal raises it to 1, which
opens a fresh line, resets numberInLine and drops straight back to line mode.

Packing needs a quantity this package has never computed: how wide a node comes
out. widthOf is the mirror of heightOf, memoised per node and per width for the
same reason, and contentWidth is addHTML's width column for each of the five
layouts.

The break is decided on width alone. checkDimensions would also move a child
too tall for the room left onto the next line; the room left is not carried by
a measurement that runs before the layout, which is the same limit
contentHeight has for tb.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…se 4 live

pdf.js splits an lr-tb: $isSplittable refuses "position" and anything
containing "row" (template.js:4952-4955), and "lr-tb".includes("row") is false.
Clause 2 says so now, which puts a wrapping container in the flowing chain, and
that is what the fourth clause has been waiting for — it reads the parent's
numberInLine, which only a container filling a line has. It is asked 440 times
over the corpus and refuses 4, all on us-uscis__g-1055.

A LINE is what moves to the next content area, never part of one, because every
child of a line pdf.js has flushed is already in that line's div. The check is
stricter than pdf.js's literal one and turnTo says why: pdf.js accepts a child
of unwritten height on "more than two points remain" and takes it back when the
child's own contents fail, and there is no failure to take back here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… doc

Every branch of the packing, of widthOf and contentWidth, and of flowLines is
witnessed: the two-attempt protocol child by child, a child re-measured against
a whole line after the tail of one refused it, rl-tb anchored at the right, a
container split between two of its lines, and the fourth clause of
$isSplittable asked of the same subform first on a line and second on one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit fe5f70e into main Sep 4, 2026
1 check passed
@tannevaled
tannevaled deleted the slice7-lr-tb-line-breaking branch September 4, 2026 12:14
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