ttf.c: glyph outline extraction, simple and composite (item 4.3.7a)
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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5f6cc054d4
commit
70b6918279
+282
-5
@@ -8,12 +8,28 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* ttf.c — TrueType font parser core (FABRIC.md item 4.3.7)
|
||||
* ttf.c — TrueType font parser core (FABRIC.md items 4.3.7, 4.3.7a)
|
||||
*
|
||||
* 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.
|
||||
* 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.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"
|
||||
@@ -244,4 +260,265 @@ int ttf_glyph_header(const ttf_font_t *font, uint32_t glyph_index, ttf_glyph_hea
|
||||
out->y_max = rd_i16(font->data, out->glyf_offset + 8);
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* 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);
|
||||
}
|
||||
Reference in New Issue
Block a user