Files
LithosAnanake/kernel/src/hal/ttf.c
T
rajamesandJunie a8b70e88d3 Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
2026-10-01 15:40:09 -04:00

967 lines
36 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) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* ttf.c — TrueType font parser core (FABRIC-0.md items 4.3.7, 4.3.7a)
*
* sfnt directory + head/maxp/loca/glyf/cmap(format 4) table parsing, plus
* simple- and composite-glyph outline extraction. 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.
*
* Composite-glyph transforms: this file applies (dx,dy) TRANSLATION only.
* A composite component carrying a non-identity scale/rotation/skew (the
* WE_HAVE_A_SCALE/WE_HAVE_AN_X_AND_Y_SCALE/WE_HAVE_A_TWO_BY_TWO flags with
* a non-1.0/non-zero F2Dot14 value) is rejected with TTF_ERR_UNSUPPORTED
* rather than silently mis-rendered. Reason: applying such a transform
* needs a signed fixed-point multiply, and the shared q48_mul()/q48_div()
* in src/starkernel/math/q48_16.c are unsigned-only (q48_div saturates via
* an unsigned overflow check; q48_mul does a plain unsigned widen-multiply)
* — confirmed by reading that file, not assumed. Per this repo's rule
* against modifying a shared/tested module to "fix" it without being
* asked, that gap is reported (see FABRIC-0.md's 4.3.7a completion note),
* not patched here. No glyph in the v1 repertoire (§27.6.4) needs a
* non-identity composite transform — checked against
* fonts/JetBrainsMono-Regular.ttf before writing this, not assumed.
*/
#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;
/* hhea (for numberOfHMetrics) + hmtx (advance widths) -- mandatory,
* needed for ttf_glyph_advance_width() (4.3.7e's proportional
* spacing). numberOfHMetrics is hhea's 18th field, a fixed 36-byte
* table per spec. */
{
uint32_t hhea_off, hhea_len_unused, hmtx_len;
rc = find_table(data, size, num_tables, TAG('h', 'h', 'e', 'a'), &hhea_off, &hhea_len_unused);
if (rc != TTF_OK) return rc;
if (!in_bounds(out, hhea_off, 36)) return TTF_ERR_BAD_TABLE;
out->num_h_metrics = rd_u16(data, hhea_off + 34);
rc = find_table(data, size, num_tables, TAG('h', 'm', 't', 'x'), &out->hmtx_off, &hmtx_len);
if (rc != TTF_OK) return rc;
if (out->num_h_metrics == 0
|| !in_bounds(out, out->hmtx_off, (uint32_t) out->num_h_metrics * 4))
return TTF_ERR_BAD_TABLE;
}
/* 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;
}
uint16_t ttf_glyph_advance_width(const ttf_font_t *font, uint32_t glyph_index) {
uint32_t idx, off;
if (!font || font->num_h_metrics == 0) return 0;
idx = glyph_index < font->num_h_metrics ? glyph_index : (uint32_t) font->num_h_metrics - 1;
off = font->hmtx_off + idx * 4;
if (!in_bounds(font, off, 2)) return 0;
return rd_u16(font->data, off);
}
/* ===========================================================================
* 4.3.7a — glyph outline extraction
* ===========================================================================
*/
static uint8_t rd_u8(const uint8_t *d, uint32_t off) {
return d[off];
}
static int8_t rd_i8(const uint8_t *d, uint32_t off) {
return (int8_t) d[off];
}
/* Left-shift a signed 32-bit font-unit coordinate into Q48.16. Two's-
* complement, deliberately not going through q48_16.h's q48_from_u64 (same
* operation, but that name implies an unsigned caller, which this isn't —
* see this file's header comment on why negative values are fine for
* shift/add/sub but not for q48_mul/q48_div). */
static q48_16_t q48_from_i32(int32_t v) {
return ((uint64_t) (int64_t) v) << 16;
}
#define TTF_FLAG_ON_CURVE 0x01
#define TTF_FLAG_X_SHORT 0x02
#define TTF_FLAG_Y_SHORT 0x04
#define TTF_FLAG_REPEAT 0x08
#define TTF_FLAG_X_SAME_OR_POS 0x10
#define TTF_FLAG_Y_SAME_OR_POS 0x20
/* Decode-time cap on points/contours per single simple-glyph component —
* a defensive stack-buffer bound, independent of the caller's out->
* max_points/max_contours (which are checked separately, on the combined
* outline). No glyph in the v1 repertoire (§27.6.4) comes close to this. */
#define TTF_MAX_SIMPLE_POINTS 512
static int decode_simple_glyph(const ttf_font_t *font, const ttf_glyph_header_t *hdr,
ttf_outline_t *out) {
uint32_t off = hdr->glyf_offset + 10;
uint16_t nc = (uint16_t) hdr->num_contours;
uint16_t end_pts[TTF_MAX_SIMPLE_POINTS]; /* reused below for endPtsOfContours only, nc <= that bound checked */
uint16_t num_points;
uint8_t flags[TTF_MAX_SIMPLE_POINTS];
int32_t xs[TTF_MAX_SIMPLE_POINTS];
int32_t ys[TTF_MAX_SIMPLE_POINTS];
uint32_t i;
uint32_t point_base;
int32_t acc;
if (nc == 0 || nc > TTF_MAX_SIMPLE_POINTS) return TTF_ERR_TOO_MANY_CONTOURS;
if (!in_bounds(font, off, (uint32_t) nc * 2 + 2)) return TTF_ERR_OUT_OF_BOUNDS;
for (i = 0; i < nc; i++) {
end_pts[i] = rd_u16(font->data, off + i * 2);
}
off += (uint32_t) nc * 2;
num_points = (uint16_t) (end_pts[nc - 1] + 1);
if (num_points == 0 || num_points > TTF_MAX_SIMPLE_POINTS) return TTF_ERR_TOO_MANY_POINTS;
{
uint16_t instruction_length;
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
instruction_length = rd_u16(font->data, off);
off += 2;
if (!in_bounds(font, off, instruction_length)) return TTF_ERR_OUT_OF_BOUNDS;
off += instruction_length;
}
/* Flags, with run-length repeat expansion. */
i = 0;
while (i < num_points) {
uint8_t f;
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
f = rd_u8(font->data, off);
off += 1;
flags[i++] = f;
if (f & TTF_FLAG_REPEAT) {
uint8_t repeat;
uint32_t r;
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
repeat = rd_u8(font->data, off);
off += 1;
for (r = 0; r < repeat; r++) {
if (i >= num_points) return TTF_ERR_TOO_MANY_POINTS;
flags[i++] = f;
}
}
}
/* X coordinates, delta-decoded. */
acc = 0;
for (i = 0; i < num_points; i++) {
uint8_t f = flags[i];
int32_t dx;
if (f & TTF_FLAG_X_SHORT) {
uint8_t v;
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
v = rd_u8(font->data, off);
off += 1;
dx = (f & TTF_FLAG_X_SAME_OR_POS) ? (int32_t) v : -(int32_t) v;
} else if (f & TTF_FLAG_X_SAME_OR_POS) {
dx = 0;
} else {
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
dx = rd_i16(font->data, off);
off += 2;
}
acc += dx;
xs[i] = acc;
}
/* Y coordinates, delta-decoded. */
acc = 0;
for (i = 0; i < num_points; i++) {
uint8_t f = flags[i];
int32_t dy;
if (f & TTF_FLAG_Y_SHORT) {
uint8_t v;
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
v = rd_u8(font->data, off);
off += 1;
dy = (f & TTF_FLAG_Y_SAME_OR_POS) ? (int32_t) v : -(int32_t) v;
} else if (f & TTF_FLAG_Y_SAME_OR_POS) {
dy = 0;
} else {
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
dy = rd_i16(font->data, off);
off += 2;
}
acc += dy;
ys[i] = acc;
}
if (out->point_count + num_points > out->max_points) return TTF_ERR_TOO_MANY_POINTS;
if (out->contour_count + nc > out->max_contours) return TTF_ERR_TOO_MANY_CONTOURS;
point_base = out->point_count;
for (i = 0; i < num_points; i++) {
out->points[point_base + i].x = q48_from_i32(xs[i]);
out->points[point_base + i].y = q48_from_i32(ys[i]);
out->points[point_base + i].on_curve = (uint8_t) ((flags[i] & TTF_FLAG_ON_CURVE) != 0);
}
for (i = 0; i < nc; i++) {
out->contour_ends[out->contour_count + i] = (uint16_t) (point_base + end_pts[i]);
}
out->point_count += num_points;
out->contour_count += nc;
return TTF_OK;
}
#define TTF_COMP_ARGS_ARE_WORDS 0x0001
#define TTF_COMP_ARGS_ARE_XY_VALUES 0x0002
#define TTF_COMP_WE_HAVE_A_SCALE 0x0008
#define TTF_COMP_MORE_COMPONENTS 0x0020
#define TTF_COMP_WE_HAVE_XY_SCALE 0x0040
#define TTF_COMP_WE_HAVE_2X2 0x0080
#define TTF_F2DOT14_ONE 16384 /* 1.0 in F2Dot14 */
static int decode_glyph_r(const ttf_font_t *font, uint32_t glyph_index,
ttf_outline_t *out, int depth);
static int decode_composite_glyph(const ttf_font_t *font, const ttf_glyph_header_t *hdr,
ttf_outline_t *out, int depth) {
uint32_t off = hdr->glyf_offset + 10;
int more = 1;
while (more) {
uint16_t flags, comp_glyph;
int32_t arg1, arg2;
uint32_t point_base;
int rc;
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
flags = rd_u16(font->data, off);
comp_glyph = rd_u16(font->data, off + 2);
off += 4;
if (!(flags & TTF_COMP_ARGS_ARE_XY_VALUES)) return TTF_ERR_UNSUPPORTED; /* point-matching, not implemented */
if (flags & TTF_COMP_ARGS_ARE_WORDS) {
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
arg1 = rd_i16(font->data, off);
arg2 = rd_i16(font->data, off + 2);
off += 4;
} else {
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
arg1 = rd_i8(font->data, off);
arg2 = rd_i8(font->data, off + 1);
off += 2;
}
if (flags & TTF_COMP_WE_HAVE_A_SCALE) {
int16_t s;
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
s = rd_i16(font->data, off);
off += 2;
if (s != TTF_F2DOT14_ONE) return TTF_ERR_UNSUPPORTED;
} else if (flags & TTF_COMP_WE_HAVE_XY_SCALE) {
int16_t sx, sy;
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
sx = rd_i16(font->data, off);
sy = rd_i16(font->data, off + 2);
off += 4;
if (sx != TTF_F2DOT14_ONE || sy != TTF_F2DOT14_ONE) return TTF_ERR_UNSUPPORTED;
} else if (flags & TTF_COMP_WE_HAVE_2X2) {
int16_t a, b, c, dd;
if (!in_bounds(font, off, 8)) return TTF_ERR_OUT_OF_BOUNDS;
a = rd_i16(font->data, off);
b = rd_i16(font->data, off + 2);
c = rd_i16(font->data, off + 4);
dd = rd_i16(font->data, off + 6);
off += 8;
if (a != TTF_F2DOT14_ONE || b != 0 || c != 0 || dd != TTF_F2DOT14_ONE)
return TTF_ERR_UNSUPPORTED;
}
point_base = out->point_count;
rc = decode_glyph_r(font, comp_glyph, out, depth + 1);
if (rc != TTF_OK) return rc;
{
q48_16_t dx = q48_from_i32(arg1);
q48_16_t dy = q48_from_i32(arg2);
uint32_t i;
for (i = point_base; i < out->point_count; i++) {
out->points[i].x = q48_add(out->points[i].x, dx);
out->points[i].y = q48_add(out->points[i].y, dy);
}
}
more = (flags & TTF_COMP_MORE_COMPONENTS) != 0;
}
return TTF_OK;
}
static int decode_glyph_r(const ttf_font_t *font, uint32_t glyph_index,
ttf_outline_t *out, int depth) {
ttf_glyph_header_t hdr;
int rc;
if (depth > TTF_MAX_COMPOSITE_DEPTH) return TTF_ERR_TOO_DEEP;
rc = ttf_glyph_header(font, glyph_index, &hdr);
if (rc != TTF_OK) return rc;
if (hdr.glyf_length == 0) return TTF_OK; /* empty glyph, e.g. space */
if (hdr.num_contours >= 0) return decode_simple_glyph(font, &hdr, out);
return decode_composite_glyph(font, &hdr, out, depth);
}
int ttf_glyph_outline(const ttf_font_t *font, uint32_t glyph_index, ttf_outline_t *out) {
if (!font || !out) return TTF_ERR_BAD_TABLE;
out->point_count = 0;
out->contour_count = 0;
return decode_glyph_r(font, glyph_index, out, 0);
}
/* ===========================================================================
* 4.3.7c — rasterization: quadratic Bezier flattening + even-odd scanline
* fill. No antialiasing (deferred, per this item's own "done when" clause).
*
* Fill rule: even-odd, not TrueType's native nonzero winding. Simpler to
* implement (no edge-direction bookkeeping) and produces an identical
* result to nonzero winding for the v1 glyph repertoire (§27.6.4), whose
* contours are simple (non-self-intersecting) and properly nested
* (outer contour + inner counters, e.g. 'a', 'o') -- the two rules only
* diverge on self-intersecting outlines, which no glyph here has. Not
* correct in general for an arbitrary font.
*
* Local signed Q48.16 multiply (q48_smul): the shared q48_mul()/q48_div()
* (src/starkernel/math/q48_16.c) are unsigned-only (see 4.3.7a's
* completion note) and this file routinely multiplies a negative outline
* coordinate by a non-negative scale/Bezier-blend weight -- exactly the
* signed use case q48_mul doesn't support. Reported there, not patched
* here, per this repo's rule against modifying a shared/tested module
* without being asked. All values here (glyph coordinates scaled to at
* most a few hundred pixels) stay far inside int64_t range.
* ===========================================================================
*/
#define TTF_QUAD_SEGMENTS 8 /* fixed subdivision count per quadratic curve --
* same fixed-segment-count approach as
* capsules/fabric.4th's CIRCLE/ELLIPSE (36 segs)
* and ARC (18 segs); no adaptive tessellation. */
#define TTF_RASTER_RAW_MAX_POINTS 256
#define TTF_RASTER_RAW_MAX_CONTOURS 16
#define TTF_RASTER_FLAT_MAX_POINTS (TTF_RASTER_RAW_MAX_POINTS * TTF_QUAD_SEGMENTS)
#define TTF_RASTER_FLAT_MAX_CONTOURS TTF_RASTER_RAW_MAX_CONTOURS
/* Flattened polygon vertex: pixel space, Q48.16, after scale + origin +
* the font-Y-up-to-raster-Y-down flip. */
typedef struct {
q48_16_t x, y;
} ttf_flat_point_t;
static q48_16_t q48_smul(q48_16_t a, q48_16_t b) {
return (q48_16_t) (((int64_t) a * (int64_t) b) >> 16);
}
static q48_16_t q48_mid(q48_16_t a, q48_16_t b) {
return (q48_16_t) (((int64_t) (a + b)) >> 1);
}
static void raw_midpoint(const ttf_point_t *a, const ttf_point_t *b, ttf_point_t *out) {
out->x = q48_mid(a->x, b->x);
out->y = q48_mid(a->y, b->y);
out->on_curve = 1;
}
static void to_pixel_space(q48_16_t fx, q48_16_t fy, q48_16_t scale,
q48_16_t origin_x, q48_16_t origin_y,
q48_16_t *px, q48_16_t *py) {
*px = q48_add(origin_x, q48_smul(fx, scale));
*py = q48_sub(origin_y, q48_smul(fy, scale));
}
static void flat_push(ttf_flat_point_t *pts, uint32_t *count, uint32_t max,
q48_16_t x, q48_16_t y, int *err) {
if (*err != TTF_OK) return;
if (*count >= max) { *err = TTF_ERR_TOO_MANY_POINTS; return; }
pts[*count].x = x;
pts[*count].y = y;
(*count)++;
}
/* Subdivide one quadratic Bezier (control points already in pixel space)
* into TTF_QUAD_SEGMENTS line segments. Emits the interior + final
* vertices only -- the caller's `cur` is already the start (p0). */
static void flatten_quad(ttf_flat_point_t *pts, uint32_t *count, uint32_t max,
q48_16_t p0x, q48_16_t p0y, q48_16_t p1x, q48_16_t p1y,
q48_16_t p2x, q48_16_t p2y, int *err) {
uint32_t i;
for (i = 1; i <= TTF_QUAD_SEGMENTS; i++) {
q48_16_t t = (q48_16_t) ((uint64_t) Q48_ONE * i / TTF_QUAD_SEGMENTS);
q48_16_t omt = (q48_16_t) (Q48_ONE - t);
q48_16_t w0 = q48_smul(omt, omt);
q48_16_t w1 = q48_smul(q48_smul((q48_16_t) (2ULL * Q48_ONE), omt), t);
q48_16_t w2 = q48_smul(t, t);
q48_16_t x = q48_add(q48_add(q48_smul(w0, p0x), q48_smul(w1, p1x)), q48_smul(w2, p2x));
q48_16_t y = q48_add(q48_add(q48_smul(w0, p0y), q48_smul(w1, p1y)), q48_smul(w2, p2y));
flat_push(pts, count, max, x, y, err);
}
}
/* Walk one contour (raw[begin..end] inclusive, font design units) and
* append its flattened, closed polygon to the flat point list. Handles
* the three TrueType start-point cases (first point on-curve, last point
* on-curve, or neither -- synthesized midpoint start) and implied
* on-curve points between two consecutive off-curve control points. */
static void flatten_contour(const ttf_point_t *raw, uint32_t begin, uint32_t end,
q48_16_t scale, q48_16_t origin_x, q48_16_t origin_y,
ttf_flat_point_t *flat_pts, uint32_t *flat_count, uint32_t flat_max,
int *err) {
uint32_t n = end - begin + 1;
ttf_point_t start_pt;
uint32_t walk_start, walk_count, idx, steps_done;
q48_16_t cur_px, cur_py, start_px, start_py;
if (n == 0 || *err != TTF_OK) return;
if (raw[begin].on_curve) {
start_pt = raw[begin];
walk_start = 1;
walk_count = n - 1;
} else if (raw[end].on_curve) {
start_pt = raw[end];
walk_start = 0;
walk_count = n - 1;
} else {
raw_midpoint(&raw[end], &raw[begin], &start_pt);
walk_start = 0;
walk_count = n;
}
to_pixel_space(start_pt.x, start_pt.y, scale, origin_x, origin_y, &start_px, &start_py);
cur_px = start_px;
cur_py = start_py;
flat_push(flat_pts, flat_count, flat_max, start_px, start_py, err);
idx = walk_start;
steps_done = 0;
while (steps_done < walk_count && *err == TTF_OK) {
const ttf_point_t *p = &raw[begin + idx];
if (p->on_curve) {
q48_16_t px, py;
to_pixel_space(p->x, p->y, scale, origin_x, origin_y, &px, &py);
flat_push(flat_pts, flat_count, flat_max, px, py, err);
cur_px = px;
cur_py = py;
idx = (idx + 1) % n;
steps_done++;
} else if (steps_done + 1 < walk_count) {
uint32_t idx2 = (idx + 1) % n;
const ttf_point_t *p2 = &raw[begin + idx2];
ttf_point_t end_pt;
int consumed2;
q48_16_t c1x, c1y, ex, ey;
if (p2->on_curve) {
end_pt = *p2;
consumed2 = 1;
} else {
raw_midpoint(p, p2, &end_pt);
consumed2 = 0;
}
to_pixel_space(p->x, p->y, scale, origin_x, origin_y, &c1x, &c1y);
to_pixel_space(end_pt.x, end_pt.y, scale, origin_x, origin_y, &ex, &ey);
flatten_quad(flat_pts, flat_count, flat_max, cur_px, cur_py, c1x, c1y, ex, ey, err);
cur_px = ex;
cur_py = ey;
if (consumed2) {
idx = (idx2 + 1) % n;
steps_done += 2;
} else {
idx = idx2;
steps_done += 1;
}
} else {
/* Last point in the walk is a control point -- the curve it
* starts closes the contour, ending at start_pt. */
q48_16_t c1x, c1y;
to_pixel_space(p->x, p->y, scale, origin_x, origin_y, &c1x, &c1y);
flatten_quad(flat_pts, flat_count, flat_max, cur_px, cur_py, c1x, c1y,
start_px, start_py, err);
cur_px = start_px;
cur_py = start_py;
steps_done += 1;
}
}
/* Close the polygon back to its start when the walk ended on an
* on-curve vertex that isn't the start itself -- the common case,
* not a rare fallback (a contour that ends mid-curve self-closes in
* the branch above instead). */
if (cur_px != start_px || cur_py != start_py) {
flat_push(flat_pts, flat_count, flat_max, start_px, start_py, err);
}
}
int ttf_rasterize_glyph(const ttf_font_t *font, uint32_t glyph_index,
q48_16_t scale, q48_16_t origin_x, q48_16_t origin_y,
uint8_t fill_value, ttf_bitmap_t *out) {
ttf_point_t raw_points[TTF_RASTER_RAW_MAX_POINTS];
uint16_t raw_contour_ends[TTF_RASTER_RAW_MAX_CONTOURS];
ttf_outline_t raw_outline;
ttf_flat_point_t flat_points[TTF_RASTER_FLAT_MAX_POINTS];
uint16_t flat_contour_ends[TTF_RASTER_FLAT_MAX_CONTOURS];
uint32_t flat_count = 0;
uint32_t flat_contour_count = 0;
int err = TTF_OK;
uint32_t c, begin;
int rc;
int32_t yrow;
if (!font || !out || !out->pixels) return TTF_ERR_BAD_TABLE;
raw_outline.points = raw_points;
raw_outline.max_points = TTF_RASTER_RAW_MAX_POINTS;
raw_outline.contour_ends = raw_contour_ends;
raw_outline.max_contours = TTF_RASTER_RAW_MAX_CONTOURS;
rc = ttf_glyph_outline(font, glyph_index, &raw_outline);
if (rc != TTF_OK) return rc;
if (raw_outline.contour_count == 0) return TTF_OK; /* empty glyph, e.g. space */
begin = 0;
for (c = 0; c < raw_outline.contour_count; c++) {
uint32_t end = raw_outline.contour_ends[c];
flatten_contour(raw_points, begin, end, scale, origin_x, origin_y,
flat_points, &flat_count, TTF_RASTER_FLAT_MAX_POINTS, &err);
if (err != TTF_OK) return err;
if (flat_contour_count >= TTF_RASTER_FLAT_MAX_CONTOURS) return TTF_ERR_TOO_MANY_CONTOURS;
flat_contour_ends[flat_contour_count++] = (uint16_t) (flat_count - 1);
begin = end + 1;
}
/* Even-odd scanline fill, sampled at the pixel-center Y (row + 0.5)
* so a vertex landing exactly on an integer scanline doesn't get
* double-counted -- standard convention. */
for (yrow = 0; yrow < (int32_t) out->height; yrow++) {
q48_16_t yc = q48_add((q48_16_t) ((uint64_t) yrow << 16), (q48_16_t) (Q48_ONE / 2));
q48_16_t xs[64];
uint32_t xn = 0;
uint32_t cbegin = 0;
for (c = 0; c < flat_contour_count; c++) {
uint32_t cend = flat_contour_ends[c];
uint32_t i;
for (i = cbegin; i <= cend; i++) {
uint32_t j = (i == cend) ? cbegin : i + 1;
int64_t y0q = (int64_t) flat_points[i].y;
int64_t y1q = (int64_t) flat_points[j].y;
int below0 = y0q <= (int64_t) yc;
int below1 = y1q <= (int64_t) yc;
if (below0 == below1) continue; /* edge doesn't cross this scanline */
{
int64_t x0 = (int64_t) flat_points[i].x;
int64_t x1 = (int64_t) flat_points[j].x;
int64_t dy = y1q - y0q;
int64_t dx = x1 - x0;
int64_t t_num = (int64_t) yc - y0q;
int64_t x = x0 + (dx * t_num) / dy;
if (xn < 64) xs[xn++] = (q48_16_t) x;
}
}
cbegin = cend + 1;
}
/* insertion sort -- xn is small for a glyph outline */
{
uint32_t a;
for (a = 1; a < xn; a++) {
q48_16_t key = xs[a];
int64_t keyv = (int64_t) key;
int32_t b = (int32_t) a - 1;
while (b >= 0 && (int64_t) xs[b] > keyv) {
xs[b + 1] = xs[b];
b--;
}
xs[b + 1] = key;
}
}
{
uint32_t a;
for (a = 0; a + 1 < xn; a += 2) {
int32_t xa = (int32_t) (((int64_t) xs[a]) >> 16);
int32_t xb = (int32_t) (((int64_t) xs[a + 1]) >> 16);
int32_t x;
if (xa < 0) xa = 0;
if (xb > (int32_t) out->width) xb = (int32_t) out->width;
for (x = xa; x < xb; x++) {
out->pixels[(uint32_t) yrow * out->width + (uint32_t) x] = fill_value;
}
}
}
}
return TTF_OK;
}
/* ===========================================================================
* 4.3.7d — glyph raster cache: fixed caller-owned slots, no allocation.
* ===========================================================================
*/
void ttf_raster_cache_init(ttf_raster_cache_t *cache, ttf_raster_cache_slot_t *slots,
uint32_t slot_count) {
uint32_t i;
cache->slots = slots;
cache->slot_count = slot_count;
cache->evict_next = 0;
for (i = 0; i < slot_count; i++) {
slots[i].valid = 0;
slots[i].hits = 0;
}
}
int ttf_raster_cache_get(ttf_raster_cache_t *cache, const ttf_font_t *font,
uint32_t codepoint, uint32_t size_px,
ttf_bitmap_t *out, int *was_hit) {
uint32_t i, slot_idx;
ttf_raster_cache_slot_t *slot;
int rc;
if (!cache || !font || !out) return TTF_ERR_BAD_TABLE;
if (size_px > TTF_CACHE_MAX_SIZE_PX) return TTF_ERR_UNSUPPORTED;
for (i = 0; i < cache->slot_count; i++) {
slot = &cache->slots[i];
if (slot->valid && slot->font == font && slot->codepoint == codepoint
&& slot->size_px == size_px) {
slot->hits++;
out->pixels = slot->pixels;
out->width = slot->width;
out->height = slot->height;
if (was_hit) *was_hit = 1;
return TTF_OK;
}
}
/* Miss: use a free slot if one exists, else evict round-robin. */
slot_idx = cache->slot_count;
for (i = 0; i < cache->slot_count; i++) {
if (!cache->slots[i].valid) { slot_idx = i; break; }
}
if (slot_idx == cache->slot_count) {
slot_idx = cache->evict_next;
cache->evict_next = (cache->evict_next + 1) % cache->slot_count;
}
slot = &cache->slots[slot_idx];
{
uint32_t j;
ttf_bitmap_t bmp;
q48_16_t scale;
uint32_t gid;
for (j = 0; j < TTF_CACHE_BITMAP_BYTES; j++) slot->pixels[j] = 0;
gid = ttf_codepoint_to_glyph(font, codepoint);
scale = q48_div(q48_from_u64(size_px), q48_from_u64(font->units_per_em));
bmp.pixels = slot->pixels;
bmp.width = TTF_CACHE_BITMAP_DIM;
bmp.height = TTF_CACHE_BITMAP_DIM;
rc = ttf_rasterize_glyph(font, gid, scale,
q48_from_u64(TTF_CACHE_MARGIN),
q48_from_u64(size_px + TTF_CACHE_MARGIN),
255, &bmp);
if (rc != TTF_OK) {
slot->valid = 0;
return rc;
}
slot->valid = 1;
slot->font = font;
slot->codepoint = codepoint;
slot->size_px = size_px;
slot->width = TTF_CACHE_BITMAP_DIM;
slot->height = TTF_CACHE_BITMAP_DIM;
slot->hits = 0;
}
out->pixels = slot->pixels;
out->width = slot->width;
out->height = slot->height;
if (was_hit) *was_hit = 0;
return TTF_OK;
}
/* ===========================================================================
* 4.3.7b — font data ingestion (capsule-backed, zero-copy)
* ===========================================================================
*/
#ifdef __STARKERNEL__
int ttf_load_from_capsule(
const CapsuleDirHeader *dir,
const CapsuleDesc *descs,
const CapsuleNameEntry *names,
const uint8_t *arena,
const char *capsule_name,
ttf_font_t *out) {
const CapsuleDesc *cap;
const uint8_t *payload;
CapsuleValidateResult vr;
if (!dir || !descs || !names || !arena || !capsule_name || !out)
return TTF_ERR_BAD_TABLE;
cap = capsule_find_by_name(dir, descs, names, capsule_name);
if (!cap) return TTF_ERR_TABLE_MISSING;
vr = capsule_validate(cap, arena, dir->arena_size, 1);
if (vr != CAPSULE_VALID) return TTF_ERR_BAD_TABLE;
payload = capsule_get_payload(cap, arena);
if (!payload) return TTF_ERR_BAD_TABLE;
return ttf_parse(payload, (uint32_t) cap->length, out);
}
#endif /* __STARKERNEL__ */