Files
LithosAnanake/tools/ttftest.c
T
Robert Allan JamesandClaude Sonnet 5 b031b802e3 Rename FABRIC series: FABRIC.md->0, FABRIC-2.md->1, FABRIC-3.md->2, FABRIC-4.md unchanged
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
2026-09-04 11:22:51 -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) 20232025 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 "../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;
}