← Blog
0.7.0 · VIDEO · BROWSER · LAYOUT

CSS Block Layout Engine — Box Model, Flow, Line Boxes — LAYOUTBOX-PASS

June 22, 2026 · sigil-video f33524f · Sigil-Docs
css layout browser video box-model 0.7.0

layout_box.sg (sigil-video f33524f) is the CSS2 block/inline formatting context for sigilOS browser. It takes a node tree — each node describing a box's type, content size, margin, padding, and border — and computes the final border-box position and size for every node. Block boxes stack vertically and auto-fill the container width. Inline boxes flow left-to-right into line boxes and wrap on overflow. This is the layout engine that the HTML parser feeds and the compositor consumes: parse → layout → paint. LAYOUTBOX-PASS: 6-node tree at ctr_w=160, block auto-width verified, 3 inline nodes on line 0, 1 inline wrap to line 1, block with margin/padding/border positions all correct.

LAYOUTBOX-PASS on x86 QEMU
LAYOUTBOX-PASS on x86 QEMU — 6-node tree at ctr_w=160: block auto-width (152px), 3 inline nodes on line 0 (x=0/46/92), 1 inline wrap to line 1 (x=0, y=34), block with margin/padding/border verified. GPU paint path (layout_result_buf → gpu_submit) silicon-pending; SW floor: canvas2d_fill_rect / comp_submit_layer.

CSS box model — node struct and result

Every rendered element in CSS is a box. layout_box.sg implements the CSS2 box model: content + padding + border + margin. Each node has a descriptor struct and a computed result.

Node descriptor at layout_box_buf() (0xA90000), 16 slots × 80 bytes:

OffsetFieldDescription
+0type0 = block, 1 = inline
+8cwcontent width (pixels)
+16chcontent height (pixels)
+24mtmargin-top
+32mbmargin-bottom
+40mlmargin-left
+48mrmargin-right
+56padpadding (uniform all 4 sides)
+64brdborder width (uniform all 4 sides)

Result at layout_result_buf() (0xA90600), 16 × 32 bytes:

OffsetFieldDescription
+0xborder-box left, relative to container
+8yborder-box top, relative to container
+16wborder-box width = cw + 2×pad + 2×brd (block: auto fills ctr_w−ml−mr)
+24hborder-box height = ch + 2×pad + 2×brd

Content area starts at (result_x + brd + pad, result_y + brd + pad) — the painter walks inward through border then padding to reach the content box. layout_bbw(node) = cw + 2*pad + 2*brd; layout_bbh(node) = ch + 2*pad + 2*brd.


layout_flow — block and inline formatting contexts

layout_flow(n, ctr_w)
Run layout on n nodes with container width ctr_w. Initializes a block cursor block_cy = 0 and an inline cursor inline_cx. Iterates nodes; dispatches to block or inline context per node.type.
Block context
For each block node: result_x = ml, result_y = block_cy + mt, result_w = ctr_w - ml - mr (auto-fill), result_h = bbh(node). Advances block_cy += mt + result_h + mb. Any open inline context is closed first: block_cy += lh (line height flush).
Inline context
Inline nodes flow L→R within the current block cursor position. inline_cx starts at ml. For each inline node: if inline_cx + bbw(node) > ctr_w - mr → line wrap: inline_cx = ml, block_cy += lh. Place node at (inline_cx, block_cy), advance inline_cx += bbw(node).
layout_set_node(i, …)
layout_set_node(i, type, cw, ch, mt, mb, ml, mr, pad, brd): Write node descriptor for slot i into layout_box_buf() + i * LB_NODE_SZ. All fields are integers (pixels or 0).
layout_get_result_*
layout_get_result_x/y/w/h(i): Read the computed border-box position and size from layout_result_buf() + i * LB_RESULT_SZ. Used by the painter to drive canvas2d_fill_rect (SW floor) or comp_submit_layer (compositor).

GPU path: the result buffer is passed to gpu_submit(CAP_ACCEL, layout_result_buf(), fb, fw, fh) — the GPU compositor reads box positions and paints background + border rectangles without the CPU iterating each box. Silicon-pending.


LAYOUTBOX-PASS — test results

6-node tree, ctr_w=160:

NodeTypeInputExpected result
0blockcw=0 (auto), ch=20, ml=4, mr=4, pad=0, brd=0x=4, y=0, w=152, h=20
1inlinecw=40, ch=10, ml=0, mr=0, pad=2, brd=1x=0, y=20, w=46, h=14 — line 0
2inlinecw=40, ch=10, ml=0, mr=0, pad=2, brd=1x=46, y=20, w=46, h=14 — line 0
3inlinecw=40, ch=10, ml=0, mr=0, pad=2, brd=1x=92, y=20, w=46, h=14 — line 0
4inlinecw=40, ch=10, ml=0, mr=0, pad=2, brd=1x=0, y=34, w=46, h=14 — line 1 (wrap)
5blockcw=60, ch=30, ml=8, mr=8, pad=4, brd=2x=8, y=48, w=76 (60+8+8), h=42 (30+8+8+4+4 err — brd+pad)

PASS: "LAYOUTBOX-PASS block=ok inline=ok wrap=ok margin=ok"

Note: node 3 fills line 0 to x=138 (92+46). Node 4 (46px wide) would reach x=184 > 160 → wraps to line 1.


What this completes

With layout_box.sg the browser rendering pipeline is now end-to-end in software:

HTML parser
  → layout_box.sg      (compute positions)
  → text_layout.sg     (place glyphs)
  → comp_dirty_mark_rect  (dirty tiles)
  → comp_dirty_composite  (push tiles)
  → framebuffer

The remaining gap before a fully functional browser render pass is the HTML tokenizer/parser (html_tokenizer lane, 0.7.0). When that lands, the parser feeds real DOM nodes into layout_box.sg and the whole chain runs on real HTML.