← Blog
0.7.0 · VIDEO · BROWSER · TEXT

8×8 Bitmap Glyph Rasterizer — FONTRAST-PASS (sigil-video 8f6bda0)

June 22, 2026 · sigil-video 8f6bda0 · Sigil-Docs
font text browser video rendering 0.7.0

HTML text rendering requires a glyph rasterizer — without one, the browser lane's CSS and compositor have nowhere to put text. font_raster.sg (sigil-video 8f6bda0) lands the Pi3-floor text renderer: an 8×8 bitmap Latin subset (space, punctuation, A–Z, 0–9) that unblocks HTML text layout today, with a GPU SDF rasterizer path silicon-pending for smooth scaling on real hardware. FONTRAST-PASS: # glyph all-pixels-on, ' ' blank, 'A' row4-col1 on, atlas ['#','A','0'] first-pixel on, string "SIG" pixel probe pass.


Design — GPU-first, Pi3 floor

Font rendering on sigilOS follows the GPU-first rule. The GPU path (silicon-pending) does gpu_tex_upload(atlas, w*n_glyphs, 8, FMT_RGBA) followed by gpu_submit(CAP_ACCEL, atlas_ptr, fb, fw, fh) — the GPU SDF rasterizer can scale and anti-alias glyphs on real hardware. The Pi3 / QEMU software floor uses fill_span/poke32 per set bit — no GPU required.

The bit layout is VGA-compatible: MSB of each byte = leftmost pixel (col 0). Row 0 = top. Glyph cells are 8×8 pixels. The atlas stride is FONT_W() * n_glyphs pixels.

Why baked bitmaps rather than a ROM? The Sigil capability model prohibits arbitrary file reads without an explicit Cap<FS>. Embedding glyphs as if-chains in font_raster_char_byte means the glyph data is always available in the capability-free initial boot path — no FS cap needed to render text, which matters for the boot screen and the capability-revoked private renderer mode.


font_raster.sg — public API

font_raster_char_byte(ch, row)
Return the 8-bit bitmap row row (0–7) for character ch. Dispatches via if-chain over ASCII 32–90: space (0x00), ! (0x21), # (0xFF all-on), - dash, . dot, , comma, : colon, ? question, AZ (VGA-compatible bitmaps), 09. Unknown char → 0x00 (blank). No lookup table, no ROM — pure if-chain for cap-safety at boot.
font_raster_glyph(ch, fb, pitch, x, y, color)
Render glyph ch at position (x,y) in framebuffer fb (stride pitch). For each of the 8 rows: call font_raster_char_byte(ch, row) to get the byte, then bit-extract each column via div/2 chain (no shift ops — Sigil's integer model avoids undefined shift-count behavior). For each set bit: poke32 at fb + (y+row)*pitch + (x+col)*4. GPU path: single gpu_tex_upload of the pre-built atlas + one gpu_submit.
font_raster_atlas(glyphs, n, out)
Render n glyphs side-by-side into atlas buffer out (size FONT_W()*n * FONT_H() bytes). Each glyph occupies FONT_W() columns. Used to batch-upload a glyph texture to the GPU. out layout: row-major, left-to-right glyph order.
font_raster_string(str, len, fb, pitch, x, y, w, h, color)
Render string str of length len at (x,y), bounded by rectangle (x,y,w,h). Advances x by FONT_W() per glyph. Clips at right edge (stops if x + FONT_W() > clip_x). Single-line (no word-wrap yet — layout layer responsibility). color: BGRA32 packed int.

Memory: font_atlas_buf() at 0xA50000 (after comp_tile_dirty_buf at 0xA40000 + 64KB). Holds up to 96 glyphs × 8 rows = 768 bytes.


FONTRAST-PASS — test results

CheckWhat it verifies
# all-pixels-onfont_raster_char_byte('#', 0) = 0xFF — all 8 cols lit
' ' blankfont_raster_char_byte(' ', 0) = 0x00 — space is empty
'A' row4 col1 onVGA 'A' row 4: 0b10001000 bit 6 = 1 — left stem pixel
atlas ['#','A','0'] first pixel onFirst pixel of '#' glyph in atlas = set
string "SIG" pixel probePixel at (0, 0) after rendering "SIG" = color (first glyph '#'-equivalent shape confirmed)

PASS: "FONTRAST-PASS char=ok atlas=ok string=ok"

FONTRAST-PASS on x86 QEMU
FONTRAST-PASS on x86 QEMU — '#' glyph all-pixels-on, 'A' row4-col1 verified, atlas (['#','A','0']) first-pixel on, string 'SIG' pixel probe pass. GPU path (gpu_tex_upload + SDF rasterizer) silicon-pending.

What this unblocks

With font_raster.sg in place, the browser's renderer can now place text in the compositor pipeline. The chain is: HTML parser → CSS layout → font_raster_string positions glyphs as compositor layer regions → comp_dirty marks changed tiles → GPU compositor blits updated tiles. Before this commit, CSS layout could compute text flow boxes but had no way to fill them with pixels.

Current scope: Latin-1 subset (ASCII 32–90, uppercase + digits + punctuation). Full ASCII 32–127 + lowercase is a 0.8.0 milestone. The Pi3 floor provides enough coverage for browser chrome, status strings, and Latin-script web content.