/* 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 }