css: fragmentation properties, @page, and the absolute length units - #141
Merged
Merged
Conversation
…te length units CSS Fragmentation 3 properties on Style (BreakBefore/BreakAfter as Break, BreakInside, Orphans/Widows — inherited, initial 2) with the legacy page-break-* aliases; ParsePageRules/ResolvePage/DocumentPage for @page (selectors, size with the named sizes and orientation, margins, @media print honoured, nested margin boxes skipped without losing neighbours). parseLength now understands in/cm/mm/Q/pt/pc (CSS Values 4 §6.2): a `height:40mm` laid out as 0 before. @page goes through the same table. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tannevaled
added a commit
that referenced
this pull request
Sep 6, 2026
…142) A new pure-logic package: Breaks(root, pageH) / Paginate(root, Options) return the document y at which each page after the first starts. It cuts only between atoms — a text line, a whole table row (a layout-table wrapper row is descended into), a leaf box with height — the rule go-pdfkit/html2pdf paginated with, moved here so every consumer of the layout tree paginates alike; and it now reads what the document asks (css #141): forced breaks (break-before/after: page, left, right and the page-break-* aliases), break-inside: avoid kept whole when the box can fit a page, break-before/after: avoid (keep with the next or previous content), orphans and widows — relaxing orphans/widows first and the avoid rules second when a page cannot otherwise be cut, as css-break-3 §5.4 prescribes. Margins adjoining an unforced break are not truncated (documented). The answer key is Chrome: html2pdf's fixture (sections, a straddling table and figure, a heading kept with its paragraph, an eight-line paragraph with orphans/widows 3, on A5 with 15 mm margins) printed with headless Chrome gave marker → page; laid out at the A5 page area with the engine's own fonts, every marker lands on Chrome's page and the count is Chrome's nine. Synthetic trees cover each rule and each relaxation. 100 % covered and added to the coverage gate at 100. Co-authored-by: Claude Fable 5.1 <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.
Two items, both for paged media (go-pdfkit/html2pdf and the
paginatepackage consume thecsspackage through the names below, which are a contract).1. CSS Fragmentation 3 properties and
@pagecss.StylegainsBreakBefore, BreakAfter css.Break(BreakAuto,BreakAvoid,BreakPage,BreakLeft,BreakRight),BreakInside css.BreakInside(BreakInsideAuto,BreakInsideAvoid) — not inherited, initial auto — andOrphans, Widows int— inherited, initial 2. Everybreak-before/break-after/break-insidevalue parses; column/region-only values map to auto (they say nothing about pages);always/all/recto/versoare a forced page break;page-break-*are aliases.orphans/widowsaccept positive integers only (0, negatives,2.5ignored).inherit/initial/unsetper CSS Cascade.css/page.go:PageRule,ParsePageRules(src, m Media) []PageRule,PageSpec{Width, Height float64; Margin [4]float64; MarginSet [4]bool},ResolvePage(rules, selector) PageSpec,DocumentPage(root, externalSheets, m, selector) PageSpec. Selectors:first/:left/:right/:blank, named pages, selector lists;size(auto, 1–2 lengths, the ten named sizes,landscape/portraiteither order);margin+ longhands.@pageinside@media printapplies undercss.Printonly;@layerunwrapped. Nested margin boxes (@top-center { … }) are skipped and the declarations around them kept.parseRulesalready skipped@pageas an unknown at-rule;styleElementTextis factored out ofcollectAuthorRulesFromsoDocumentPagereads the same<style>text.2. Absolute length units in
parseLengthMeasured:
<div style="height:40mm">laid out with H = 0 (4cm,1.5in,100pt,10pclikewise);height:151pxgave 151.parseLengthknew only px/em/rem/%/vw/vh. Addedin,cm,mm,Q,pt,pcper CSS Values 4 §6.2 in the one function every property resolves a length through (font-size, calc() terms, line-height, flex basis —hasUnitupdated too). A unit is split off as trailing letters and the rest must be a number, sothinnever reads as a length.@pagesizes/margins use the same table.Tests
Every value of every property through both spellings; cascade inheritance (orphans/widows inherited, break-* not);
inherit/initial/unset; the unit table (40mm→ 151.18px,12pt→ 16px); a layout test thatheight:40mmis 151.18px tall; and the@pagecontract cases (A5 + 15mm → 559.37×793.70 with 56.69px margins;100mm 200mm;letter landscapeboth orders;10cmsquare;margin: 1in 2in;:firstoverriding only the top; print-only@page; nested@top-centerwith surroundingmarginstill read;DocumentPageover two<style>s plus an external sheet, external first, later wins).Coverage: css 99.5% → 99.6% (floor 99.5); layout 100%. Cross-builds for riscv64/loong64 checked locally. FIDELITY.md round 59.
🤖 Generated with Claude Code