/* 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.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 */ #include #include #include #include #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.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 \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; }