text_layout.sg (sigil-video 6158863) closes the browser text rendering loop started with font_raster.sg (8f6bda0). The glyph rasterizer gave sigilOS pixels per character; the layout engine tells those characters where to go. text_layout.sg takes a stream of HTML text tokens and turns them into positioned glyphs on the framebuffer — left-to-right cursor advance, automatic line-wrap when the cursor reaches the block width, forced newlines on \n, and a GPU quad buffer for batching the full block upload once silicon is available. TEXTLAYOUT-PASS: 19 chars per line at max_w=152; char 19 wraps to y=12; forced newline; 21 quads emitted.
Layout model
text_layout.sg uses a left-to-right, top-to-bottom cursor model matching the CSS inline layout spec:
- Cell:
FONT_W × FONT_Hpixels (8×8). Each glyph occupies exactly one cell. - Cursor (cx, cy): top-left of the next glyph. Initialized at
(x0, y0)bytext_layout_init. - Line-wrap: when
cx + FONT_W > x0 + max_w, the cursor resets to(x0, cy + FONT_H)before placing the glyph — the glyph that triggered the wrap lands at the start of the new line. - Forced newline: character
\n(ch=10) →cx = x0,cy += FONT_H, no glyph placed. - Baseline:
cyis the top pixel row. Proportional baseline offset (for descenders) is a 0.8.0 extension — the 8×8 floor treats top = baseline. - Line height:
FONT_H(8 px). Multi-line spacing added in 0.8.0 when the vector font path lands.
Layout state lives at text_layout_buf() (0xA80000) — 80-byte block: fb / pitch / fw / fh / x0 / y0 / max_w / color / cx / cy / n_quads.
API
text_layout_init(fb, pitch, fw, fh, x0, y0, max_w, color) — Initialize layout state. Sets framebuffer base, stride (pitch), canvas dimensions, left margin x0, top y0, wrap width max_w, and color (BGRA32). Resets cursor to (x0, y0), clears quad counter. Call once per text block before put_char/put_string.text_layout_put_char(ch) — Place one glyph. Check line-wrap condition first (cx + FONT_W > x0 + max_w → wrap). If ch = 10 (\n), force newline without placing a glyph. Otherwise: SW floor calls font_raster_glyph(ch, fb, pitch, cx, cy, color); GPU path appends a quad to text_quad_buf() ({x:cx, y:cy, ch:ch, color:color}, 16B per quad); advances cx += FONT_W. Quads accumulate until text_layout_flush_gpu().text_layout_put_string(str, n) — Call text_layout_put_char for each of the n bytes in str. Handles all wrap and newline logic per-character. No UTF-8 yet — Latin-1 byte values only (ASCII 32–90 from font_raster scope; others render blank).text_layout_flush_gpu() — GPU batch submission. gpu_submit(CAP_ACCEL, text_quad_buf(), fb, fw, fh) — routes all accumulated quads to the GPU glyph rasterizer + texture cache (silicon-pending). Resets n_quads = 0. SW floor: no-op (glyphs already written to fb by put_char). Called once per frame after the full text block is laid out.Quad buffer: text_quad_buf() at 0xA90000 (after text_layout_buf() at 0xA80000 + 80B). Capacity: 256 quads × 16B = 4096B. Enough for a typical browser text block; larger blocks are split across multiple flush_gpu calls.
How it fits in the browser pipeline
The text rendering chain is now complete:
HTML parser → CSS layout (box model, flow)
→ text_layout_init per text box
→ text_layout_put_string
→ comp_dirty_mark_rect (marks affected tiles)
→ comp_dirty_composite (pushes dirty tiles)
→ GPU compositor
→ framebuffer
On the GPU path (silicon): text_layout_flush_gpu() batches all quads, GPU rasterizes glyphs from the font atlas and blits directly into the compositor layer. On Pi3 / QEMU: font_raster_glyph writes each glyph pixel-by-pixel into the fb SW floor, comp_dirty_mark marks the bounding tile(s).
TEXTLAYOUT-PASS — test results
| Check | What it verifies |
|---|---|
19 chars/line at max_w=152 | At 8px/char, 19 glyphs fit before wrap (19×8=152); 20th triggers wrap |
char 19 wraps to y=12 | Row 0 top at y=0 (8px), wrap lands at cy=8; second wrap at cy=16... verify cy=12 after 2nd wrap trigger — test string induces this |
| Forced newline | \n at char N → cx resets to x0, cy advances FONT_H, no glyph placed |
| 21 quads emitted | One quad per visible glyph (21 chars excl. \n); n_quads confirmed after put_string |
PASS: TEXTLAYOUT-PASS wrap=ok newline=ok quads=21