Skip to content

layout: block and inline formatting, CSS 2.1 §8.3.1, §9.4, §10 and §16 to a fragment tree - #154

Merged
atiernan merged 5 commits into
mainfrom
claude/flow-layout-050856
Nov 13, 2026
Merged

atiernan merged 5 commits into
mainfrom
claude/flow-layout-050856

Conversation

@atiernan

Copy link
Copy Markdown
Member

What

Item 5.14 of docs/phases/phase-5.md: new module crates/clazel-layout/src/flow/. Branch claude/flow-layout-050856, tip c35d902, on main at 6e581e9.

flow::layout(tree, arena, styles, fonts, viewport) takes item 5.13's box tree, the DOM arena (for the document's mode and replaced elements' attributes), the computed style (StyleFns), the fonts and the viewport. It returns a FragmentTree keyed by box id:

  • each box laid out as a box: its border box in document coordinates, its used margins, borders and padding, and its baselines;
  • every line box, with its inline box pieces (start and end edges marked), positioned glyph runs and atomic inlines;
  • the node each box came from (for hit testing).

Lengths are 1/64 px in i32 with saturating arithmetic. The passes:

  1. style.rs: each box's style. Anonymous boxes inherit; §17.4 splits a table's properties between its wrapper and table boxes.
  2. inline.rs, preparation: each inline formatting context's units (5.12's Phase I output), wrap opportunities, and each text run shaped once.
  3. intrinsic.rs: min-content and max-content widths, leaves first, for §10.3.5's shrink-to-fit.
  4. block.rs: one walk with an explicit stack of tasks and a stack of block-container frames:
    • widths top down (widths.rs: §10.3.3's five rules, §10.3.4, §10.3.9, §10.4, HTML's "align descendants"; replaced.rs: §10.3.2, §10.6.2);
    • the block flow with §8.3.1 margin collapsing, negative margins included (margins.rs): each container keeps a set of pending adjoining margins, and a resolution walk closes "open" containers from the outermost in when content is placed;
    • line boxes at each inline context's end (inline.rs): 5.12's fit_step with each line's width, Phase II, §16.1 text-indent, §16.2 text-align, and §10.8's line heights with the strut, half-leading and every vertical-align value;
    • heights bottom up (heights.rs: §10.6.3, §10.6.7, §10.7);
    • the Quirks Mode Standard's §3.3 to §3.7;
    • then every position made absolute.

state.rs states each state, its invariant as a function, and each step's contract. It was committed first, on its own (78718e9), before the implementation. Nothing recurses: a tree 100,000 blocks deep lays out in 0.04 s (release).

Fonts are a parameter, flow::TextFns, as computed style is for the box tree:

  • flow::FaceFonts is the real one, over clazel-font faces with 5.10's matching and 5.11's shaper;
  • flow::BoxFont gives every glyph an em square with Ahem's metrics, so tests can be worked out by hand.

