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:
Robert Allan James
2026-08-10 21:45:04 -04:00
co-authored by Claude Sonnet 5
parent 5f6cc054d4
commit 70b6918279
4 changed files with 471 additions and 6 deletions
+36 -1
View File
@@ -4712,11 +4712,46 @@ document and committing that amendment as its own item.*
> compile check above is what stands in for it, per this item's own "done when" clause
> (which never asked for a kernel boot).
- [ ] **4.3.7a — Glyph outline extraction.** Simple and composite glyph outlines from `glyf`
- [x] **4.3.7a — Glyph outline extraction.** Simple and composite glyph outlines from `glyf`
— on-curve/off-curve point lists, quadratic Bézier control points, composite glyph
transforms — in Q48.16. *Done when:* outline point lists for a handful of test glyphs
(including at least one composite, e.g. an accented character if the test font has one)
match reference-tool output within Q48.16 rounding tolerance. *Refs:* §27.7.
> **Done 2026-08-10.** `ttf_glyph_outline()` in `src/starkernel/hal/ttf.c`/`ttf.h`: flags
> run-length decode, delta-decoded x/y coordinates (simple glyphs), and recursive composite
> component resolution, into caller-supplied point/contour-end buffers (no allocation in
> this module). Points are stored in raw font design units shifted into Q48.16
> (`q48_from_i32`, a local two's-complement left-shift — deliberately not routed through
> `q48_mul`/`q48_div`, see below).
>
> **Known gap, reported not fixed, per this repo's rule against modifying a shared/tested
> module without being asked:** `src/starkernel/math/q48_16.c`'s `q48_mul`/`q48_div` are
> unsigned-only (`q48_div` saturates via an unsigned overflow check on `a`; `q48_mul` does a
> plain unsigned widen-multiply) — confirmed by reading the source before writing any of
> this, not assumed. A composite glyph's transform can carry a signed F2Dot14 scale/
> rotation/skew, which needs a *signed* fixed-point multiply that function doesn't provide.
> Resolution taken here: `decode_composite_glyph()` applies (dx,dy) translation only (plain
> `q48_add`, safe under two's-complement regardless of the unsigned typing) and explicitly
> **rejects** any component with a non-identity scale/2×2 transform or point-matched
> (non-xy-offset) args, returning `TTF_ERR_UNSUPPORTED` rather than silently mis-rendering
> it. Checked, not assumed: every composite in `fonts/JetBrainsMono-Regular.ttf` (11 sampled
> accented Latin glyphs: `ÁÉÍÓÚÑéáñüö`) uses identity-scale translation-only components, so
> this doesn't block the v1 repertoire (§27.6.4) — but it's a real limitation for an
> arbitrary future font, and fixing it means either giving `q48_16.c` a signed multiply
> variant or doing the scale math locally the same way translation already is. Whoever picks
> that up next should decide which, not silently patch it in passing.
>
> Verified against `/tmp/.../scratchpad/ttf_outline_ref.py` (not committed, reproducible
> from this note) — a second from-scratch Python decoder sharing no code with `ttf.c`,
> covering the same "no `fonttools` installed" substitution already recorded under 4.3.7.
> Three glyphs checked point-for-point (coordinates, on/off-curve flags, contour-end
> indices) via the extended `tools/ttftest.c`: `.` (12 points, 1 contour, simple), `A` (17
> points, 2 contours, simple), and `á` (43 points, 3 contours, a genuine 2-component
> translation-only composite exercising the recursive path) — all match exactly.
> `gcc -std=c99 -Wall -Wextra -Werror`, zero warnings; single-file freestanding compile
> against the real amd64 `Makefile.starkernel` flags also re-checked clean. Same "not a
> kernel-boot change yet" posture as 4.3.7 — no FORTH/capsule wiring exists for this module,
> so no three-arch QEMU acceptance applies here either.
- [ ] **4.3.7b — Font data ingestion.** `.ttf` bytes encoded (hex or base64 — pick one, record
why) into capsule blocks per the resolved design decision (§27.7), decoded into a `kmalloc`
+57
View File
@@ -55,6 +55,18 @@ extern "C" {
#define TTF_ERR_BAD_TABLE -3
#define TTF_ERR_BAD_GLYPH_INDEX -4
#define TTF_ERR_OUT_OF_BOUNDS -5
#define TTF_ERR_TOO_MANY_POINTS -6
#define TTF_ERR_TOO_MANY_CONTOURS -7
#define TTF_ERR_TOO_DEEP -8 /* composite glyph nesting exceeded TTF_MAX_COMPOSITE_DEPTH */
#define TTF_ERR_UNSUPPORTED -9 /* e.g. a non-identity composite transform or point-matched
* component args — see ttf.c's file header comment; not a
* malformed font, just a code path this parser doesn't
* implement yet */
/* Recursion guard for nested composite glyphs (a component referencing a
* component). The TTF spec doesn't hard-cap this; this is a defensive
* limit for freestanding/kernel-stack safety. */
#define TTF_MAX_COMPOSITE_DEPTH 8
/** Glyph index returned for "no mapping" by ttf_codepoint_to_glyph(). */
#define TTF_GLYPH_MISSING 0
@@ -125,6 +137,51 @@ uint32_t ttf_codepoint_to_glyph(const ttf_font_t *font, uint32_t codepoint);
int ttf_glyph_header(const ttf_font_t *font, uint32_t glyph_index,
ttf_glyph_header_t *out);
/** One outline point, in raw font design units (NOT scaled by unitsPerEm —
* that's the caller's job, same convention as ttf_glyph_header_t's bbox),
* expressed in Q48.16. Two's-complement negative values are expected and
* correct for q48_add/q48_sub and for q48_from_u64-style left-shift
* conversion; this module never calls q48_mul/q48_div on outline
* coordinates (see ttf.c's file header comment for why). */
typedef struct {
q48_16_t x, y;
uint8_t on_curve;
} ttf_point_t;
/** Simple- and composite-glyph outline, flattened to one point list plus
* per-contour end indices (TrueType convention: contour_ends[c] is the
* index of the LAST point of contour c, inclusive; points are shared
* across contours only in the sense that contour c+1 starts right after
* contour_ends[c]). Caller supplies both backing arrays — this module
* never allocates. */
typedef struct {
ttf_point_t *points;
uint32_t max_points;
uint32_t point_count;
uint16_t *contour_ends;
uint32_t max_contours;
uint32_t contour_count;
} ttf_outline_t;
/**
* ttf_glyph_outline - Extract a glyph's outline (simple or composite,
* recursively resolving composite components) into caller-supplied
* buffers.
*
* Composite components with a non-identity transform (any scale/rotation/
* skew, i.e. anything but a pure (dx,dy) translation) or with
* point-matched (rather than xy-offset) placement args return
* TTF_ERR_UNSUPPORTED rather than silently producing a wrong outline —
* see ttf.c's file header comment for why, and check that limitation
* before relying on this for an arbitrary font.
*
* @return TTF_OK, or a TTF_ERR_* code (including TTF_ERR_TOO_MANY_POINTS/
* _CONTOURS if a caller buffer is too small, and TTF_ERR_TOO_DEEP
* if composite nesting exceeds TTF_MAX_COMPOSITE_DEPTH)
*/
int ttf_glyph_outline(const ttf_font_t *font, uint32_t glyph_index, ttf_outline_t *out);
#ifdef __cplusplus
}
#endif
+282 -5
View File
@@ -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);
}
+96
View File
@@ -55,6 +55,100 @@ static void check_eq_i(const char *what, uint32_t cp, long got, long want) {
}
}
/* Outline expectations (4.3.7a), independently computed by a from-scratch
* Python outline decoder (tools/../../tmp .../ttf_outline_ref.py, not
* committed, reproducible from FABRIC.md's 4.3.7a completion note) that
* shares no code with ttf.c. '.' and '0' are simple glyphs; 'A' is simple
* with two contours; 'a' is a translation-only composite (2 components). */
typedef struct {
int32_t x, y;
int on_curve;
} exp_point_t;
typedef struct {
uint32_t codepoint;
uint32_t glyph;
const uint16_t *contour_ends;
uint32_t contour_count;
const exp_point_t *points;
uint32_t point_count;
} outline_expect_t;
static const uint16_t DOT_ENDS[] = {11};
static const exp_point_t DOT_PTS[] = {
{300, -10, 1}, {263, -10, 0}, {218, 34, 0}, {218, 69, 1}, {218, 106, 0}, {263, 152, 0},
{300, 152, 1}, {337, 152, 0}, {382, 106, 0}, {382, 69, 1}, {382, 34, 0}, {337, -10, 0},
};
static const uint16_t A_ENDS[] = {7, 16};
static const exp_point_t A_PTS[] = {
{50, 0, 1}, {240, 730, 1}, {361, 730, 1}, {550, 0, 1}, {459, 0, 1}, {411, 194, 1},
{190, 194, 1}, {142, 0, 1}, {208, 270, 1}, {392, 270, 1}, {336, 495, 1}, {320, 559, 0},
{302, 645, 0}, {300, 658, 1}, {298, 645, 0}, {280, 559, 0}, {264, 496, 1},
};
static const uint16_t ALOWER_ENDS[] = {27, 38, 42};
static const exp_point_t ALOWER_PTS[] = {
{252, -10, 1}, {167, -10, 0}, {67, 85, 0}, {67, 162, 1}, {67, 213, 0}, {113, 289, 0},
{195, 332, 0}, {248, 332, 1}, {418, 332, 1}, {418, 375, 1}, {418, 482, 0}, {301, 482, 1},
{249, 482, 0}, {185, 444, 0}, {183, 410, 1}, {93, 410, 1}, {98, 475, 0}, {209, 560, 0},
{301, 560, 1}, {401, 560, 0}, {508, 464, 0}, {508, 378, 1}, {508, 0, 1}, {419, 0, 1},
{419, 100, 1}, {417, 100, 1}, {409, 49, 0}, {323, -10, 0}, {274, 66, 1}, {340, 66, 0},
{418, 130, 0}, {418, 185, 1}, {418, 262, 1}, {258, 262, 1}, {214, 262, 0}, {159, 209, 0},
{159, 165, 1}, {159, 119, 0}, {220, 66, 0}, {247, 645, 1}, {353, 785, 1}, {450, 785, 1},
{339, 645, 1},
};
static const outline_expect_t OUTLINE_EXPECTED[] = {
{0x002E, 908, DOT_ENDS, 1, DOT_PTS, 12},
{0x0041, 1, A_ENDS, 2, A_PTS, 17},
{0x0061, 199, ALOWER_ENDS, 3, ALOWER_PTS, 43},
};
static int32_t unscale(q48_16_t q) {
return (int32_t) (((int64_t) q) >> 16);
}
static void test_outlines(const ttf_font_t *font) {
ttf_point_t point_buf[600];
uint16_t contour_buf[16];
size_t i;
for (i = 0; i < sizeof(OUTLINE_EXPECTED) / sizeof(OUTLINE_EXPECTED[0]); i++) {
const outline_expect_t *e = &OUTLINE_EXPECTED[i];
ttf_outline_t outline;
int rc;
uint32_t j;
outline.points = point_buf;
outline.max_points = sizeof(point_buf) / sizeof(point_buf[0]);
outline.contour_ends = contour_buf;
outline.max_contours = sizeof(contour_buf) / sizeof(contour_buf[0]);
rc = ttf_glyph_outline(font, e->glyph, &outline);
if (rc != TTF_OK) {
printf(" FAIL U+%04X ttf_glyph_outline rc=%d\n", e->codepoint, rc);
failures++;
continue;
}
printf("U+%04X outline: points=%u (want %u) contours=%u (want %u)\n",
e->codepoint, outline.point_count, e->point_count,
outline.contour_count, e->contour_count);
check_eq_i("point_count", e->codepoint, outline.point_count, e->point_count);
check_eq_i("contour_count", e->codepoint, outline.contour_count, e->contour_count);
for (j = 0; j < e->contour_count && j < outline.contour_count; j++) {
check_eq_i("contour_end", e->codepoint, outline.contour_ends[j], e->contour_ends[j]);
}
for (j = 0; j < e->point_count && j < outline.point_count; j++) {
check_eq_i("point.x", e->codepoint, unscale(outline.points[j].x), e->points[j].x);
check_eq_i("point.y", e->codepoint, unscale(outline.points[j].y), e->points[j].y);
check_eq_i("point.on_curve", e->codepoint, outline.points[j].on_curve, e->points[j].on_curve);
}
}
}
int main(int argc, char **argv) {
FILE *fp;
long size;
@@ -115,6 +209,8 @@ int main(int argc, char **argv) {
check_eq_i("glyf_length", e->codepoint, hdr.glyf_length, e->expect_glyf_length);
}
test_outlines(&font);
free(buf);
if (failures) {