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>
222 lines
7.9 KiB
C
222 lines
7.9 KiB
C
/*
|
||
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 <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;
|
||
} |