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>
967 lines
36 KiB
C
967 lines
36 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.
|
||
*/
|
||
|
||
/**
|
||
* 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__ */ |