// TrueType outlines. See include/ttf.h for scope. // // Written from Apple's TrueType Reference Manual and the OpenType // specification. Nothing here is derived from FreeType or stb_truetype. #ifdef TTF_HOST #include #include typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned int uint32; typedef signed short int16; #else #include "types.h" #include "defs.h" #include "param.h" #endif #include "text.h" #include "ttf.h" // Every multi-byte field in an sfnt file is big-endian, whatever the machine // is. Read them byte by byte rather than casting: the file is not guaranteed // to be aligned, and on x86 a cast would silently byte-swap everything. static uint16 rd16(const uint8 *p) { return (uint16)((p[0] << 8) | p[1]); } static int16 rs16(const uint8 *p) { return (int16)rd16(p); } static uint32 rd32(const uint8 *p) { return ((uint32)p[0] << 24) | ((uint32)p[1] << 16) | ((uint32)p[2] << 8) | p[3]; } static uint32 find_table(const uint8 *d, uint32 len, const char *tag, uint32 *out_len) { if (len < 12) return 0; int n = rd16(d + 4); for (int i = 0; i < n; i++) { const uint8 *e = d + 12 + 16 * i; if ((uint32)(12 + 16 * i + 16) > len) return 0; if (e[0] == (uint8)tag[0] && e[1] == (uint8)tag[1] && e[2] == (uint8)tag[2] && e[3] == (uint8)tag[3]) { uint32 off = rd32(e + 8), sz = rd32(e + 12); if (off >= len) return 0; if (out_len) *out_len = sz; return off; } } return 0; } int ttf_open(struct ttf *f, const void *data, uint32 len, int px) { const uint8 *d = data; memset(f, 0, sizeof(*f)); f->data = d; f->len = len; f->px = px; // 0x00010000 is TrueType; 'true' is Apple's older tag. 'OTTO' is CFF // outlines, which this cannot read -- rejecting it explicitly beats failing // later with an empty glyph and no explanation. if (len < 12) return -1; uint32 ver = rd32(d); if (ver == 0x4F54544FU) { // 'OTTO' return -1; } if (ver != 0x00010000U && ver != 0x74727565U) return -1; uint32 head = find_table(d, len, "head", 0); uint32 maxp = find_table(d, len, "maxp", 0); uint32 hhea = find_table(d, len, "hhea", 0); f->hmtx = find_table(d, len, "hmtx", 0); f->glyf = find_table(d, len, "glyf", &f->glyf_len); f->loca = find_table(d, len, "loca", &f->loca_len); if (!head || !maxp || !hhea || !f->hmtx || !f->glyf || !f->loca) return -1; f->units_per_em = rd16(d + head + 18); f->long_loca = rs16(d + head + 50); f->num_glyphs = rd16(d + maxp + 4); f->ascent = rs16(d + hhea + 4); f->descent = rs16(d + hhea + 6); f->num_hmetrics = rd16(d + hhea + 34); if (f->units_per_em == 0) return -1; // cmap: prefer a Windows BMP subtable (platform 3, encoding 1), fall back to // symbol (3,0). Format 4 only -- it covers the basic multilingual plane, // which is every codepoint this system will draw for a long time. uint32 cmap = find_table(d, len, "cmap", 0); if (cmap) { int n = rd16(d + cmap + 2); for (int i = 0; i < n; i++) { const uint8 *rec = d + cmap + 4 + 8 * i; int plat = rd16(rec), enc = rd16(rec + 2); uint32 sub = cmap + rd32(rec + 4); if (sub + 4 > len) continue; if (rd16(d + sub) != 4) continue; if (plat == 3 && (enc == 1 || enc == 0)) { f->cmap4 = sub; if (enc == 1) break; // exact match; keep looking otherwise } } } return f->cmap4 ? 0 : -1; } // Codepoint to glyph index, cmap format 4. // // The format is four parallel arrays of segments plus an idDelta/idRangeOffset // pair whose second form is the awkward one: idRangeOffset is a byte offset // *from its own address* into glyphIdArray, which is why the arithmetic below // looks like it does rather than being an index. static int glyph_index(struct ttf *f, uint32 cp) { if (cp > 0xFFFF || !f->cmap4) return 0; const uint8 *t = f->data + f->cmap4; int segx2 = rd16(t + 6); const uint8 *ends = t + 14; const uint8 *starts = ends + segx2 + 2; const uint8 *deltas = starts + segx2; const uint8 *ranges = deltas + segx2; for (int i = 0; i < segx2; i += 2) { if (rd16(ends + i) < cp) continue; if (rd16(starts + i) > cp) return 0; // in a gap between segments int ro = rd16(ranges + i); if (ro == 0) return (int)((cp + rd16(deltas + i)) & 0xFFFF); const uint8 *p = ranges + i + ro + (cp - rd16(starts + i)) * 2; if (p + 1 >= f->data + f->len) return 0; int g = rd16(p); return g ? (int)((g + rd16(deltas + i)) & 0xFFFF) : 0; } return 0; } static int advance_for(struct ttf *f, int gid) { // hmtx holds numberOfHMetrics full entries, then only left side bearings -- // monospaced tails are stored once. Clamping is the whole trick. int i = gid < f->num_hmetrics ? gid : f->num_hmetrics - 1; if (i < 0) return 0; return rd16(f->data + f->hmtx + 4 * i); } // --- outline extraction ---------------------------------------------------- #define MAXPTS 512 #define MAXCONT 32 struct outline { int x[MAXPTS], y[MAXPTS]; // font units uint8 on[MAXPTS]; // on-curve flag int ends[MAXCONT]; int npts, ncont; }; static int load_outline(struct ttf *f, int gid, struct outline *o) { uint32 g0, g1; if (gid < 0 || gid >= f->num_glyphs) return -1; if (f->long_loca) { g0 = rd32(f->data + f->loca + 4 * gid); g1 = rd32(f->data + f->loca + 4 * gid + 4); } else { g0 = (uint32)rd16(f->data + f->loca + 2 * gid) * 2; g1 = (uint32)rd16(f->data + f->loca + 2 * gid + 2) * 2; } o->npts = o->ncont = 0; if (g0 >= g1) return 0; // empty glyph, e.g. space -- not an error const uint8 *g = f->data + f->glyf + g0; int nc = rs16(g); // A negative contour count means a composite: the glyph is other glyphs // placed at offsets. This is not an exotic case to skip -- `i` is built from // a dotless i plus the dot, so skipping composites loses a letter from // "Schunix", which is exactly how the gap was found. // // Only the translated case is handled: components with a scale or a 2x2 // matrix are rare outside of synthesised obliques, and applying a transform // wrongly is worse than not applying one. if (nc < 0) { const uint8 *p = g + 10; int flags; do { flags = rd16(p); int cgid = rd16(p + 2); p += 4; int dx, dy; if (flags & 0x0001) { // ARG_1_AND_2_ARE_WORDS dx = rs16(p); dy = rs16(p + 2); p += 4; } else { dx = (signed char)p[0]; dy = (signed char)p[1]; p += 2; } if (!(flags & 0x0002)) // ARGS_ARE_XY_VALUES dx = dy = 0; // point matching: not supported if (flags & 0x0008) p += 2; // WE_HAVE_A_SCALE else if (flags & 0x0040) p += 4; // X_AND_Y_SCALE else if (flags & 0x0080) p += 8; // TWO_BY_TWO // Recurse, then merge the component's contours into ours with the offset // applied. Depth is bounded by the recursion into load_outline, which // rejects anything that would overflow the point arrays. struct outline sub; if (load_outline(f, cgid, &sub) != 0) continue; if (o->npts + sub.npts > MAXPTS || o->ncont + sub.ncont > MAXCONT) break; for (int i = 0; i < sub.npts; i++) { o->x[o->npts + i] = sub.x[i] + dx; o->y[o->npts + i] = sub.y[i] + dy; o->on[o->npts + i] = sub.on[i]; } for (int i = 0; i < sub.ncont; i++) o->ends[o->ncont + i] = sub.ends[i] + o->npts; o->npts += sub.npts; o->ncont += sub.ncont; } while (flags & 0x0020); // MORE_COMPONENTS return 0; } if (nc > MAXCONT) return -1; o->ncont = nc; const uint8 *p = g + 10; for (int i = 0; i < nc; i++) { o->ends[i] = rd16(p); p += 2; } int npts = nc ? o->ends[nc - 1] + 1 : 0; if (npts > MAXPTS) return -1; o->npts = npts; p += 2 + rd16(p); // skip the hinting instructions // Flags are run-length encoded: bit 3 means "the next byte is a repeat // count". Missing that turns every glyph with a repeated flag into garbage. uint8 flags[MAXPTS]; for (int i = 0; i < npts; ) { uint8 fl = *p++; flags[i++] = fl; if (fl & 8) { int rep = *p++; while (rep-- > 0 && i < npts) flags[i++] = fl; } } // Coordinates are deltas, and each axis is stored in one of three ways // depending on two flag bits: 8-bit with a separate sign bit, or 16-bit // signed, or "same as the previous point" which occupies no bytes at all. int v = 0; for (int i = 0; i < npts; i++) { if (flags[i] & 2) { int dv = *p++; v += (flags[i] & 16) ? dv : -dv; } else if (!(flags[i] & 16)) { v += rs16(p); p += 2; } o->x[i] = v; o->on[i] = (flags[i] & 1) != 0; } v = 0; for (int i = 0; i < npts; i++) { if (flags[i] & 4) { int dv = *p++; v += (flags[i] & 32) ? dv : -dv; } else if (!(flags[i] & 32)) { v += rs16(p); p += 2; } o->y[i] = v; } return 0; } // --- rasterising ----------------------------------------------------------- // // Scanline fill with the non-zero winding rule, four sub-scanlines per output // row. Coverage is accumulated in fixed point per pixel, so a horizontal span // ending part-way through a pixel contributes a fraction rather than being // rounded -- which is where anti-aliasing actually comes from. Vertical // smoothing comes from the four samples. #define SUBS 4 struct edge { int x0, y0, x1, y1; }; // in 1/64 pixel #define MAXEDGE 1024 struct raster { struct edge e[MAXEDGE]; int n; }; static void add_edge(struct raster *r, int x0, int y0, int x1, int y1) { if (y0 == y1 || r->n >= MAXEDGE) return; // horizontal edges contribute no crossings r->e[r->n].x0 = x0; r->e[r->n].y0 = y0; r->e[r->n].x1 = x1; r->e[r->n].y1 = y1; r->n++; } // Flatten a quadratic bézier into line segments. // // Fixed subdivision rather than adaptive. At text sizes the control polygon is // a few pixels across, so eight segments is already below the point where the // difference is visible, and an adaptive version would spend more time deciding // than drawing. static void quad_to(struct raster *r, int x0, int y0, int cx, int cy, int x1, int y1) { const int N = 8; int px = x0, py = y0; for (int i = 1; i <= N; i++) { int t = (i * 256) / N, u = 256 - t; // (u*u*p0 + 2*u*t*c + t*t*p1) / 65536 int nx = (u * u * x0 + 2 * u * t * cx + t * t * x1) >> 16; int ny = (u * u * y0 + 2 * u * t * cy + t * t * y1) >> 16; add_edge(r, px, py, nx, ny); px = nx; py = ny; } } int ttf_render(struct ttf *f, uint32 cp, struct glyph *out, unsigned char *cov, int covcap, int *ox, int *oy) { struct outline o; struct raster r; int gid = glyph_index(f, cp); if (gid == 0 && cp != 0) return -1; if (load_outline(f, gid, &o) != 0) return -1; // Font units to 1/64 pixel. int upem = f->units_per_em; #define SX(v) (((v) * f->px * 64) / upem) int minx = 0, miny = 0, maxx = 0, maxy = 0; if (o.npts) { minx = maxx = SX(o.x[0]); miny = maxy = SX(o.y[0]); for (int i = 1; i < o.npts; i++) { int X = SX(o.x[i]), Y = SX(o.y[i]); if (X < minx) minx = X; if (X > maxx) maxx = X; if (Y < miny) miny = Y; if (Y > maxy) maxy = Y; } } int x0 = minx >> 6, y0 = miny >> 6; int w = ((maxx + 63) >> 6) - x0 + 1; int h = ((maxy + 63) >> 6) - y0 + 1; if (w < 1) w = 1; if (h < 1) h = 1; if (w * h > covcap) return -1; memset(cov, 0, (uint32)(w * h)); // Build the edge list, contour by contour. Off-curve points imply an // on-curve point midway between consecutive off-curve neighbours -- TrueType // omits those, and a renderer that does not synthesise them draws corners // where the font has curves. r.n = 0; int start = 0; for (int c = 0; c < o.ncont; c++) { int end = o.ends[c]; int n = end - start + 1; if (n < 2) { start = end + 1; continue; } int px = 0, py = 0, have = 0, fx = 0, fy = 0; int cx = 0, cy = 0, havec = 0; for (int k = 0; k <= n; k++) { int i = start + (k % n); int X = SX(o.x[i]) - (x0 << 6), Y = SX(o.y[i]) - (y0 << 6); if (o.on[i]) { if (!have) { fx = px = X; fy = py = Y; have = 1; } else if (havec) { quad_to(&r, px, py, cx, cy, X, Y); px = X; py = Y; havec = 0; } else { add_edge(&r, px, py, X, Y); px = X; py = Y; } } else { if (havec) { int mx = (cx + X) / 2, my = (cy + Y) / 2; // implied on-curve point if (!have) { fx = px = mx; fy = py = my; have = 1; } else { quad_to(&r, px, py, cx, cy, mx, my); px = mx; py = my; } } cx = X; cy = Y; havec = 1; } } if (have) { if (havec) quad_to(&r, px, py, cx, cy, fx, fy); else add_edge(&r, px, py, fx, fy); } start = end + 1; } // Fill. Y grows upward in font space and downward in the bitmap, so the // sample line is mirrored as it is computed. for (int row = 0; row < h; row++) { for (int s = 0; s < SUBS; s++) { int sy = ((h - 1 - row) << 6) + (s * 64) / SUBS + 32 / SUBS; int xs[64], nx = 0, dir[64]; for (int i = 0; i < r.n && nx < 64; i++) { struct edge *e = &r.e[i]; int ya = e->y0, yb = e->y1, d = 1; if (ya > yb) { int t = ya; ya = yb; yb = t; d = -1; } if (sy < ya || sy >= yb) continue; int t = ((sy - e->y0) * 1024) / (e->y1 - e->y0); xs[nx] = e->x0 + ((e->x1 - e->x0) * t) / 1024; dir[nx] = d; nx++; } // Insertion sort: nx is small and this keeps the direction paired. for (int i = 1; i < nx; i++) { int vx = xs[i], vd = dir[i], j = i - 1; while (j >= 0 && xs[j] > vx) { xs[j+1] = xs[j]; dir[j+1] = dir[j]; j--; } xs[j+1] = vx; dir[j+1] = vd; } int wind = 0; for (int i = 0; i + 1 < nx; i++) { wind += dir[i]; if (wind == 0) continue; // non-zero winding int xa = xs[i], xb = xs[i + 1]; for (int col = xa >> 6; col <= (xb >> 6) && col < w; col++) { if (col < 0) continue; // Fraction of this pixel the span covers, in 1/64ths. int l = xa > (col << 6) ? xa : (col << 6); int rr = xb < ((col + 1) << 6) ? xb : ((col + 1) << 6); if (rr <= l) continue; int add = ((rr - l) * 255) / (64 * SUBS); int v = cov[row * w + col] + add; cov[row * w + col] = (unsigned char)(v > 255 ? 255 : v); } } } } out->cov = cov; out->w = w; out->h = h; out->bearing_x = x0; out->bearing_y = (maxy + 63) >> 6; out->advance = (advance_for(f, gid) * f->px + upem / 2) / upem; if (ox) *ox = x0; if (oy) *oy = y0; #undef SX return 0; } // --- font provider --------------------------------------------------------- // --- glyph cache ----------------------------------------------------------- // // Rasterising is not cheap -- it walks the outline, flattens every curve and // fills four sub-scanlines per row -- and a terminal draws the same hundred // glyphs over and over. Without a cache every character costs that again, and // worse: the single scratch buffer meant a glyph was only valid until the next // call, so a caller could not hold two at once. // // Direct-mapped over printable ASCII, which is the case that matters and needs // no eviction policy, no hashing and no allocation. Anything outside that range // still renders, just uncached, through the scratch buffer. // // The pool is filled linearly and never freed. A cache that can only fill up is // the right shape here because the contents are pure functions of (codepoint, // size) -- nothing ever becomes stale -- and the only event that invalidates it // is a size change, which drops the whole thing. #define CACHE_LO 32 // space #define CACHE_HI 127 // exclusive #define CACHE_N (CACHE_HI - CACHE_LO) #define CACHE_POOL (48 * 1024) struct gcache { struct glyph g; uint32 off; // into pool, valid only when `have` int have; }; static struct gcache cache[CACHE_N]; static unsigned char cache_pool[CACHE_POOL]; static uint32 cache_used; static int cache_px; // size the cache was built at // Scratch for anything the cache cannot hold. Same contract as before -- valid // until the next call -- and now only used off the common path. static unsigned char ttf_cov[128 * 128]; static int ttf_glyph_op(struct font *fo, uint32 cp, struct glyph *out) { struct ttf *f = fo->priv; // A size change makes every cached bitmap wrong. Dropping the lot is correct // and costs nothing: it happens when a font is resized, not per glyph. if (cache_px != f->px) { for (int i = 0; i < CACHE_N; i++) cache[i].have = 0; cache_used = 0; cache_px = f->px; } if (cp < CACHE_LO || cp >= CACHE_HI) return ttf_render(f, cp, out, ttf_cov, (int)sizeof(ttf_cov), 0, 0); struct gcache *c = &cache[cp - CACHE_LO]; if (c->have) { *out = c->g; out->cov = cache_pool + c->off; // rebind: the pool may have moved in return 0; // a future revision, the offset has not } struct glyph g; if (ttf_render(f, cp, &g, ttf_cov, (int)sizeof(ttf_cov), 0, 0) != 0) return -1; uint32 need = (uint32)(g.w * g.h); if (cache_used + need <= CACHE_POOL) { memmove(cache_pool + cache_used, g.cov, need); c->off = cache_used; c->g = g; c->g.cov = cache_pool + cache_used; // not the scratch buffer c->have = 1; cache_used += need; *out = c->g; return 0; } // Pool exhausted. Still correct, just uncached -- and the glyph is in the // scratch buffer, which is exactly the old contract. *out = g; return 0; } static int ttf_height_op(struct font *fo) { struct ttf *f = fo->priv; return ((f->ascent - f->descent) * f->px + f->units_per_em / 2) / f->units_per_em; } static int ttf_baseline_op(struct font *fo) { struct ttf *f = fo->priv; return (f->ascent * f->px + f->units_per_em / 2) / f->units_per_em; } static const struct font_ops ttf_ops = { .glyph = ttf_glyph_op, .height = ttf_height_op, .baseline = ttf_baseline_op, }; static struct font ttf_font_obj; struct font * ttf_font(struct ttf *f) { ttf_font_obj.ops = &ttf_ops; ttf_font_obj.priv = f; return &ttf_font_obj; }