FABRIC.md -> FABRIC-0.md FABRIC-2.md -> FABRIC-1.md FABRIC-3.md -> FABRIC-2.md (the current/living document) FABRIC-4.md unchanged (new #3 to follow separately) Every cross-reference repo-wide updated to match, including doc-comment citations inside kernel source (.c/.h) files -- done via an ordered placeholder substitution (FABRIC-3.md->placeholder2, FABRIC-2.md-> placeholder1, FABRIC.md->placeholder0, then placeholders resolved to final names) in a single pass per file to avoid double-shifting already-renamed references. One line in capsules/font.4th grew past the 64-char block-format limit as a side effect of the longer filename; shortened it and reverified with mkcapsule --lint (34/34 pass) before rebuilding. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground) after the fix; logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
169 lines
5.1 KiB
C
169 lines
5.1 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
|
||
Copyright (c) 2023–2025 Robert A. James
|
||
All rights reserved.
|
||
|
||
Licensed under the StarForth License, Version 1.0
|
||
*/
|
||
|
||
/* ttf_words.c — TrueType text entry point (FABRIC-0.md item 4.3.7e).
|
||
* Kernel-only; no-op on hosted builds. */
|
||
|
||
#include "include/ttf_words.h"
|
||
#include "../../include/log.h"
|
||
#include "../../include/word_registry.h"
|
||
|
||
#ifdef __STARKERNEL__
|
||
#include "starkernel/framebuffer.h"
|
||
#include "starkernel/ttf.h"
|
||
#include "starkernel/capsule_generated.h"
|
||
|
||
#define TTF_WORDS_CACHE_SLOTS 32
|
||
#define TTF_WORDS_DEFAULT_FONT "fonts:JetBrainsMono-Regular.ttf"
|
||
|
||
static ttf_font_t g_ttf_font;
|
||
static int g_ttf_font_ready = 0;
|
||
static ttf_raster_cache_slot_t g_ttf_cache_slots[TTF_WORDS_CACHE_SLOTS];
|
||
static ttf_raster_cache_t g_ttf_cache;
|
||
|
||
static int ttf_words_ensure_font(void) {
|
||
if (g_ttf_font_ready) return 1;
|
||
if (ttf_load_from_capsule(
|
||
capsule_get_directory(), capsule_get_descriptors(),
|
||
capsule_get_names(), capsule_get_arena(),
|
||
TTF_WORDS_DEFAULT_FONT, &g_ttf_font) != TTF_OK)
|
||
return 0;
|
||
ttf_raster_cache_init(&g_ttf_cache, g_ttf_cache_slots, TTF_WORDS_CACHE_SLOTS);
|
||
g_ttf_font_ready = 1;
|
||
return 1;
|
||
}
|
||
|
||
/* Decode one UTF-8 codepoint from VM memory at *a (VM-space address),
|
||
* with *len bytes known remaining. Mirrors capsules/fabric.4th's
|
||
* DECODE-UTF8 exactly: same lead-byte-length table, same U+FFFD
|
||
* fallback (advancing exactly one byte) on an invalid or truncated
|
||
* sequence. Advances *a and *len past the consumed bytes. */
|
||
static uint32_t ttf_words_decode_utf8(VM *vm, vaddr_t *a, cell_t *len) {
|
||
uint8_t lead, b[4];
|
||
uint32_t seqlen, i, cp;
|
||
|
||
if (*len <= 0 || !vm_addr_ok(vm, *a, 1)) {
|
||
*len = 0;
|
||
return 0xFFFD;
|
||
}
|
||
lead = vm_load_u8(vm, *a);
|
||
|
||
if (lead < 0x80) seqlen = 1;
|
||
else if ((lead & 0xE0) == 0xC0) seqlen = 2;
|
||
else if ((lead & 0xF0) == 0xE0) seqlen = 3;
|
||
else if ((lead & 0xF8) == 0xF0) seqlen = 4;
|
||
else seqlen = 0;
|
||
|
||
if (seqlen == 0 || (cell_t) seqlen > *len || !vm_addr_ok(vm, *a, seqlen)) {
|
||
*a += 1;
|
||
*len -= 1;
|
||
return 0xFFFD;
|
||
}
|
||
|
||
for (i = 0; i < seqlen; i++) b[i] = vm_load_u8(vm, *a + i);
|
||
|
||
switch (seqlen) {
|
||
case 1:
|
||
cp = b[0];
|
||
break;
|
||
case 2:
|
||
cp = ((uint32_t) (b[0] & 0x1F) << 6) | (b[1] & 0x3F);
|
||
break;
|
||
case 3:
|
||
cp = ((uint32_t) (b[0] & 0x0F) << 12) | ((uint32_t) (b[1] & 0x3F) << 6) | (b[2] & 0x3F);
|
||
break;
|
||
default:
|
||
cp = ((uint32_t) (b[0] & 0x07) << 18) | ((uint32_t) (b[1] & 0x3F) << 12)
|
||
| ((uint32_t) (b[2] & 0x3F) << 6) | (b[3] & 0x3F);
|
||
break;
|
||
}
|
||
|
||
*a += seqlen;
|
||
*len -= (cell_t) seqlen;
|
||
return cp;
|
||
}
|
||
|
||
/* TTF-TEXT ( c-addr u x y size color -- ) */
|
||
static void ttf_word_text(VM *vm) {
|
||
cell_t color, size, y, x, u, addr_cell;
|
||
vaddr_t a;
|
||
cell_t remaining;
|
||
uint32_t pen_x, baseline_y;
|
||
q48_16_t scale;
|
||
|
||
if (vm->dsp < 5) {
|
||
log_message(LOG_ERROR, "TTF-TEXT: Stack underflow");
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
color = vm_pop(vm);
|
||
size = vm_pop(vm);
|
||
y = vm_pop(vm);
|
||
x = vm_pop(vm);
|
||
u = vm_pop(vm);
|
||
addr_cell = vm_pop(vm);
|
||
|
||
if (!ttf_words_ensure_font()) {
|
||
log_message(LOG_ERROR, "TTF-TEXT: font capsule load failed");
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
if (size <= 0 || size > TTF_CACHE_MAX_SIZE_PX) {
|
||
log_message(LOG_ERROR, "TTF-TEXT: size out of range");
|
||
vm->error = 1;
|
||
return;
|
||
}
|
||
|
||
a = VM_ADDR(addr_cell);
|
||
remaining = u;
|
||
pen_x = (uint32_t) x;
|
||
/* Caller-facing (x,y) is Cartesian: origin at the bottom-left of the physical
|
||
* framebuffer, y increasing upward. fb_put_pixel() takes framebuffer coordinates
|
||
* (origin top-left, y increasing downward), so flip once here per-call rather
|
||
* than at every blit site. */
|
||
baseline_y = fb_height() - (uint32_t) y;
|
||
scale = q48_div(q48_from_u64((uint32_t) size), q48_from_u64(g_ttf_font.units_per_em));
|
||
|
||
while (remaining > 0) {
|
||
uint32_t cp = ttf_words_decode_utf8(vm, &a, &remaining);
|
||
ttf_bitmap_t bmp;
|
||
int rc, was_hit;
|
||
uint32_t gid, adv_units, px, py;
|
||
q48_16_t adv_q;
|
||
|
||
rc = ttf_raster_cache_get(&g_ttf_cache, &g_ttf_font, cp, (uint32_t) size, &bmp, &was_hit);
|
||
if (rc == TTF_OK) {
|
||
for (py = 0; py < bmp.height; py++) {
|
||
for (px = 0; px < bmp.width; px++) {
|
||
if (bmp.pixels[py * bmp.width + px]) {
|
||
fb_put_pixel(pen_x + px - TTF_CACHE_MARGIN,
|
||
baseline_y + py - (uint32_t) size - TTF_CACHE_MARGIN,
|
||
(uint32_t) color);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
gid = ttf_codepoint_to_glyph(&g_ttf_font, cp);
|
||
adv_units = ttf_glyph_advance_width(&g_ttf_font, gid);
|
||
adv_q = q48_mul(q48_from_u64(adv_units), scale);
|
||
pen_x += (uint32_t) q48_to_u64(adv_q);
|
||
}
|
||
}
|
||
#endif /* __STARKERNEL__ */
|
||
|
||
void register_ttf_words(VM *vm)
|
||
{
|
||
#ifdef __STARKERNEL__
|
||
register_word(vm, "TTF-TEXT", ttf_word_text);
|
||
#else
|
||
(void) vm;
|
||
#endif
|
||
}
|