Initial commit: Schunix as it stands before the SchOS rename

An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
This commit is contained in:
Nexoniarz 2026-08-09 13:47:01 +02:00
commit d0f7acf9eb
275 changed files with 51417 additions and 0 deletions

606
kernel/dev/ttf.c Normal file
View file

@ -0,0 +1,606 @@
// 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;
};
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;
}