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.
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:
| Offset | Field | Description |
|---|---|---|
+0 | type | 0 = block, 1 = inline |
+8 | cw | content width (pixels) |
+16 | ch | content height (pixels) |
+24 | mt | margin-top |
+32 | mb | margin-bottom |
+40 | ml | margin-left |
+48 | mr | margin-right |
+56 | pad | padding (uniform all 4 sides) |
+64 | brd | border width (uniform all 4 sides) |
Result at layout_result_buf() (0xA90600), 16 × 32 bytes:
| Offset | Field | Description |
|---|---|---|
+0 | x | border-box left, relative to container |
+8 | y | border-box top, relative to container |
+16 | w | border-box width = cw + 2×pad + 2×brd (block: auto fills ctr_w−ml−mr) |
+24 | h | border-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
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.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_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, 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_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:
| Node | Type | Input | Expected result |
|---|---|---|---|
| 0 | block | cw=0 (auto), ch=20, ml=4, mr=4, pad=0, brd=0 | x=4, y=0, w=152, h=20 |
| 1 | inline | cw=40, ch=10, ml=0, mr=0, pad=2, brd=1 | x=0, y=20, w=46, h=14 — line 0 |
| 2 | inline | cw=40, ch=10, ml=0, mr=0, pad=2, brd=1 | x=46, y=20, w=46, h=14 — line 0 |
| 3 | inline | cw=40, ch=10, ml=0, mr=0, pad=2, brd=1 | x=92, y=20, w=46, h=14 — line 0 |
| 4 | inline | cw=40, ch=10, ml=0, mr=0, pad=2, brd=1 | x=0, y=34, w=46, h=14 — line 1 (wrap) |
| 5 | block | cw=60, ch=30, ml=8, mr=8, pad=4, brd=2 | x=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.