fix(layout): give an anchor that spans pages the slice on each of them - #672
Merged
Merged
Conversation
A resolved anchor reported the anchored subtree's whole height on every page it crossed. A section spanning five pages said 453.25pt five times, with tops hundreds of points outside the page, so nothing could use an anchor on anything taller than a page. That was written down as a limitation rather than fixed because no consumer needed the per-page extent; one does now, and the numbers were unusable rather than merely imprecise. The placement was already the answer. A composite's fragments are emitted once per page it occupies, and CompositeDecoration clamps each segment to that page's content band — the geometry that puts a spanning section's border on every page it crosses, per-page margins included. The definition was throwing that away and reading the measurement instead. Reading the placement is also what keeps this one formula instead of a second one drifting beside it. The margin comes off per edge, which the slice makes necessary. It is part of the flow extent the wrapper occupies but no part of the box reported, and a slice only meets the edges it contains: the top margin is inside the first page's slice, the bottom margin inside the last page's, a middle page holds neither, and a node that fits on one page is both so it loses both. Horizontal margins come off every slice, as before. Anchors on content that fits on one page are unchanged, which is every anchor that exists today — the atomic marker, the marker with an asymmetric margin, the marker in a table cell all report the same numbers to 1e-9, and no snapshot or pixel baseline moved. Measured against the thing it has to agree with: an anchored section and an accentLeft section of the same shape, across five pages, now differ by dy=0.000000 dh=0.000000 on every one.
DemchaAV
force-pushed
the
fix/resolved-anchor-page-slices
branch
from
September 9, 2026 19:57
dd1e61d to
362ebfb
Compare
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.
Problem
A
LayoutAnchorNodewrapping content taller than a page emitted one resolved anchor perpage it occupied — and every one of them repeated the full measured subtree height.
A section spanning five pages, on a 320×150 page with a 20pt margin (content band 20..130):
Every top is hundreds of points off the page. Nothing could use an anchor on content taller
than one page: not "where does this start on page 2", not "how tall is it here". The
per-page occurrences were there, but each carried a number belonging to no page.
This was written down as a limitation rather than fixed, on the grounds that no consumer
needed the per-page extent yet. One does now, and the numbers turned out to be unusable
rather than merely imprecise.
Fix
Each occurrence reports the wrapped node's actual border-box slice on that page, taken
from the
FragmentPlacementthe compiler had already computed for it.The placement was always the answer. A composite's fragments are emitted once per page it
occupies, and
CompositeDecorationclamps each segment to that page's content band — thesame geometry that puts a spanning section's border on every page it crosses, per-page
margins included.
LayoutAnchorDefinitionwas discarding it and readingprepared.measureResult()instead. Reading the placement is also what keeps this oneformula rather than a second one drifting beside it.
Same case, after:
Checked against the thing it has to agree with
An anchored section and an
accentLeftsection of the same shape, laid out across the samefive pages — the border fragment is the independent implementation of "this box, clamped to
this page", so the anchor slices have to match it:
Page 4 includes the section's own 14pt bottom padding inside the border, and the anchor
reports it too.
Margins come off per edge
A margin is part of the flow extent the wrapper has to occupy but no part of the box being
reported, and a slice only meets the edges it actually contains:
before;
Unchanged
The atomic 8×8 marker, the marker carrying an asymmetric margin, and the marker inside a
table cell all report the same numbers to 1e-9.
its container's.
LayoutAnchorIdacross every slice — samegroupKey,kindandindex.extract-api --checkis currentwithout regenerating.
Verification
./mvnw -B -ntp clean verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose-qa,:graph-compose-coverage -am→ BUILD SUCCESS.
javadoc:javadocgreen; the knowledge gates green (surfaces current,stability doc, claims, routes, bundle verify); the backends' no-op handler tests green.
ResolvedLayoutPassTestgains four cases, each written against a measurement rather thanagainst the implementation:
→ node bottom, none of them the whole subtree;
them;
document default.
Proven fail-closed by putting
measureResult()back: those four go red and the identitycase stays green, which is right — identity was never the broken part.
Lane: shared-engine —
document.layoutonly. No Timeline file is touched; the worddoes not appear in the diff.