Tables have a placeholder until 5.15. Wrapper and table boxes are laid out as blocks (the table's margins and width on the wrapper, shrink-to-fit when auto). Row groups, rows, cells and captions are blocks stacked in their parent's flow; cells and captions are block formatting context roots. Columns are empty boxes. Every box in a table is laid out and its text readable. border-spacing, column widths, cell vertical-align and colspan have no effect yet.

No new dependency of any class. No unsafe, no lint override, no tier change.

Decisions taken from the plan (maintainer away)

These are recorded in crates/clazel-layout/README.md ("Decisions taken for 5.14") and at the top of each file.

  • Fonts as a parameter (above).
  • A and D (§10.8.1) come from OS/2's typo metrics when USE_TYPO_METRICS is set, else from hhea, as engines read them.
    • CSS 2.1's non-normative note recommends the typo metrics always; this is listed as a deviation.
    • line-height: normal is A + D + the line gap.
    • An inline box's A and D are its first available font's.
    • The x-height is sxHeight, or half an em.
    • sub lowers by 1/5 of the parent's font size and super raises by 1/3.
    • The leading's odd 1/64 px goes below.
  • Table placeholder (above).
  • Markers are a line of the list item's font, ending at its content edge, on its first baseline.
  • Inline box edges go before the box's first unit and after its last.
    • An empty inline box takes no width or height. CSS 2.1 §10.8 says such boxes still count; listed.
    • An edge next to a unit that Phase II removes is dropped with it.
  • Characters that draw nothing: default ignorables (U+00AD, U+200B to U+200D, U+2060, U+FEFF), forced breaks and tabs draw no glyph. A tab's advance is CSS Text 3 §4.2's.
  • Each text run is shaped once, so kerning runs across a line break.
  • A negative auto width is 0; the box is then over-constrained and margin-right follows.
  • "Align descendants" (center, div align, table parts' align):
    • applies to block-level boxes whose margins are both non-auto and that rule 2 over-constrained, using the nearest aligning ancestor;
    • "set accordingly" is read as auto margins would place the box;
    • table-internal boxes are never aligned.
  • text-indent:
    • a percentage refers to the block's own content width;
    • each-line and hanging are applied, and so is §16.1's rule for anonymous blocks;
    • justify is drawn as left, and a line too wide for its box is start-aligned (CSS Text 3 §7.1†).
  • Quirks:
    • §3.3 and §3.4 apply in quirks and limited-quirks mode; §3.5 to §3.7 in quirks mode only.
    • §3.3 is read per line, and a box it zeroes is left out of the line height.
    • Without §3.4's strut, the root inline box counts only on lines where it holds text of its own.
    • §3.5's UA-defined value for table cells is none.
    • For §3.7, body's parent content box is the html box as §3.6 makes it at least.
  • Replaced natural sizes:
    • img is 0×0; iframe has none, so 300×150;
    • text input and textarea use HTML §15.5.6's character-width conversion (the average width is the advance of 0) and the line height;
    • buttons take the width of their value (or "Submit" / "Reset");
    • checkboxes are 13px square.
    • Their baseline is the bottom margin edge (CSS 2.1); engines use a text control's text instead. Listed.
  • Block formatting context roots: the root element, inline blocks, cells, captions, the placeholder's table boxes, and blocks whose overflow is not visible unless it propagates to the viewport.
  • ERRATA: CSS 2.1 §8.3.1's definition of adjoining margins and its bullets disagree (min-height for the parent-bottom pair; "no in-flow children" against "all of its in-flow children's margins collapse"). The bullets are followed. "Reported upstream: not yet."
  • A consequence to check against 5.25's reference engine. §8.3.1 reads the computed height, so in quirks mode a last child's bottom margin collapses through a body that §3.7 stretched. The result: <p>One<p>Two gives a 608px html box in a 600px viewport.
  • Section numbers: CSS 2.1's † numbers were checked against the Recommendation, and the Quirks Mode Standard's against the pinned commit 5a06deb5. CSS Text 3 §7.1 keeps its †.
  • Target tier stays 0, per the Crates table. The Kani harnesses were added as the brief asked.
  • WPT reftests (css/CSS2/normal-flow, margin-padding-clear, box-display, linebox, visudet, visuren) need 5.25's harness. They are not vendored or run here.

Evidence

  • Unit tests (src/flow/tests.rs, 36). Each uses the real cascade with a display-only UA sheet and BoxFont, with every expected position worked out by hand in a comment. Covered:

    • each §8.3.1 adjoining pair and result: positive, negative and mixed margins, HN's margin-bottom:-10px, collapse-through and its position, BFC roots, inline blocks, the root element;
    • §10.3.3's five rules and the over-constrained cases; §10.4; align descendants;
    • shrink-to-fit and an inline block's baseline; replaced sizes;
    • §10.5 with the §3.5 quirk; §10.6 and §10.7; §3.6 and §3.7;
    • §10.8.1's 1.2 / 1.2em / 120% example; the half-leading; the strut and §3.4; §3.3 (a 1px spacer image gives a 1px line in quirks mode and a 10px one in no-quirks mode); every vertical-align value;
    • empty lines, wrapping and br, text-align and text-indent, inline edges;
    • markers, the table placeholder, and a tree 100,000 blocks deep.
  • Golden fragment dumps (tests/golden_flow.rs, tests/golden/flow/):

    • The inputs are the 1,761 html5lib documents 5.13's golden test builds, plus two pages of our own: an HN-shaped table page in quirks mode, and a page of margins, lists and lines.
    • Each is cascaded under the real UA sheets at 800×600 and laid out in the vendored DejaVu Serif and Source Serif 4 (Linux's generic families, so the result is the same on every OS).
    • The result is 8,108 fragments, 1,086 line boxes and 1,174 glyph runs, compared byte for byte; CLAZEL_BLESS=1 regenerates them.
    • Both pages were checked by hand against the rules. That check caught and fixed one bug: alignment had been applied to table cells.
    • 2.6 s natively.
  • Kani (src/flow/proofs.rs). Each harness ran alone, in its own process group, under the lock, capped at 20 min and 8 GB RSS (polled every 5 s), on an Apple-silicon Mac. Times include cargo's start.

    Harness Kind Time Peak
    collapsed_any (the margin set against §8.3.1's rule written a second time) step, every size 15 s 0.70 GB
    enter_any step, every size 5 s 0.27 GB
    place_any step, every size 5 s 0.27 GB
    close_any step, every size 6 s 0.26 GB
    exit_any step, every size 5 s 0.27 GB
    finish_any step, every size 5 s 0.27 GB
    line_extent_any step, every size 5 s 0.27 GB
    solve_block_any (no panic, width ≥ 0) any input 5 s 0.28 GB
    solve_block_sum (§10.3.3's equality and rules) inputs within 2^22 px 20 s 0.28 GB
    align_any inputs within 2^22 px 5 s 0.27 GB
    width_min_max_any any input 5 s 0.27 GB
    shrink_to_fit_any any input 5 s 0.27 GB
    heights_any any input 5 s 0.27 GB
    half_leading_any any input (sum within 2^22 px) 10 s 0.27 GB
    align_offset_any any input 6 s 0.27 GB
    role_any any class 5 s 0.27 GB
    halves_any any value 5 s 0.26 GB
    times_any any value 5 s 0.27 GB
    • Proved for every size: the margin set (of, adjoin, join, resolve), each flow step (enter, place, close, exit, finish with the BlockFacts predicates), and LineExtent::add.
    • Proved for every input: solve_block (panic-free, width ≥ 0), widths::min_max, shrink_to_fit, the height functions, half_leading (panic-free), align_offset, role, and the length arithmetic.
    • Bounded: §10.3.3's equality, align descendants' sum, and §10.8.1's sum hold on values within 2^22 px; beyond that they saturate.
    • No harness hit a cap; none was removed.
    • Not covered by Kani: the drivers (the walk and its resolution walk, line building and vertical alignment, the intrinsic pass) and reading style and fonts. The unit and golden tests run them.
  • Miri. Every test this item adds was run alone, one test per process, capped at 10 min, under the lock and never alongside Kani. All 37 passed. Times are on Miri's clock (--report-time):

    Test Miri time
    golden_fragment_trees (case 0 only) 109.4 s
    vertical_align 40.9 s
    text_align 38.8 s
    last_child_and_parent_bottom 27.9 s
    inline_blocks_shrink_to_fit 23.0 s
    margins_collapse_through_an_empty_box 22.2 s
    deep_trees_lay_out_without_recursion (300 levels under Miri) 8.7 s
    the other 30 under 21 s each

Not done

  • Table layout (5.15); paint and overflow clipping (5.16); the WPT reftests (5.25).
  • Floats, positioning and clearance (not in the subset); bidi; justification; letter-spacing and word-spacing.
  • Not bounded: finding a line's inline boxes costs about nesting depth × lines on deeply nested paragraphs. 5.26's hostile pages will measure it.

Verification status

  • cargo xtask verify --changed was not run locally, as instructed; nor was scripts/verify.sh.
  • What was run:
    • cargo fmt --all -- --check;
    • cargo clippy --workspace --all-targets --locked -- -D warnings;
    • cargo test -p clazel-layout (all pass);
    • cargo xtask check, whose last line is RESULT: STATIC CHECKS PASSED (no build, tests or proofs run; use cargo xtask verify);
    • the Kani harnesses and Miri tests above.
  • CI's full verification has not run. Its proof jobs need a full-verification approval.
  • The Status table, roadmap line, docs/STATUS.md and its SVGs, SPEC-REVIEWS.md, SYS-AUDITS.md, CLAUDE.md and clazel-sys are untouched.

🤖 Generated with Claude Code

atiernan and others added 5 commits November 12, 2026 19:32
The output types (a fragment tree keyed by box id with line boxes and
glyph runs), the states of block and inline formatting (a set of adjoining
margins, a container's block flow, the width equation's input and output,
a line box's extent, each box's role in the walk), their invariants, and
the contract of every step over them, before the implementation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CSS 2.1 §8.3.1 collapsing margins (negative ones included) as a pending
set of adjoining margins in each block container's flow; §9.4.1 block
formatting contexts; §9.4.2 inline formatting contexts and line boxes over
item 5.12's units, opportunities and first-fit step, with item 5.11's
shaping through a font parameter; §10.3.2 to §10.3.4 and §10.3.9 widths
with §10.4, §10.3.5 shrink-to-fit from min-content and max-content sizes;
§10.6.2, §10.6.3, §10.6.7 heights with §10.7; §10.8 line heights, the
strut and vertical-align; §16.1 text-indent and §16.2 text-align; HTML's
align descendants; the Quirks Mode Standard's §3.3 to §3.7. Tables have a
stated block placeholder until item 5.15. One walk with an explicit stack;
lengths in 1/64 px with saturating arithmetic; a fragment tree keyed by box
id. ERRATA: CSS 2.1 §8.3.1's definition and bullets disagree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit tests (36) with a font whose glyphs are em squares, so each expected
position is worked out by hand: each of §8.3.1's adjoining pairs and
results (negative margins, Hacker News' -10px), §10.3.3's five rules and
the over-constrained cases, §10.4, align descendants, shrink-to-fit,
replaced sizes, §10.5 and the percentage quirk, §10.6 and §10.7, §10.8.1's
example and vertical-align values, the strut and quirks §3.3, §3.4, §3.6,
§3.7, text-align and text-indent, markers, the table placeholder and a
100,000-deep tree. Golden dumps: html5lib's 1,761 documents and two pages
of our own, cascaded and laid out in the vendored TrueType faces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eighteen harnesses: the margin set (against §8.3.1's rule written a
second time), each step of a block container's flow from any state
satisfying its invariant (enter, place, close, exit, finish), a line box's
extent, §10.3.3's equality and rules, align descendants, §10.4, §10.3.5,
§10.7 and the fill quirks, §10.8.1's half-leading, §16.2's offset, the
walk's roles and the length arithmetic. All verified locally, each under
20 s and 0.7 GB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…i and Miri (5.14)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
full-verification — c35d9023 Deployed Nov 13, 2026 by atiernan via Approve the proofs #431
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