Files
LithosAnanake/tools/ttftest.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

378 lines
14 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.
*/
/**
* ttftest.c — Host-side unit test for src/starkernel/hal/ttf.c (FABRIC-0.md
* item 4.3.7). No QEMU needed, same pattern as tools/README.md's fbtest.c
* entry (that file is itself missing from tools/ — stale doc, reported
* separately, not fixed here).
*
* Expected values below were independently computed by a from-scratch
* Python/struct reference reader (not this codebase's parser), against
* JetBrainsMono-Regular.ttf.
*
* Usage: ./ttftest <path-to-ttf>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../kernel/include/starkernel/ttf.h"
typedef struct {
uint32_t codepoint;
uint32_t expect_glyph;
int16_t expect_contours;
int16_t expect_xmin, expect_ymin, expect_xmax, expect_ymax;
uint32_t expect_glyf_offset;
uint32_t expect_glyf_length;
} expect_t;
static const expect_t EXPECTED[] = {
/* cp glyph nc xmin ymin xmax ymax off len */
{0x0041, 1, 2, 50, 0, 550, 730, 35536, 112}, /* 'A' */
{0x0061, 198, 2, 67, -10, 508, 560, 46968, 244}, /* 'a' */
{0x0030, 737, 3, 80, -10, 520, 740, 92152, 184}, /* '0' */
{0x002E, 908, 1, 218, -10, 382, 152, 110044, 72}, /* '.' */
{0x0021, 913, 2, 225, -5, 375, 730, 110632, 108}, /* '!' */
{0x0040, 1151, 2, 45, -180, 560, 740, 144400, 224}, /* '@' */
{0x0020, 821, 0, 0, 0, 0, 0, 99448, 0}, /* space, empty glyph */
};
static int failures = 0;
static void check_eq_i(const char *what, uint32_t cp, long got, long want) {
if (got != want) {
printf(" FAIL U+%04X %s: got %ld want %ld\n", cp, what, got, want);
failures++;
}
}
/* Outline expectations (4.3.7a), independently computed by a from-scratch
* Python outline decoder (tools/../../tmp .../ttf_outline_ref.py, not
* committed, reproducible from FABRIC-0.md's 4.3.7a completion note) that
* shares no code with ttf.c. '.' and '0' are simple glyphs; 'A' is simple
* with two contours; 'a' is a translation-only composite (2 components). */
typedef struct {
int32_t x, y;
int on_curve;
} exp_point_t;
typedef struct {
uint32_t codepoint;
uint32_t glyph;
const uint16_t *contour_ends;
uint32_t contour_count;
const exp_point_t *points;
uint32_t point_count;
} outline_expect_t;
static const uint16_t DOT_ENDS[] = {11};
static const exp_point_t DOT_PTS[] = {
{300, -10, 1}, {263, -10, 0}, {218, 34, 0}, {218, 69, 1}, {218, 106, 0}, {263, 152, 0},
{300, 152, 1}, {337, 152, 0}, {382, 106, 0}, {382, 69, 1}, {382, 34, 0}, {337, -10, 0},
};
static const uint16_t A_ENDS[] = {7, 16};
static const exp_point_t A_PTS[] = {
{50, 0, 1}, {240, 730, 1}, {361, 730, 1}, {550, 0, 1}, {459, 0, 1}, {411, 194, 1},
{190, 194, 1}, {142, 0, 1}, {208, 270, 1}, {392, 270, 1}, {336, 495, 1}, {320, 559, 0},
{302, 645, 0}, {300, 658, 1}, {298, 645, 0}, {280, 559, 0}, {264, 496, 1},
};
static const uint16_t ALOWER_ENDS[] = {27, 38, 42};
static const exp_point_t ALOWER_PTS[] = {
{252, -10, 1}, {167, -10, 0}, {67, 85, 0}, {67, 162, 1}, {67, 213, 0}, {113, 289, 0},
{195, 332, 0}, {248, 332, 1}, {418, 332, 1}, {418, 375, 1}, {418, 482, 0}, {301, 482, 1},
{249, 482, 0}, {185, 444, 0}, {183, 410, 1}, {93, 410, 1}, {98, 475, 0}, {209, 560, 0},
{301, 560, 1}, {401, 560, 0}, {508, 464, 0}, {508, 378, 1}, {508, 0, 1}, {419, 0, 1},
{419, 100, 1}, {417, 100, 1}, {409, 49, 0}, {323, -10, 0}, {274, 66, 1}, {340, 66, 0},
{418, 130, 0}, {418, 185, 1}, {418, 262, 1}, {258, 262, 1}, {214, 262, 0}, {159, 209, 0},
{159, 165, 1}, {159, 119, 0}, {220, 66, 0}, {247, 645, 1}, {353, 785, 1}, {450, 785, 1},
{339, 645, 1},
};
static const outline_expect_t OUTLINE_EXPECTED[] = {
{0x002E, 908, DOT_ENDS, 1, DOT_PTS, 12},
{0x0041, 1, A_ENDS, 2, A_PTS, 17},
{0x0061, 199, ALOWER_ENDS, 3, ALOWER_PTS, 43},
};
static int32_t unscale(q48_16_t q) {
return (int32_t) (((int64_t) q) >> 16);
}
static void test_outlines(const ttf_font_t *font) {
ttf_point_t point_buf[600];
uint16_t contour_buf[16];
size_t i;
for (i = 0; i < sizeof(OUTLINE_EXPECTED) / sizeof(OUTLINE_EXPECTED[0]); i++) {
const outline_expect_t *e = &OUTLINE_EXPECTED[i];
ttf_outline_t outline;
int rc;
uint32_t j;
outline.points = point_buf;
outline.max_points = sizeof(point_buf) / sizeof(point_buf[0]);
outline.contour_ends = contour_buf;
outline.max_contours = sizeof(contour_buf) / sizeof(contour_buf[0]);
rc = ttf_glyph_outline(font, e->glyph, &outline);
if (rc != TTF_OK) {
printf(" FAIL U+%04X ttf_glyph_outline rc=%d\n", e->codepoint, rc);
failures++;
continue;
}
printf("U+%04X outline: points=%u (want %u) contours=%u (want %u)\n",
e->codepoint, outline.point_count, e->point_count,
outline.contour_count, e->contour_count);
check_eq_i("point_count", e->codepoint, outline.point_count, e->point_count);
check_eq_i("contour_count", e->codepoint, outline.contour_count, e->contour_count);
for (j = 0; j < e->contour_count && j < outline.contour_count; j++) {
check_eq_i("contour_end", e->codepoint, outline.contour_ends[j], e->contour_ends[j]);
}
for (j = 0; j < e->point_count && j < outline.point_count; j++) {
check_eq_i("point.x", e->codepoint, unscale(outline.points[j].x), e->points[j].x);
check_eq_i("point.y", e->codepoint, unscale(outline.points[j].y), e->points[j].y);
check_eq_i("point.on_curve", e->codepoint, outline.points[j].on_curve, e->points[j].on_curve);
}
}
}
/* Rasterization (4.3.7c) sanity check: rasterize a glyph at a modest
* pixel size, print an ASCII-art dump for visual "is this recognizable"
* inspection (this item's own acceptance criterion), plus loose
* structural checks -- fill coverage in a plausible range, background
* untouched in the far corner -- since there's no independent reference
* rasterizer to diff against pixel-for-pixel (unlike 4.3.7/4.3.7a, which
* had a from-scratch Python decoder to check against). */
static void test_rasterize(const ttf_font_t *font, uint32_t codepoint, const char *label) {
enum { SIZE_PX = 28, MARGIN = 6 };
uint32_t w = SIZE_PX + MARGIN * 2;
uint32_t h = SIZE_PX + MARGIN * 2;
uint8_t *pixels = calloc((size_t) w * h, 1);
ttf_bitmap_t bmp;
q48_16_t scale;
uint32_t gid;
int rc;
uint32_t x, y, filled = 0;
if (!pixels) {
printf("test_rasterize U+%04X: calloc failed\n", codepoint);
failures++;
return;
}
bmp.pixels = pixels;
bmp.width = w;
bmp.height = h;
scale = q48_div(q48_from_u64(SIZE_PX), q48_from_u64(font->units_per_em));
gid = ttf_codepoint_to_glyph(font, codepoint);
rc = ttf_rasterize_glyph(font, gid, scale,
q48_from_u64(MARGIN),
q48_from_u64(SIZE_PX + MARGIN),
255, &bmp);
if (rc != TTF_OK) {
printf("test_rasterize U+%04X (%s): ttf_glyph_outline/rasterize rc=%d\n",
codepoint, label, rc);
failures++;
free(pixels);
return;
}
printf("U+%04X (%s) rasterized %ux%u:\n", codepoint, label, w, h);
for (y = 0; y < h; y++) {
putchar('|');
for (x = 0; x < w; x++) {
int on = pixels[y * w + x] != 0;
putchar(on ? '#' : '.');
if (on) filled++;
}
putchar('|');
putchar('\n');
}
printf(" filled=%u / %u px (%.1f%%)\n", filled, w * h, 100.0 * filled / (w * h));
if (filled == 0) {
printf(" FAIL U+%04X (%s): rasterized to nothing\n", codepoint, label);
failures++;
} else if (filled > (w * h * 3) / 4) {
printf(" FAIL U+%04X (%s): implausibly full (%u/%u px) -- looks like a fill-rule bug\n",
codepoint, label, filled, w * h);
failures++;
}
if (pixels[0] != 0) {
printf(" FAIL U+%04X (%s): top-left corner (background) is filled\n", codepoint, label);
failures++;
}
free(pixels);
}
/* Raster cache (4.3.7d): first ttf_raster_cache_get() for a given
* (font, codepoint, size) must miss and rasterize; the second, identical
* call must hit -- checked both via the returned *was_hit flag and the
* slot's own hits counter (the item's own "measurable... counter or
* timing difference" bar), plus a wall-clock comparison as corroborating
* (not load-bearing -- host timing is noisy) evidence. A third call for
* a different codepoint at the same size must miss again, proving the
* cache actually discriminates on key, not just "always says hit". */
static void test_raster_cache(const ttf_font_t *font) {
enum { SLOTS = 4 };
ttf_raster_cache_slot_t slot_storage[SLOTS];
ttf_raster_cache_t cache;
ttf_bitmap_t bmp;
int was_hit, rc;
clock_t t0, t1, t2;
ttf_raster_cache_init(&cache, slot_storage, SLOTS);
t0 = clock();
rc = ttf_raster_cache_get(&cache, font, 0x0041, 24, &bmp, &was_hit);
t1 = clock();
if (rc != TTF_OK || was_hit) {
printf(" FAIL raster_cache: first call rc=%d was_hit=%d (want TTF_OK, miss)\n",
rc, was_hit);
failures++;
}
rc = ttf_raster_cache_get(&cache, font, 0x0041, 24, &bmp, &was_hit);
t2 = clock();
if (rc != TTF_OK || !was_hit) {
printf(" FAIL raster_cache: second (identical) call rc=%d was_hit=%d (want TTF_OK, hit)\n",
rc, was_hit);
failures++;
}
check_eq_i("raster_cache hits", 0x0041, slot_storage[0].hits, 1);
printf("raster_cache: miss=%.3fms hit=%.3fms (hit expected <= miss on a real cache;"
" host clock() is coarse/noisy, informational only)\n",
1000.0 * (t1 - t0) / CLOCKS_PER_SEC, 1000.0 * (t2 - t1) / CLOCKS_PER_SEC);
rc = ttf_raster_cache_get(&cache, font, 0x0061, 24, &bmp, &was_hit);
if (rc != TTF_OK || was_hit) {
printf(" FAIL raster_cache: different codepoint call rc=%d was_hit=%d (want TTF_OK, miss)\n",
rc, was_hit);
failures++;
}
}
/* Advance width (hmtx, needed for 4.3.7e's proportional spacing): no
* independent reference for this specific font's values, but
* JetBrainsMono is monospace, so every printable glyph's advance width
* should be identical and nonzero -- a real structural property, not an
* arbitrary assumption. */
static void test_advance_width(const ttf_font_t *font) {
static const uint32_t cps[] = {0x0041, 0x0061, 0x0030, 0x0020};
uint16_t first = 0;
size_t i;
for (i = 0; i < sizeof(cps) / sizeof(cps[0]); i++) {
uint32_t gid = ttf_codepoint_to_glyph(font, cps[i]);
uint16_t adv = ttf_glyph_advance_width(font, gid);
printf("U+%04X advance_width=%u\n", cps[i], adv);
if (i == 0) {
first = adv;
if (adv == 0) {
printf(" FAIL advance_width: U+%04X is 0\n", cps[i]);
failures++;
}
} else if (adv != first) {
printf(" FAIL advance_width: U+%04X=%u != U+%04X=%u (JetBrainsMono is monospace)\n",
cps[i], adv, cps[0], first);
failures++;
}
}
}
int main(int argc, char **argv) {
FILE *fp;
long size;
uint8_t *buf;
ttf_font_t font;
int rc;
size_t i;
if (argc < 2) {
fprintf(stderr, "usage: %s <path-to-ttf>\n", argv[0]);
return 2;
}
fp = fopen(argv[1], "rb");
if (!fp) {
perror("fopen");
return 2;
}
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
buf = malloc((size_t) size);
if (!buf || fread(buf, 1, (size_t) size, fp) != (size_t) size) {
fprintf(stderr, "read failed\n");
return 2;
}
fclose(fp);
rc = ttf_parse(buf, (uint32_t) size, &font);
if (rc != TTF_OK) {
printf("ttf_parse failed: rc=%d\n", rc);
return 1;
}
printf("parsed: unitsPerEm=%u numGlyphs=%u indexToLocFormat=%d has_cmap=%d\n",
font.units_per_em, font.num_glyphs, font.index_to_loc_format, font.has_cmap);
for (i = 0; i < sizeof(EXPECTED) / sizeof(EXPECTED[0]); i++) {
const expect_t *e = &EXPECTED[i];
uint32_t gid = ttf_codepoint_to_glyph(&font, e->codepoint);
ttf_glyph_header_t hdr;
int hrc;
printf("U+%04X -> glyph %u (want %u)\n", e->codepoint, gid, e->expect_glyph);
check_eq_i("glyph", e->codepoint, gid, e->expect_glyph);
hrc = ttf_glyph_header(&font, gid, &hdr);
if (hrc != TTF_OK) {
printf(" FAIL U+%04X ttf_glyph_header rc=%d\n", e->codepoint, hrc);
failures++;
continue;
}
check_eq_i("num_contours", e->codepoint, hdr.num_contours, e->expect_contours);
check_eq_i("x_min", e->codepoint, hdr.x_min, e->expect_xmin);
check_eq_i("y_min", e->codepoint, hdr.y_min, e->expect_ymin);
check_eq_i("x_max", e->codepoint, hdr.x_max, e->expect_xmax);
check_eq_i("y_max", e->codepoint, hdr.y_max, e->expect_ymax);
check_eq_i("glyf_offset", e->codepoint, hdr.glyf_offset, e->expect_glyf_offset);
check_eq_i("glyf_length", e->codepoint, hdr.glyf_length, e->expect_glyf_length);
}
test_outlines(&font);
test_rasterize(&font, 0x0041, "'A'");
test_rasterize(&font, 0x002E, "'.'");
test_rasterize(&font, 0x0061, "'a'");
test_raster_cache(&font);
test_advance_width(&font);
free(buf);
if (failures) {
printf("%d check(s) FAILED\n", failures);
return 1;
}
printf("all checks passed\n");
return 0;
}