Files
LithosAnanake/tools/ttftest.c
T
Robert Allan JamesandClaude Sonnet 5 70b6918279 ttf.c: glyph outline extraction, simple and composite (item 4.3.7a)
ttf_glyph_outline() decodes simple-glyph flag/coordinate runs and
recursively resolves composite components into a caller-supplied point/
contour-end buffer, in Q48.16. Composite scale/rotation/skew transforms
are rejected with TTF_ERR_UNSUPPORTED rather than mis-rendered, since the
shared q48_mul/q48_div are unsigned-only; translation-only composites
(the only kind the v1 glyph repertoire uses) apply cleanly via q48_add.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 21:45:04 -04:00

222 lines
7.9 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.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 "../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);
}
}
}
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);
free(buf);
if (failures) {
printf("%d check(s) FAILED\n", failures);
return 1;
}
printf("all checks passed\n");
return 0;
}