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>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user