"Ą Ę Ś Ł Ż" drew as "?????" and the obvious diagnosis -- the font has no glyphs for them -- was wrong twice over. `struct vtcell.ch` was **one byte** holding a CP437 index, and the console mapped UTF-8 down to it on the way in. U+0104 does not fit in a byte. No font could have fixed that; the cell had to get wider first. So: `ch` is a uint32 codepoint, the mapping on the way in is gone, and the cell draws through kernel/dev/ttf.c against JetBrains Mono linked into the image. Cell size comes from the font's measured advance instead of a hardcoded 8x16. The grid costs 864 KB of BSS instead of 432 KB, which is the honest price of holding a codepoint per cell for six terminals at 240x75. The rasteriser needed three fixes before it could be trusted with this. **21 KB of locals on a 4 KB kernel stack.** `ttf_render` held an outline (512 points as three arrays) and a raster (1024 edges) as automatics. KSTACKSIZE is 4096. They walked off the bottom of the stack into whatever was underneath, which surfaced as user processes faulting on addresses they never touched and then a panic inside freevm() on an overwritten page directory. Nothing pointed anywhere near a font. **And then again, recursively.** load_outline() held a 4736-byte outline as a local *and recursed* for composite glyphs. Composite glyphs are exactly Ą, Ę, Ś, Ł, Ż -- so the second crash arrived the moment the first fix let Unicode reach the screen, looking like a fresh bug. Now a static outline per recursion level with a depth cap of 4 (the font's deepest real nesting is 2), and the leaf parse split into load_simple() so its 512-byte flag array is not multiplied by the depth. Worst chain measured from the object file: ~2.3 KB. Neither showed in tools/ttftest, which has an 8 MB stack. That is the standing hazard of one source file built for host and kernel: the host is most forgiving about exactly the resource the kernel is strictest about. **Component transforms were being skipped**, on the stated theory that a wrong transform is worse than none. It is not. `‘` in this font is the comma glyph with a scale of -1 -- a 180-degree rotation -- and the offset that ships with it only lands correctly after the rotation. Without it the mark went twelve pixels into a ten-pixel cell and was clipped away entirely, so the character read as missing from the font rather than as misplaced. F2Dot14 2x2 now, with SCALED_COMPONENT_OFFSET honoured. The glyph cache was ASCII-only, so every accented letter re-rasterised on every repaint: a screenful of Polish did hundreds of bezier flattenings per scroll where the same screenful of English did none. 95 reserved ASCII slots plus 224 shared slots keyed by codepoint, 64 KB pool. Verified 13680 cached renders byte-identical to direct ones across collisions and a pool flush. tools/ttftest speaks UTF-8 now. Iterating bytes meant it could only ever ask for ASCII, and ASCII is entirely simple glyphs -- it could not reach the code path that mattered. Also: resolvconf's success line moves behind /proc/bootmode, same rule as vgpud. A quiet boot opened with "resolvconf: nameserver 10.0.2.3" where the one line was supposed to be "Welcome to SchOS!". Failures stay loud. Verified on screen, not just on the host: Ą Ę Ś Ł Ż ć ń ó ź, Ω λ π Œ Ð Æ ß, ∞ ‘x’ → ≤ all render, panic=0 across four boots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
731 lines
24 KiB
C
731 lines
24 KiB
C
// 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 <string.h>
|
|
#include <stdlib.h>
|
|
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;
|
|
};
|
|
|
|
// The simple (non-composite) glyph body, split out from load_outline so that
|
|
// its flags[] array -- 512 bytes -- lives only in the leaf frame. Composite
|
|
// glyphs recurse, and a half-kilobyte local multiplied by the recursion depth
|
|
// is not affordable on a 4 KB kernel stack.
|
|
static int
|
|
load_simple(const uint8 *g, int nc, struct outline *o)
|
|
{
|
|
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;
|
|
}
|
|
|
|
#define F2DOT14_ONE 16384
|
|
|
|
// One row of a 2x2 F2Dot14 transform applied to a point, rounded to nearest
|
|
// rather than truncated -- at 16 px a font unit is a sixteenth of a pixel, and
|
|
// truncating every component point biases the whole mark down and left by
|
|
// enough to see.
|
|
//
|
|
// long, not int: a coordinate can reach the em square's bounds and a matrix
|
|
// entry reach two, so each product is comfortably inside 32 bits but the sum
|
|
// of two is not guaranteed to be.
|
|
static int
|
|
f2d14(int m0, int v0, int m1, int v1)
|
|
{
|
|
long acc = (long)m0 * v0 + (long)m1 * v1;
|
|
return (int)((acc + (acc >= 0 ? F2DOT14_ONE / 2 : -(F2DOT14_ONE / 2)))
|
|
/ F2DOT14_ONE);
|
|
}
|
|
|
|
// Composite glyphs nest: an accented letter is a base plus a mark, and the
|
|
// mark can itself be a composite. Real fonts stay within two or three levels,
|
|
// so cap it and refuse rather than let a malformed or hostile font drive the
|
|
// recursion arbitrarily deep.
|
|
#define MAXDEPTH 4
|
|
|
|
// One scratch outline per recursion level, allocated once. As a local this was
|
|
// 4736 bytes per frame in a recursive function, which overflowed the kernel
|
|
// stack the moment anything composite was drawn -- and composites are exactly
|
|
// A-ogonek, E-ogonek, S-acute and the rest of the Polish alphabet, so the
|
|
// crash only appeared once non-ASCII text reached the console.
|
|
//
|
|
// Static for the same reason and under the same rule as the buffers in
|
|
// ttf_render: every caller is serialised behind cons.lock.
|
|
static struct outline compose[MAXDEPTH];
|
|
|
|
static int
|
|
load_outline_at(struct ttf *f, int gid, struct outline *o, int depth)
|
|
{
|
|
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
|
|
// "SchOS", which is exactly how the gap was found.
|
|
//
|
|
// The component transform is applied, not ignored. An earlier version read
|
|
// the offset and stepped over the scale, on the theory that a wrong
|
|
// transform is worse than none. It is not: a left single quote in this font
|
|
// is the comma glyph with a scale of -1, which is a 180-degree rotation, and
|
|
// the offset that goes with it only lands in the right place once the
|
|
// rotation has happened. Dropping the scale put the mark twelve pixels into
|
|
// a ten-pixel cell -- clipped away entirely, so the character read as
|
|
// missing from the font rather than as misplaced.
|
|
if (nc < 0) {
|
|
if (depth >= MAXDEPTH)
|
|
return -1;
|
|
struct outline *sub = &compose[depth];
|
|
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
|
|
|
|
// The 2x2 in F2Dot14: a fixed-point format with two integer bits and
|
|
// fourteen fractional, so the unit matrix is 16384 and -1 is -16384.
|
|
// Identity unless the component says otherwise.
|
|
int a = F2DOT14_ONE, b = 0, c = 0, d = F2DOT14_ONE;
|
|
if (flags & 0x0008) { // WE_HAVE_A_SCALE
|
|
a = d = rs16(p); p += 2;
|
|
} else if (flags & 0x0040) { // X_AND_Y_SCALE
|
|
a = rs16(p); d = rs16(p + 2); p += 4;
|
|
} else if (flags & 0x0080) { // TWO_BY_TWO
|
|
a = rs16(p); b = rs16(p + 2);
|
|
c = rs16(p + 4); d = rs16(p + 6); p += 8;
|
|
}
|
|
|
|
// Whether the offset is itself transformed. Neither bit set means
|
|
// unscaled, which is what Microsoft's rasteriser does and what every
|
|
// font in practice is drawn against.
|
|
if (flags & 0x0800) { // SCALED_COMPONENT_OFFSET
|
|
int tx = dx, ty = dy;
|
|
dx = f2d14(a, tx, c, ty);
|
|
dy = f2d14(b, tx, d, ty);
|
|
}
|
|
|
|
// Recurse into the next level's scratch outline, then merge its contours
|
|
// into ours with the transform applied. `sub` is only read back here,
|
|
// and the callee only ever touches levels deeper than this one, so the
|
|
// two never alias.
|
|
if (load_outline_at(f, cgid, sub, depth + 1) != 0)
|
|
continue;
|
|
if (o->npts + sub->npts > MAXPTS || o->ncont + sub->ncont > MAXCONT)
|
|
break;
|
|
for (int i = 0; i < sub->npts; i++) {
|
|
int X = sub->x[i], Y = sub->y[i];
|
|
o->x[o->npts + i] = f2d14(a, X, c, Y) + dx;
|
|
o->y[o->npts + i] = f2d14(b, X, d, Y) + 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;
|
|
}
|
|
|
|
return load_simple(g, nc, o);
|
|
}
|
|
|
|
static int
|
|
load_outline(struct ttf *f, int gid, struct outline *o)
|
|
{
|
|
return load_outline_at(f, gid, o, 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)
|
|
{
|
|
// **Static, not automatic.** Together these are about 21 KB -- an outline
|
|
// holds 512 points as three arrays, and a raster holds 1024 edges -- and
|
|
// KSTACKSIZE is 4096. As locals they walked straight off the bottom of the
|
|
// kernel stack and into whatever was underneath, which showed up as
|
|
// user processes faulting at addresses they never touched and then a panic
|
|
// in freevm() on a page directory that had been overwritten. None of it
|
|
// pointed anywhere near a font.
|
|
//
|
|
// It never showed in the host harness, where the same code runs on an 8 MB
|
|
// stack. That is the whole hazard of a rasteriser that builds host-side and
|
|
// kernel-side from one source: the host is forgiving about exactly the
|
|
// resource the kernel is strictest about.
|
|
//
|
|
// Safe as statics because every caller is serialised: the console path holds
|
|
// cons.lock across a whole message, and the host harness is single-threaded.
|
|
// If a second thread ever rasterises concurrently, these have to become a
|
|
// caller-supplied scratch buffer rather than quietly go back on the stack.
|
|
static struct outline o;
|
|
static 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.
|
|
//
|
|
// Two regions, both direct-mapped and neither needing hashing or allocation.
|
|
//
|
|
// Printable ASCII gets a reserved slot each, one codepoint per slot, so the
|
|
// characters that make up nearly every line can never be evicted by anything
|
|
// else. Everything above ASCII shares a second block of slots keyed by
|
|
// codepoint modulo the block size. That block was the missing half: with an
|
|
// ASCII-only cache every accented letter re-rasterised on every redraw, so a
|
|
// screenful of Polish did hundreds of bezier flattenings per scroll while the
|
|
// same screenful of English did none.
|
|
//
|
|
// The modulus is chosen so that a run of consecutive codepoints lands in
|
|
// consecutive slots. Latin-1 Supplement and Latin Extended-A together are
|
|
// under 200 codepoints, so a European text in any one language fits without
|
|
// collisions; mixing scripts costs collisions, not correctness.
|
|
//
|
|
// 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. Two events empty it: a size
|
|
// change, which makes every bitmap wrong, and the pool running out, which is
|
|
// the price of letting a slot be re-keyed without being able to reclaim the
|
|
// bytes its previous occupant held. Both are safe because the returned glyph
|
|
// is only promised to be valid until the next call.
|
|
|
|
#define CACHE_LO 32 // space
|
|
#define CACHE_HI 127 // exclusive
|
|
#define ASCII_N (CACHE_HI - CACHE_LO)
|
|
#define EXTRA_N 224 // shared slots for everything above ASCII
|
|
#define CACHE_N (ASCII_N + EXTRA_N)
|
|
#define CACHE_POOL (64 * 1024)
|
|
|
|
struct gcache {
|
|
struct glyph g;
|
|
uint32 cp; // key; meaningful only when `have`
|
|
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;
|
|
}
|
|
|
|
// Control characters are not glyphs and would waste a reserved slot each.
|
|
if (cp < CACHE_LO)
|
|
return ttf_render(f, cp, out, ttf_cov, (int)sizeof(ttf_cov), 0, 0);
|
|
|
|
struct gcache *c = cp < CACHE_HI ? &cache[cp - CACHE_LO]
|
|
: &cache[ASCII_N + cp % EXTRA_N];
|
|
if (c->have && c->cp == cp) {
|
|
*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);
|
|
|
|
// Out of pool. The bytes held by evicted slots cannot be reclaimed
|
|
// individually, so reclaim all of them at once and start over rather than
|
|
// degrading to permanently uncached the first time a page of mixed scripts
|
|
// goes past. Rebuilding costs one rasterisation per glyph still on screen.
|
|
if (cache_used + need > CACHE_POOL && need <= CACHE_POOL) {
|
|
for (int i = 0; i < CACHE_N; i++)
|
|
cache[i].have = 0;
|
|
cache_used = 0;
|
|
}
|
|
|
|
if (cache_used + need <= CACHE_POOL) {
|
|
memmove(cache_pool + cache_used, g.cov, need);
|
|
c->off = cache_used;
|
|
c->cp = cp;
|
|
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;
|
|
}
|
|
|
|
// A single glyph larger than the whole pool. Still correct, just uncached --
|
|
// and it 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;
|
|
}
|