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:
co-authored by
Claude Sonnet 5
parent
b9b7cb2c6a
commit
5f6cc054d4
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 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;
|
||||
}
|
||||
Reference in New Issue
Block a user