starkernel: retarget REPL glyph rendering to TTF-TEXT's rasterizer (4.4j)

font_8x16.c keeps rendering everything through and including POST;
TTF-TEXT's rasterizer takes over at the interactive REPL boundary
(sk_repl()) via a new runtime mode switch, vt100_enable_ttf()
(console_fb_enable_ttf() wrapper), not a compile-time swap -- both
backends coexist in the same binary since boot/POST must stay
font_8x16.c per this item's own done-when.

TTF-TEXT (the FORTH word) isn't directly callable from vt100.c -- VM
stack arguments, different call shape than a one-glyph cell draw. Used
hal/ttf.c's VM-independent primitives directly instead (same rasterizer
TTF-TEXT itself calls underneath), added as a native C helper in
vt100.c. Lazily loads fonts:JetBrainsMono-Regular.ttf and kmallocs a
96-slot raster cache (covers all 95 printable ASCII, no eviction
thrash) on first switch.

Cell geometry changes at the switch (mode-aware cell_w()/cell_h()):
provisional 12x24 TTF cell (600/1000em * 20px = 12px exactly, using
4.4i's confirmed-uniform hmtx advance width) vs font_8x16's fixed 8x16
-- cols/rows re-derived and screen cleared at the switch point, same as
vt100_init() itself does. Final REPL text size is 4.4m's decision, not
this item's.

Also fixes the second call site 4.4i flagged: erase_line_range() now
uses one fb_fill_rect() instead of a per-cell font_8x16-specific blank
glyph draw, consistent with erase_display(2)'s full-screen case.

Three-arch verified: amd64/aarch64/riscv64 all reach ok>, POST
Failed: 0, identical dict-hashes. amd64 screendump shows real
proportional JetBrains Mono letterforms on the REPL tail, visibly
distinct from every prior font_8x16 screenshot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-11 19:56:50 -04:00
co-authored by Claude Sonnet 5
parent 64c20c78eb
commit f729b91a09
18 changed files with 55116 additions and 115 deletions
+71 -1
View File
@@ -5306,7 +5306,7 @@ document and committing that amendment as its own item.*
> `draw_cursor_glyph()`'s call, `erase_line_range()`'s call, and the `fb_cell_w()`/
> `fb_cell_h()`/`UNDERLINE_ROW` metrics that currently assume `font_8x16` specifically.
- [ ] **4.4j — Retarget the glyph-draw call site from `font_8x16.c` to `TTF-TEXT`.** Depends
- [x] **4.4j — Retarget the glyph-draw call site from `font_8x16.c` to `TTF-TEXT`.** Depends
on 4.4i. **Boundary, corrected 2026-08-11:** `font_8x16.c`/VT100 keeps rendering everything
through and including **POST** (the parity/dictionary-hash self-test) — not just the
pre-bootstrap M1M6 messages originally scoped here. POST runs *after* the capsule/VM system
@@ -5328,6 +5328,76 @@ document and committing that amendment as its own item.*
> preference"). The actual early-boot/POST fallback is `font_8x16.c` specifically, not "the
> stroke font" generically.
> **Done 2026-08-11.** `TTF-TEXT` (the FORTH word, `word_source/ttf_words.c`) turned out not
> to be directly callable from `vt100.c` — it takes VM-stack arguments and a
> string+advance-loop, a different call shape than a one-glyph-at-a-time cell draw.
> `hal/ttf.c`'s primitives underneath it (`ttf_load_from_capsule`, `ttf_raster_cache_get`,
> etc.) are VM-independent, though, exactly like `font_8x16.c`'s own C functions — so
> "retarget to TTF-TEXT" means "retarget to the same rasterizer TTF-TEXT itself uses",
> implemented as a native C helper in `vt100.c`, not a call into the FORTH word. Recording
> this now so it isn't read later as scope drift.
>
> **Boundary implemented as a runtime switch, not a compile-time swap** (this item's own
> done-when requires boot/POST to stay `font_8x16.c` while REPL output uses TTF — both must
> coexist in the same binary). New `vt100_enable_ttf()` (`vt100.c`, wrapped by
> `console_fb_enable_ttf()` in `console.c`) flips a mode flag; `draw_cursor_glyph()` and
> `erase_line_range()` branch on it. Called exactly once, at the top of `sk_repl()`
> (`repl.c`) — the actual start of "the interactive REPL itself", after boot/POST have both
> already run under `font_8x16.c`. Lazily loads `fonts:JetBrainsMono-Regular.ttf` from its
> capsule and `kmalloc`s a 96-slot raster cache (up from `ttf_words.c`'s 32 — sized to cover
> all 95 printable ASCII with no eviction thrash, since a terminal cycles through that whole
> set constantly; ~614 KB, checked against the 2 GiB kmalloc heap default before choosing it,
> trivial by comparison) on first call only.
>
> **Cell geometry changes at the switch, deliberately (advisor-reviewed option (a), not
> (b)):** `cell_w()`/`cell_h()` are now mode-aware — `font_8x16`'s fixed 8×16 in bitmap mode,
> a provisional 12×24 in TTF mode (`600/1000 × 20px = 12px` exactly, using 4.4i's own
> confirmed-uniform `hmtx` advance width so the fixed-cell grid tiles cleanly; final REPL
> text size is 4.4m's decision, not this item's — marked provisional throughout). Since the
> grid changes, `vt100_enable_ttf()` re-derives `cols`/`rows` from the new cell size and
> clears the screen, the same as `vt100_init()` itself does — safe because this runs once, at
> a point where nothing worth preserving is on screen yet.
>
> **Also fixed the second call site 4.4i flagged**, not just `draw_cursor_glyph()`'s:
> `erase_line_range()` no longer draws a per-cell blank glyph (`font_8x16`-specific, and
> already inconsistent with `erase_display(2)`'s full-screen case, which used a plain rect
> fill). A blank cell is visually identical to a filled background rect regardless of glyph
> backend, so it's now one `fb_fill_rect()` call for the whole erased range — simpler *and*
> mode-independent, closing 4.4i's inconsistency finding rather than carrying it forward.
>
> Underline positioning (the third 4.4i finding, `UNDERLINE_ROW` hardcoding "row 14 of 16")
> is branched per-mode: unchanged in bitmap mode, placed 2px below the TTF baseline in TTF
> mode — no equivalent fixed row exists there since this parser doesn't read `hhea`
> underline metrics (`ttf.h`), so this is provisional too.
>
> **Three-arch verified:** amd64 (`logs/20260811-194937/amd64/`), aarch64
> (`logs/20260811-195246/aarch64/`), riscv64 (`logs/20260811-195446/riscv64/`) — all reach
> `ok>`, POST `Failed: 0`, identical dict-hashes across all three (confirming the TTF switch,
> which only runs after POST, has no effect on the parity-hashed dictionary state). amd64
> screendump, full and zoomed on the REPL tail:
> [evidence/amd64/qemu-screenshot-20260811-195041-4.4j-ttf-repl.png](evidence/amd64/qemu-screenshot-20260811-195041-4.4j-ttf-repl.png),
> [evidence/amd64/qemu-screenshot-20260811-195041-4.4j-ttf-repl-zoom.png](evidence/amd64/qemu-screenshot-20260811-195041-4.4j-ttf-repl-zoom.png)
> — real proportional JetBrains Mono letterforms on the version banner, CLI banner, and
> `ok>` prompt, visibly distinct from every prior screenshot's blocky bitmap font; `[Hera]`
> stays orange, `ok>` stays cyan (4.4h unaffected). Boot/POST (everything above the visible
> tail) is unchanged `font_8x16.c` output, confirmed by this same screendump showing nothing
> else on screen — `erase_display(2)` at the switch point cleared it, same behavior as every
> screenshot since 4.4c.
>
> **Known, pre-existing, not touched here:** the mangled `â[box][box]` where an em-dash
> should read (`"FORTH-79 interpreter — type BYE..."`) is `vt100_putc()`'s byte-oriented
> pipeline treating each byte of the UTF-8 em-dash as its own codepoint — `TTF-TEXT`'s own
> word decodes UTF-8 (`ttf_words_decode_utf8()`), but `vt100.c`'s character path never did,
> in *either* glyph mode. Confirmed pre-existing by checking earlier font_8x16-mode
> screenshots (4.4f/4.4h): the same line already rendered wrong there too (garbled as "tgpe
> BYE" etc.) — not a 4.4j regression, a longstanding gap in `vt100.c` unrelated to which font
> backend draws the (wrong) glyph.
>
> **CANVAS scroll-box text**, per this item's original done-when: 4.4n/4.4o build that box as
> separate, later items — this item's job was making sure the *same* TTF renderer will serve
> it once it exists, not building the box itself. No box exists yet, so this is deferred by
> construction, not skipped.
- [ ] **4.4k — Live-verify ANSI color end-to-end.** Depends on 4.4j. Inject an SGR color
escape sequence (`ESC[3xm`/`ESC[9xm`/256-color) over serial and confirm it visibly changes
`TTF-TEXT`-rendered glyph color in a screendump — the parser and the retargeted draw call