ttf.c/ttf.h/ttftest.c: TrueType parser core -- sfnt/head/maxp/loca/glyf/cmap (item 4.3.7)

Freestanding C module resolving a Unicode codepoint (cmap format 4) to a
glyph index and its outline header (contour count, bounding box), verified
against an independent from-scratch Python reference reader via
tools/ttftest.c. Not yet wired into the boot path or capsule system.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-10 21:41:42 -04:00
co-authored by Claude Sonnet 5
parent b9b7cb2c6a
commit 5f6cc054d4
4 changed files with 552 additions and 1 deletions
+247
View File
@@ -0,0 +1,247 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* ttf.c — TrueType font parser core (FABRIC.md item 4.3.7)
*
* sfnt directory + head/maxp/loca/glyf/cmap(format 4) table parsing. All
* multi-byte fields in a TTF are big-endian; this file reads them by hand
* (no libc byteswap dependency) with bounds checks against the buffer
* length on every access, since the buffer is untrusted input.
*/
#include "ttf.h"
#define TAG(a, b, c, d) \
(((uint32_t)(a) << 24) | ((uint32_t)(b) << 16) | ((uint32_t)(c) << 8) | (uint32_t)(d))
static int fits(uint32_t size, uint32_t off, uint32_t len) {
if (off > size) return 0;
if (len > size - off) return 0;
return 1;
}
static int in_bounds(const ttf_font_t *f, uint32_t off, uint32_t len) {
return fits(f->size, off, len);
}
static uint16_t rd_u16(const uint8_t *d, uint32_t off) {
return (uint16_t)(((uint16_t) d[off] << 8) | (uint16_t) d[off + 1]);
}
static int16_t rd_i16(const uint8_t *d, uint32_t off) {
return (int16_t) rd_u16(d, off);
}
static uint32_t rd_u32(const uint8_t *d, uint32_t off) {
return ((uint32_t) d[off] << 24) | ((uint32_t) d[off + 1] << 16) |
((uint32_t) d[off + 2] << 8) | (uint32_t) d[off + 3];
}
/* Locate a table record in the sfnt directory. Returns TTF_OK and fills
* tbl_off/tbl_len, or TTF_ERR_TABLE_MISSING. */
static int find_table(const uint8_t *data, uint32_t size, uint16_t num_tables,
uint32_t tag, uint32_t *tbl_off, uint32_t *tbl_len) {
uint32_t i;
for (i = 0; i < num_tables; i++) {
uint32_t rec = 12 + i * 16;
if (!fits(size, rec, 16))
return TTF_ERR_OUT_OF_BOUNDS;
{
uint32_t rec_tag = rd_u32(data, rec);
if (rec_tag == tag) {
*tbl_off = rd_u32(data, rec + 8);
*tbl_len = rd_u32(data, rec + 12);
return TTF_OK;
}
}
}
return TTF_ERR_TABLE_MISSING;
}
int ttf_parse(const uint8_t *data, uint32_t size, ttf_font_t *out) {
uint32_t sfnt_version;
uint16_t num_tables;
uint32_t glyf_len_unused;
int rc;
if (!data || !out || size < 12) return TTF_ERR_BAD_SFNT;
sfnt_version = rd_u32(data, 0);
if (sfnt_version != 0x00010000UL && sfnt_version != TAG('t', 'r', 'u', 'e'))
return TTF_ERR_BAD_SFNT; /* not TrueType outlines (e.g. 'OTTO' = CFF) */
num_tables = rd_u16(data, 4);
if (!fits(size, 12, (uint32_t) num_tables * 16))
return TTF_ERR_OUT_OF_BOUNDS;
out->data = data;
out->size = size;
out->has_cmap = 0;
out->cmap_subtable_off = 0;
rc = find_table(data, size, num_tables, TAG('h', 'e', 'a', 'd'), &out->head_off, &glyf_len_unused);
if (rc != TTF_OK) return rc;
if (!in_bounds(out, out->head_off, 54)) return TTF_ERR_BAD_TABLE;
out->units_per_em = rd_u16(data, out->head_off + 18);
out->index_to_loc_format = rd_i16(data, out->head_off + 50);
if (out->index_to_loc_format != 0 && out->index_to_loc_format != 1)
return TTF_ERR_BAD_TABLE;
rc = find_table(data, size, num_tables, TAG('m', 'a', 'x', 'p'), &out->maxp_off, &glyf_len_unused);
if (rc != TTF_OK) return rc;
if (!in_bounds(out, out->maxp_off, 6)) return TTF_ERR_BAD_TABLE;
out->num_glyphs = rd_u16(data, out->maxp_off + 4);
rc = find_table(data, size, num_tables, TAG('l', 'o', 'c', 'a'), &out->loca_off, &out->loca_len);
if (rc != TTF_OK) return rc;
{
uint32_t expect_entries = (uint32_t) out->num_glyphs + 1;
uint32_t expect_bytes = out->index_to_loc_format == 0
? expect_entries * 2
: expect_entries * 4;
if (out->loca_len < expect_bytes) return TTF_ERR_BAD_TABLE;
if (!in_bounds(out, out->loca_off, expect_bytes)) return TTF_ERR_OUT_OF_BOUNDS;
}
rc = find_table(data, size, num_tables, TAG('g', 'l', 'y', 'f'), &out->glyf_off, &out->glyf_len);
if (rc != TTF_OK) return rc;
if (!in_bounds(out, out->glyf_off, out->glyf_len)) return TTF_ERR_OUT_OF_BOUNDS;
/* cmap is optional to overall parse success; codepoint lookup just
* fails cleanly (TTF_GLYPH_MISSING) if it's absent or unrecognized. */
{
uint32_t cmap_off, cmap_len_unused;
rc = find_table(data, size, num_tables, TAG('c', 'm', 'a', 'p'), &cmap_off, &cmap_len_unused);
if (rc == TTF_OK && in_bounds(out, cmap_off, 4)) {
uint16_t cmap_num_tables = rd_u16(data, cmap_off + 2);
uint32_t i;
if (in_bounds(out, cmap_off + 4, (uint32_t) cmap_num_tables * 8)) {
uint32_t best_off = 0;
int best_score = -1;
for (i = 0; i < cmap_num_tables; i++) {
uint32_t rec = cmap_off + 4 + i * 8;
uint16_t platform_id = rd_u16(data, rec);
uint16_t encoding_id = rd_u16(data, rec + 2);
uint32_t sub_off = cmap_off + rd_u32(data, rec + 4);
int score = -1;
if (!in_bounds(out, sub_off, 2)) continue;
if (rd_u16(data, sub_off) != 4) continue; /* format 4 only, see header */
if (platform_id == 3 && encoding_id == 1) score = 3; /* Windows BMP */
else if (platform_id == 0) score = 2; /* Unicode */
else if (platform_id == 3 && encoding_id == 0) score = 1; /* Windows symbol */
if (score > best_score) {
best_score = score;
best_off = sub_off;
}
}
if (best_score >= 0) {
out->cmap_subtable_off = best_off;
out->has_cmap = 1;
}
}
}
}
return TTF_OK;
}
uint32_t ttf_codepoint_to_glyph(const ttf_font_t *font, uint32_t codepoint) {
uint32_t sub;
uint16_t seg_count_x2, seg_count;
uint32_t end_code_off, start_code_off, id_delta_off, id_range_off;
uint32_t i;
if (!font || !font->has_cmap) return TTF_GLYPH_MISSING;
if (codepoint > 0xFFFFUL) return TTF_GLYPH_MISSING; /* format 4 = BMP only */
sub = font->cmap_subtable_off;
if (!in_bounds(font, sub, 14)) return TTF_GLYPH_MISSING;
seg_count_x2 = rd_u16(font->data, sub + 6);
seg_count = (uint16_t) (seg_count_x2 / 2);
end_code_off = sub + 14;
start_code_off = end_code_off + seg_count_x2 + 2; /* +2 skips reservedPad */
id_delta_off = start_code_off + seg_count_x2;
id_range_off = id_delta_off + seg_count_x2;
if (!in_bounds(font, end_code_off, (uint32_t) seg_count_x2 * 4 + 2))
return TTF_GLYPH_MISSING;
for (i = 0; i < seg_count; i++) {
uint16_t end_code = rd_u16(font->data, end_code_off + i * 2);
if (codepoint > end_code) continue;
{
uint16_t start_code = rd_u16(font->data, start_code_off + i * 2);
int16_t id_delta = rd_i16(font->data, id_delta_off + i * 2);
uint16_t id_range_offset = rd_u16(font->data, id_range_off + i * 2);
if (codepoint < start_code) return TTF_GLYPH_MISSING;
if (id_range_offset == 0) {
return (uint32_t) ((uint16_t) (codepoint + (uint32_t) (int32_t) id_delta));
} else {
uint32_t glyph_addr = id_range_off + i * 2 + id_range_offset +
(uint32_t) (codepoint - start_code) * 2;
uint16_t glyph_id;
if (!in_bounds(font, glyph_addr, 2)) return TTF_GLYPH_MISSING;
glyph_id = rd_u16(font->data, glyph_addr);
if (glyph_id == 0) return TTF_GLYPH_MISSING;
return (uint32_t) ((uint16_t) (glyph_id + (uint32_t) (int32_t) id_delta));
}
}
}
return TTF_GLYPH_MISSING;
}
int ttf_glyph_header(const ttf_font_t *font, uint32_t glyph_index, ttf_glyph_header_t *out) {
uint32_t off_a, off_b;
if (!font || !out) return TTF_ERR_BAD_TABLE;
if (glyph_index >= font->num_glyphs) return TTF_ERR_BAD_GLYPH_INDEX;
if (font->index_to_loc_format == 0) {
uint32_t e = font->loca_off + glyph_index * 2;
if (!in_bounds(font, e, 4)) return TTF_ERR_OUT_OF_BOUNDS;
off_a = (uint32_t) rd_u16(font->data, e) * 2;
off_b = (uint32_t) rd_u16(font->data, e + 2) * 2;
} else {
uint32_t e = font->loca_off + glyph_index * 4;
if (!in_bounds(font, e, 8)) return TTF_ERR_OUT_OF_BOUNDS;
off_a = rd_u32(font->data, e);
off_b = rd_u32(font->data, e + 4);
}
if (off_b < off_a) return TTF_ERR_BAD_TABLE;
out->glyf_offset = font->glyf_off + off_a;
out->glyf_length = off_b - off_a;
if (out->glyf_length == 0) {
/* Empty glyph (e.g. space) — no glyf record at all. */
out->num_contours = 0;
out->x_min = out->y_min = out->x_max = out->y_max = 0;
return TTF_OK;
}
if (!in_bounds(font, out->glyf_offset, 10)) return TTF_ERR_OUT_OF_BOUNDS;
out->num_contours = rd_i16(font->data, out->glyf_offset);
out->x_min = rd_i16(font->data, out->glyf_offset + 2);
out->y_min = rd_i16(font->data, out->glyf_offset + 4);
out->x_max = rd_i16(font->data, out->glyf_offset + 6);
out->y_max = rd_i16(font->data, out->glyf_offset + 8);
return TTF_OK;
}