← Blog
0.7.0 · VIDEO · BROWSER · TEXT

HTML Text Layout Engine — Line-Wrap, Baseline, Quad Emit — TEXTLAYOUT-PASS

June 22, 2026 · sigil-video 6158863 · Sigil-Docs
text layout font browser video rendering 0.7.0

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.

TEXTLAYOUT-PASS on x86 QEMU
TEXTLAYOUT-PASS on x86 QEMU — 19-char line wrap at max_w=152, forced newline, 21 quads emitted. Left-to-right cursor advance via font_raster_glyph SW floor. GPU quad buffer path (gpu_submit + glyph texture cache) silicon-pending.

Layout model

text_layout.sg uses a left-to-right, top-to-bottom cursor model matching the CSS inline layout spec:

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
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
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
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
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

CheckWhat it verifies
19 chars/line at max_w=152At 8px/char, 19 glyphs fit before wrap (19×8=152); 20th triggers wrap
char 19 wraps to y=12Row 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 emittedOne quad per visible glyph (21 chars excl. \n); n_quads confirmed after put_string

PASS: TEXTLAYOUT-PASS wrap=ok newline=ok quads=21