Files
LithosAnanake/v3/src/word_source/ttf_words.c
T
rajamesandJunie a8b70e88d3 Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
2026-10-01 15:40:09 -04:00

169 lines
5.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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
}