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:
co-authored by
Claude Sonnet 5
parent
64c20c78eb
commit
f729b91a09
@@ -296,6 +296,13 @@ void console_fb_init(const FramebufferInfo *info, FbPixelFormat fmt)
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_enable_ttf(void)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_enable_ttf();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single character from serial console (non-blocking)
|
||||
* Returns -1 if no character available
|
||||
|
||||
+143
-11
@@ -40,14 +40,47 @@
|
||||
|
||||
#include "vt100.h"
|
||||
#include "framebuffer.h"
|
||||
#include "starkernel/ttf.h"
|
||||
#include "starkernel/capsule_generated.h"
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include "log.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* Maximum CSI parameters */
|
||||
#define VT100_MAX_PARAMS 16
|
||||
|
||||
/* Underline scanline (row 14 of 16) */
|
||||
/* Underline scanline (row 14 of 16) -- font_8x16 mode only; see
|
||||
* draw_cursor_glyph()'s mode split. */
|
||||
#define UNDERLINE_ROW 14u
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* FABRIC.md item 4.4j: TTF glyph backend, active only from
|
||||
* vt100_enable_ttf() onward (boot/POST stays font_8x16.c).
|
||||
*
|
||||
* Provisional point size and cell geometry -- item 4.4m owns the final
|
||||
* REPL-strip decision. Chosen so 4.4i's confirmed hmtx result (all
|
||||
* printable ASCII share one 600/1000-em advance width) divides evenly:
|
||||
* 600/1000 * 20 = 12px exactly, no fractional-pixel cell width. Cell
|
||||
* height is size + a fixed descender/leading allowance, not derived from
|
||||
* hhea (not read by this parser -- see ttf.h) -- provisional like the
|
||||
* width.
|
||||
* --------------------------------------------------------------------- */
|
||||
#define VT100_TTF_SIZE_PX 20u
|
||||
#define VT100_TTF_CELL_W_PX 12u /* 600/1000 * VT100_TTF_SIZE_PX */
|
||||
#define VT100_TTF_CELL_H_PX 24u /* provisional: size + 4px leading/descender */
|
||||
#define VT100_TTF_CACHE_SLOTS 96u /* covers all 95 printable ASCII, no thrash */
|
||||
#define VT100_TTF_FONT_CAPSULE "fonts:JetBrainsMono-Regular.ttf"
|
||||
|
||||
typedef enum {
|
||||
VT_GLYPH_BITMAP = 0, /* font_8x16.c, via fb_draw_glyph() */
|
||||
VT_GLYPH_TTF = 1, /* TTF-TEXT's rasterizer, via ttf_draw_glyph_cell() */
|
||||
} vt_glyph_mode_t;
|
||||
|
||||
static vt_glyph_mode_t g_glyph_mode = VT_GLYPH_BITMAP;
|
||||
static ttf_font_t g_ttf_font;
|
||||
static int g_ttf_ready = 0;
|
||||
static ttf_raster_cache_t g_ttf_cache;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Parser state enum
|
||||
* --------------------------------------------------------------------- */
|
||||
@@ -110,9 +143,13 @@ static void apply_sgr(void);
|
||||
* Internal helpers
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/* Framebuffer cell dimensions (pixels) — reads from framebuffer driver */
|
||||
static uint32_t cell_w(void) { return fb_cell_w(); }
|
||||
static uint32_t cell_h(void) { return fb_cell_h(); }
|
||||
/* Cell dimensions (pixels) — font_8x16.c's fixed 8x16 grid in bitmap mode,
|
||||
* or the provisional TTF cell (4.4j) once vt100_enable_ttf() has switched
|
||||
* modes. Mode-aware per 4.4i's finding that the grid was hardcoded to
|
||||
* font_8x16.c specifically; this is the generalization that item called
|
||||
* for. */
|
||||
static uint32_t cell_w(void) { return g_glyph_mode == VT_GLYPH_TTF ? VT100_TTF_CELL_W_PX : fb_cell_w(); }
|
||||
static uint32_t cell_h(void) { return g_glyph_mode == VT_GLYPH_TTF ? VT100_TTF_CELL_H_PX : fb_cell_h(); }
|
||||
|
||||
/* Pixel position of the top-left corner of character cell (cx, cy) */
|
||||
static uint32_t px_of(uint32_t col) { return col * cell_w(); }
|
||||
@@ -122,6 +159,46 @@ static uint32_t py_of(uint32_t row) { return row * cell_h(); }
|
||||
static uint32_t eff_fg(void) { return g_vt.reverse ? g_vt.bg : g_vt.fg; }
|
||||
static uint32_t eff_bg(void) { return g_vt.reverse ? g_vt.fg : g_vt.bg; }
|
||||
|
||||
/* Draw one glyph via the TTF rasterizer into the cell at pixel (cell_px,
|
||||
* cell_py). Mirrors fb_draw_glyph()'s opaque-cell contract (every pixel in
|
||||
* the cell gets either fg or bg, not just the glyph's "ink") by filling
|
||||
* the whole cell with bg first, then stamping covered bitmap pixels with
|
||||
* fg. Baseline sits VT100_TTF_SIZE_PX down from the cell's top, matching
|
||||
* TTF-TEXT's own bitmap-origin convention (ttf.h's
|
||||
* "glyph baseline sits at pixel (MARGIN, size_px + MARGIN)"). Bitmap
|
||||
* pixels are clipped to the cell rect -- a glyph's rasterized bounds can
|
||||
* slightly exceed the nominal advance width, and cells must stay opaque
|
||||
* neighbors, not bleed into each other. */
|
||||
static void ttf_draw_glyph_cell(uint32_t cell_px, uint32_t cell_py, uint8_t ch,
|
||||
uint32_t fg, uint32_t bg)
|
||||
{
|
||||
ttf_bitmap_t bmp;
|
||||
int rc, was_hit;
|
||||
uint32_t bx, by;
|
||||
uint32_t baseline_y = cell_py + VT100_TTF_SIZE_PX;
|
||||
|
||||
fb_fill_rect(cell_px, cell_py, cell_w(), cell_h(), bg);
|
||||
|
||||
if (!g_ttf_ready) return; /* shouldn't happen once VT_GLYPH_TTF is set, defensive only */
|
||||
|
||||
rc = ttf_raster_cache_get(&g_ttf_cache, &g_ttf_font, (uint32_t)ch,
|
||||
VT100_TTF_SIZE_PX, &bmp, &was_hit);
|
||||
if (rc != TTF_OK) return; /* missing/unrasterizable glyph -- leave cell blank (bg only) */
|
||||
|
||||
for (by = 0; by < bmp.height; by++) {
|
||||
int32_t fb_y = (int32_t)baseline_y + (int32_t)by
|
||||
- (int32_t)VT100_TTF_SIZE_PX - TTF_CACHE_MARGIN;
|
||||
if (fb_y < (int32_t)cell_py || fb_y >= (int32_t)(cell_py + cell_h())) continue;
|
||||
for (bx = 0; bx < bmp.width; bx++) {
|
||||
int32_t fb_x = (int32_t)cell_px + (int32_t)bx - TTF_CACHE_MARGIN;
|
||||
if (fb_x < (int32_t)cell_px || fb_x >= (int32_t)(cell_px + cell_w())) continue;
|
||||
if (bmp.pixels[by * bmp.width + bx]) {
|
||||
fb_put_pixel((uint32_t)fb_x, (uint32_t)fb_y, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_cursor_glyph(uint8_t ch)
|
||||
{
|
||||
uint32_t f = eff_fg();
|
||||
@@ -138,11 +215,22 @@ static void draw_cursor_glyph(uint8_t ch)
|
||||
f = FB_RGB(r, g, bv);
|
||||
}
|
||||
|
||||
fb_draw_glyph(px_of(g_vt.cx), py_of(g_vt.cy), ch, f, b);
|
||||
if (g_glyph_mode == VT_GLYPH_TTF) {
|
||||
ttf_draw_glyph_cell(px_of(g_vt.cx), py_of(g_vt.cy), ch, f, b);
|
||||
} else {
|
||||
fb_draw_glyph(px_of(g_vt.cx), py_of(g_vt.cy), ch, f, b);
|
||||
}
|
||||
|
||||
if (g_vt.underline) {
|
||||
uint32_t scale = cell_h() / 16u;
|
||||
uint32_t ul_y = py_of(g_vt.cy) + UNDERLINE_ROW * scale;
|
||||
/* font_8x16 mode: underline sits at UNDERLINE_ROW (row 14 of 16)
|
||||
* of that font's fixed grid, scaled. TTF mode: no equivalent fixed
|
||||
* row exists (this parser doesn't read hhea underline metrics --
|
||||
* see ttf.h) -- placed 2px below the baseline instead, a
|
||||
* provisional choice like the rest of 4.4j's TTF cell geometry. */
|
||||
uint32_t scale = g_glyph_mode == VT_GLYPH_TTF ? 1u : cell_h() / 16u;
|
||||
uint32_t ul_y = g_glyph_mode == VT_GLYPH_TTF
|
||||
? py_of(g_vt.cy) + VT100_TTF_SIZE_PX + 2u
|
||||
: py_of(g_vt.cy) + UNDERLINE_ROW * scale;
|
||||
fb_fill_rect(px_of(g_vt.cx), ul_y, cell_w(), scale, f);
|
||||
}
|
||||
}
|
||||
@@ -195,6 +283,45 @@ void vt100_init(void)
|
||||
erase_display(2);
|
||||
}
|
||||
|
||||
void vt100_enable_ttf(void)
|
||||
{
|
||||
if (g_glyph_mode == VT_GLYPH_TTF) return; /* already switched */
|
||||
if (!g_vt.initialized) return;
|
||||
|
||||
if (!g_ttf_ready) {
|
||||
ttf_raster_cache_slot_t *slots;
|
||||
int rc;
|
||||
|
||||
rc = ttf_load_from_capsule(capsule_get_directory(), capsule_get_descriptors(),
|
||||
capsule_get_names(), capsule_get_arena(),
|
||||
VT100_TTF_FONT_CAPSULE, &g_ttf_font);
|
||||
if (rc != TTF_OK) {
|
||||
log_message(LOG_ERROR, "vt100: TTF font capsule load failed (rc=%d), staying on font_8x16", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
slots = (ttf_raster_cache_slot_t *)kmalloc(sizeof(ttf_raster_cache_slot_t) * VT100_TTF_CACHE_SLOTS);
|
||||
if (!slots) {
|
||||
log_message(LOG_ERROR, "vt100: TTF cache allocation failed, staying on font_8x16");
|
||||
return;
|
||||
}
|
||||
ttf_raster_cache_init(&g_ttf_cache, slots, VT100_TTF_CACHE_SLOTS);
|
||||
g_ttf_ready = 1;
|
||||
}
|
||||
|
||||
g_glyph_mode = VT_GLYPH_TTF;
|
||||
|
||||
/* Cell geometry just changed (font_8x16's fixed 8x16 vs. TTF's
|
||||
* provisional 12x24, see this file's 4.4j block above) -- re-derive
|
||||
* cols/rows and clear, the same as vt100_init() itself does. This only
|
||||
* ever runs once, at the POST-to-REPL boundary (sk_repl()), so nothing
|
||||
* of value is on screen yet to preserve. */
|
||||
g_vt.cols = fb_width() / cell_w();
|
||||
g_vt.rows = fb_height() / cell_h();
|
||||
g_vt.cx = g_vt.cy = 0;
|
||||
erase_display(2);
|
||||
}
|
||||
|
||||
uint32_t vt100_cols(void) { return g_vt.cols; }
|
||||
uint32_t vt100_rows(void) { return g_vt.rows; }
|
||||
|
||||
@@ -232,12 +359,17 @@ static void scroll_up(uint32_t lines)
|
||||
* Erase operations
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/* FABRIC.md item 4.4i found this drawing a per-cell blank glyph
|
||||
* (font_8x16.c-specific, and inconsistent with erase_display(2)'s
|
||||
* full-screen case just below, which already used a plain rect fill).
|
||||
* 4.4j fixes both problems at once: a blank cell is visually identical to
|
||||
* a filled bg rect regardless of glyph backend, so this needs no font
|
||||
* involvement at all -- one fb_fill_rect() for the whole range instead of
|
||||
* a per-column glyph draw. */
|
||||
static void erase_line_range(uint32_t row, uint32_t c0, uint32_t c1)
|
||||
{
|
||||
uint32_t c;
|
||||
for (c = c0; c < c1; c++) {
|
||||
fb_draw_glyph(px_of(c), py_of(row), 0x20, g_vt.fg, g_vt.bg);
|
||||
}
|
||||
if (c1 <= c0) return;
|
||||
fb_fill_rect(px_of(c0), py_of(row), (c1 - c0) * cell_w(), cell_h(), g_vt.bg);
|
||||
}
|
||||
|
||||
static void erase_display(int mode)
|
||||
|
||||
@@ -268,6 +268,13 @@ void sk_repl_run(VM *vm)
|
||||
|
||||
void sk_repl(VM *vm)
|
||||
{
|
||||
/* FABRIC.md item 4.4j: boot and POST (both already returned by the time
|
||||
* sk_repl() is called) stay on font_8x16.c/VT100 by design; the
|
||||
* interactive REPL -- this function -- is the boundary where TTF-TEXT
|
||||
* takes over. One-shot: console_fb_enable_ttf() no-ops on any later
|
||||
* call. */
|
||||
console_fb_enable_ttf();
|
||||
|
||||
console_println(lithos_version);
|
||||
console_puts("StarForth Version "); console_println(STARFORTH_VERSION);
|
||||
console_println("");
|
||||
|
||||
Reference in New Issue
Block a user