shithub: riscv

Download patch

ref: db71e19005574388bdc368081848327a5c104e5a
parent: 198f10bb25382882c4abd9081dac4dd74dbdbb9f
author: aiju <devnull@localhost>
date: Sat Jun 9 10:33:19 EDT 2018

add libttf

diff: cannot open b/sys/src/libttf//null: file does not exist: 'b/sys/src/libttf//null'
--- /dev/null
+++ b/sys/include/ttf.h
@@ -1,0 +1,168 @@
+#pragma src "/sys/src/libttf"
+#pragma lib "libttf.a"
+
+typedef struct TTTable TTTable;
+typedef struct TTChMap TTChMap;
+typedef struct TTPoint TTPoint;
+typedef struct TTGlyph TTGlyph;
+typedef struct TTGlyphInfo TTGlyphInfo;
+typedef struct TTFontU TTFontU;
+typedef struct TTFont TTFont;
+typedef struct TTFunction TTFunction;
+typedef struct TTGState TTGState;
+typedef struct TTBitmap TTBitmap;
+typedef struct TTKern TTKern;
+typedef struct Biobuf Biobuf;
+
+struct TTTable {
+	u32int tag;
+	u32int csum;
+	u32int offset;
+	u32int len;
+};
+struct TTChMap {
+	int start, end, delta;
+	int *tab;
+	enum {
+		TTCDELTA16 = 1,
+		TTCINVALID = 2,
+	} flags;
+	int temp;
+};
+struct TTPoint {
+	int x, y;
+	u8int flags;
+};
+struct TTBitmap {
+	u8int *bit;
+	int width, height, stride;
+};
+struct TTGlyph {
+	TTBitmap;
+	int idx;
+	int xmin, xmax, ymin, ymax;
+	int xminpx, xmaxpx, yminpx, ymaxpx;
+	int advanceWidthpx;
+	TTPoint *pt;
+	TTPoint *ptorg;
+	int npt;
+	u16int *confst;
+	int ncon;
+	u8int *hint;
+	int nhint;
+	TTFont *font;
+	TTGlyphInfo *info;
+};
+struct TTGlyphInfo {
+	int loca;
+	u16int advanceWidth;
+	short lsb;
+};
+struct TTFunction {
+	u8int *pgm;
+	int npgm;
+};
+struct TTGState {
+	int pvx, pvy;
+	int dpvx, dpvy;
+	int fvx, fvy;
+	u32int instctrl;
+	u32int scanctrl;
+	u32int scantype;
+	int rperiod, rphase, rthold;
+	u8int zp;
+	int rp[3];
+	int cvci;
+	int loop;
+	int mindist;
+	int deltabase, deltashift;
+	u8int autoflip;
+	u32int singlewval, singlewci;
+};
+struct TTKern {
+	u32int idx;
+	int val;
+};
+struct TTFontU {
+	int ref;
+
+	Biobuf *bin;
+
+	TTTable *tab;
+	u16int ntab;
+	
+	TTChMap *cmap;
+	int ncmap;
+	
+	short *cvtu;
+	int ncvtu;
+
+	u16int flags;
+	int emsize;
+	short xmin, ymin, xmax, ymax;
+	u16int longloca;
+	
+	TTGlyphInfo *ginfo;
+	
+	u16int numGlyphs;
+	u16int maxPoints;
+	u16int maxCountours;
+	u16int maxComponentPoints;
+	u16int maxComponentCountours;
+	u16int maxZones;
+	u16int maxTwilightPoints;
+	u16int maxStorage;
+	u16int maxFunctionDefs;
+	u16int maxInstructionDefs;
+	u16int maxStackElements;
+	u16int maxSizeOfInstructions;
+	u16int maxComponentElements;
+	u16int maxComponentDepth;
+	
+	int ascent, descent;
+	
+	u16int advanceWidthMax;
+	u16int minLeftSideBearing;
+	u16int minRightSideBearing;
+	u16int xMaxExtent;
+	u16int numOfLongHorMetrics;
+	
+	TTKern *kern;
+	int nkern;
+};
+struct TTFont {
+	TTFontU *u;
+	int ascentpx, descentpx;
+	int ppem;
+	TTGState;
+	TTGState defstate;
+	TTPoint *twilight, *twiorg;
+	u32int *hintstack;
+	TTFunction *func;
+	u32int *storage;
+	int *cvt;
+	int ncvt;
+};
+
+TTFont *ttfopen(char *, int, int);
+TTFont *ttfscale(TTFont *, int, int);
+void ttfclose(TTFont *);
+int ttffindchar(TTFont *, Rune);
+int ttfenumchar(TTFont *, Rune, Rune *);
+TTGlyph *ttfgetglyph(TTFont *, int, int);
+void ttfputglyph(TTGlyph *);
+int ttfgetcontour(TTGlyph *, int, float **, int *);
+
+enum {
+	TTFLALIGN = 0,
+	TTFRALIGN = 1,
+	TTFCENTER = 2,
+	TTFMODE = 3,
+	TTFJUSTIFY = 4,
+};
+
+TTBitmap *ttfrender(TTFont *, char *, char *, int, int, int, char **);
+TTBitmap *ttfrunerender(TTFont *, Rune *, Rune *, int, int, int, Rune **);
+TTBitmap *ttfnewbitmap(int, int);
+void ttffreebitmap(TTBitmap *);
+void ttfblit(TTBitmap *, int, int, TTBitmap *, int, int, int, int);
--- /dev/null
+++ b/sys/man/2/ttf
@@ -1,0 +1,211 @@
+.TH TTF 2
+.SH NAME
+ttfopen, ttfscale, ttfclose, ttffindchar, ttfenumchar, ttfgetglyph,
+ttfputglyph, ttfgetcontour, ttfrender, ttfrunerender, ttfnewbitmap,
+ttffreebitmap, ttfblit \- TrueType renderer
+.SH SYNOPSIS
+.de PB
+.PP
+.ft L
+.nf
+..
+.PB
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ttf.h>
+.PB
+struct TTBitmap {
+	u8int *bit;
+	int width, height, stride;
+};
+.PB
+struct TTGlyph {
+	TTBitmap;
+	int xminpx, xmaxpx, yminpx, ymaxpx, advanceWidthpx;
+	/* + internals */
+};
+.PB
+struct TTFont {
+	int ppem, ascentpx, descentpx;
+	/* + internals */
+};
+.PB
+.ta +\w'\fLTTBitmap* \fP'u
+TTFont*	ttfopen(char *filename, int size, int flags);
+TTFont*	ttfscale(TTFont *f, int size, int flags);
+void	ttfclose(TTFont *f);
+.PB
+int	ttffindchar(TTFont *f, Rune r);
+int	ttfenumchar(TTFont *f, Rune r, Rune *rp);
+.PB
+TTGlyph*	ttfgetglyph(TTFont *f, int glyphidx, int render);
+void	ttfputglyph(TTGlyph *g);
+int	ttfgetcontour(TTGlyph *g, int idx, float **fp, int *nfp);
+.PB
+TTBitmap*	ttfrender(TTFont *f, char *s, char *e, int w, int h,
+			int flags, char **pp);
+TTBitmap*	ttfrunerender(TTFont *f, Rune *s, Rune *e, int w, int h,
+			int flags, char **pp);
+.PB
+TTBitmap*	ttfnewbitmap(int w, int h);
+void	ttfblit(TTBitmap *dst, int dstx, int dsty, TTBitmap *src,
+			int srcx, int srcy, int w, int h);
+void	ttffreebitmap(TTBitmap *);
+.SH DESCRIPTION
+.PP
+.I Libttf
+is a parser and renderer of TrueType fonts.
+Given a \fLttf\fR font file it can produce the rendered versions of characters at a given size.
+.PP
+.I Ttfopen
+opens the font at
+.I filename
+and initialises it for rendering at size
+.I size
+(specified in pixels per em).
+.I Flags
+is reserved for future use and should be zero.
+If rendering at multiple sizes is desired,
+.I ttfscale
+reopens the font at a different size (internally the size-independent data is shared).
+.I TTfclose
+closes an opened font.
+Each instance of a font created by
+.I ttfopen
+and
+.I ttfscale
+must be closed separately.
+.PP
+A character in a TrueType font is called a glyph.
+Glyphs are numbered starting from 0 and the glyph indices do not need to follow any established coding scheme.
+.I Ttffindchar
+finds the glyph number of a given rune (Unicode codepoint).
+If the character does not exist in the font, zero is returned.
+Note that, in TrueType fonts, glyph 0 conventionally contains the "glyph not found" character.
+.I Ttfenumchar
+is like
+.I ttffindchar
+but will continue searching if the character is not in the font, returning the rune number for which it found a glyph in
+.BR *rp .
+It returns character in ascending Unicode order and it can be used to enumerate the characters in a font.
+Zero is returned if there are no further characters.
+.PP
+.I Ttfgetglyph
+interprets the actual data for a glyph specified by its index
+.IR glyphidx .
+With
+.I render
+set to zero, the data is left uninterpreted; currently its only use is
+.I ttfgetcontour.
+With
+.I render
+set to one, the glyph is also rendered, i.e. a pixel representation is produced and stored in the
+.I TTBitmap
+embedded in the
+.I TTGlyph
+structure it returns.
+Although TrueType uses a right handed coordinate system (y increases going up), the bitmap data returns follows Plan 9 conventions (and is compatible with the
+.IR draw (3)
+mask argument).
+The bottom left hand corner is at position (\fIxmin\fR, \fIymin\fR) in the TrueType coordinate system.
+.I Ttfputglyph
+should be used to return
+.I TTGlyph
+structures for cleanup.
+.PP
+.I Ttfgetcontour
+can be used to obtain raw contour data for a glyph.
+Given an index
+.I i
+it returns the corresponding contour (counting from zero), storing a pointer to a list of (\fIx\fR, \fIy\fR) pairs in
+.BR *fp .
+The array is allocated with
+.BR malloc (2).
+The (always odd) number of points is stored in
+.BR *np .
+The contours correspond to closed quadratic Bézier curves and the points with odd indices are the control points.
+For an invalid index, zero is returned and
+.B *fp
+and
+.B *np
+are not accessed.
+For a valid index, the number returned is the number of contours with index ≥ \fIi\fR.
+.PP
+.I Ttfrender
+and
+.I ttfrunerender
+typeset a string of text (specified as UTF-8 or raw Unicode, respectively) and return a bitmap of size
+.I w
+and
+.IR h .
+It attempts to typeset text starting from
+.I s
+and up to and not including
+.IR e .
+If
+.I e
+is
+.BR nil ,
+text is typeset until a null byte is encountered.
+.I Flags
+specifies the alignment.
+.BR TTFLALIGN ,
+.BR TTFRALIGN
+and
+.B TTFCENTER
+specify left-aligned, right-aligned and centered text, respectively.
+.B TTFJUSTIFY
+can be or'ed with these three options to produce text where any ``wrapped'' line is justified.
+.PP
+For reasons of efficiency and simplicity,
+.I libttf
+includes its own format for 1 bpp bitmaps.
+In these bitmaps,
+0 corresponds to transparent and 1 corresponds to opaque.
+Otherwise, the format is identical to
+.B k1
+.IR image (6)
+bitmaps.
+.I Ttfnewbitmap
+and
+.I ttffreebitmap
+allocate and deallocate such bitmaps, respectively.
+.I TTGlyph
+structures can be used in place of bitmaps but must be deallocated with
+.IR ttfputglyph ,
+not
+.IR ttffreebitmap .
+.I Ttfblit
+copies part of one bitmap onto another.
+Note that bits are or'ed together \(-- blitting a transparent over an opaque pixel does not produce an transparent pixel.
+.SH SOURCE
+.B /sys/src/libttf
+.SH "SEE ALSO"
+Apple, ``TrueType™ Reference Manual''.
+.br
+Microsoft, ``OpenType® specification''.
+.br
+FreeType, source code (the only accurate source).
+.br
+.IR ttfrender (1).
+.SH DIAGNOSTICS
+Following standard conventions, routines returning pointers return
+.B nil
+on error and return an error message in
+.BR errstr .
+.SH BUGS
+Both ``standards'' are packages of contradictions and lies.
+.PP
+Apple Advanced Typography and Microsoft OpenType extensions are not supported; similarly non-TrueType (Postscript, Bitmap) fonts packaged as
+.B .ttf
+files are not supported.
+.PP
+The library is immature and interfaces are virtually guaranteed to change.
+.PP
+Fonts packaged as
+.B .ttc
+files are not supported.
+.SH HISTORY
+.I Libttf
+first appeared in 9front in June, 2018.
--- /dev/null
+++ b/sys/src/libttf/bit.c
@@ -1,0 +1,92 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ttf.h>
+#include "impl.h"
+
+TTBitmap *
+ttfnewbitmap(int w, int h)
+{
+	TTBitmap *b;
+
+	b = mallocz(sizeof(TTBitmap), 1);
+	if(b == nil) return nil;
+	b->width = w;
+	b->height = h;
+	b->stride = w + 7 >> 3;
+	b->bit = mallocz(b->stride * h, 1);
+	if(b->bit == nil){
+		free(b);
+		return nil;
+	}
+	return b;
+}
+
+void
+ttffreebitmap(TTBitmap *b)
+{
+	if(b == nil) return;
+	free(b->bit);
+	free(b);
+}
+
+void
+ttfblit(TTBitmap *dst, int dx, int dy, TTBitmap *src, int sx0, int sy0, int sx1, int sy1)
+{
+	uchar *sp, *dp;
+	u32int b;
+	int x, y, ss, ds, dx1, dy1;
+
+	if(sx0 < 0) sx0 = 0;
+	if(sy0 < 0) sy0 = 0;
+	if(sx1 > src->width) sx1 = src->width;
+	if(sy1 > src->height) sy1 = src->height;
+	if(dx < 0){
+		sx0 -= dx;
+		dx = 0;
+	}
+	if(dy < 0){
+		sy0 -= dy;
+		dy = 0;
+	}
+	dx1 = dx + sx1 - sx0;
+	dy1 = dy + sy1 - sy0;
+	if(dx1 > dst->width){
+		sx1 -= dx1 - dst->width;
+		dx1 = dst->width;
+	}
+	if(dy1 > dst->height) sy1 -= dy1 - dst->height;
+	if(sx1 <= sx0 || sy1 <= sy0) return;
+	ss = src->stride - ((sx1-1 >> 3) - (sx0 >> 3) + 1);
+	ds = dst->stride - ((dx1-1 >> 3) - (dx >> 3) + 1);
+	sp = src->bit + sy0 * src->stride + (sx0 >> 3);
+	dp = dst->bit + dy * dst->stride + (dx >> 3);
+	y = sy1 - sy0;
+	do{
+		if(sx0 >> 3 == sx1 >> 3){
+			b = (*sp++ << 8 & 0xff << 8-(sx0 & 7)) & -0x10000 >> (sx1 & 7);
+			if((sx0 & 7) == 0) b >>= 8;
+			x = (dx & 7) + (sx1 - sx0);
+		}else{
+			if((sx0 & 7) != 0)
+				b = *sp++ << 8 & 0xff << (-sx0 & 7);
+			else
+				b = 0;
+			x = (sx1 >> 3) - (sx0+7 >> 3);
+			while(--x >= 0){
+				b |= *sp++;
+				*dp++ |= b >> (dx & 7) + (-sx0 & 7);
+				b <<= 8;
+			}
+			if((sx1 & 7) != 0)
+				b |= *sp++ & -0x100 >> (sx1 & 7);
+			x = (dx & 7) + (-sx0 & 7) + (sx1 & 7);
+		}
+		for(; x > 0; x -= 8){
+			*dp++ |= b >> (dx & 7) + (-sx0 & 7);
+			b <<= 8;
+		}
+		sp += ss;
+		dp += ds;
+	}while(--y > 0);
+}
--- /dev/null
+++ b/sys/src/libttf/cmap.c
@@ -1,0 +1,322 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ttf.h>
+#include "impl.h"
+
+int
+ttffindchar(TTFont *fx, Rune r)
+{
+	int i, j, k, rv;
+	TTChMap *p;
+	TTFontU *f;
+
+	f = fx->u;
+	i = 0;
+	j = f->ncmap - 1;
+	if(r < f->cmap[0].start || r > f->cmap[j].end) return 0;
+	while(i < j){
+		k = (i + j) / 2;
+		if(f->cmap[k].end < r)
+			i = k+1;
+		else if(f->cmap[k].start > r)
+			j = k-1;
+		else
+			i = j = k;
+	}
+	if(i > j) return 0;
+	p = &f->cmap[i];
+	if(r < p->start || r > p->end) return 0;
+	if((p->flags & TTCINVALID) != 0) return 0;
+	if(p->tab != nil)
+		return p->tab[r - p->start];
+	rv = r + p->delta;
+	if((p->flags & TTCDELTA16) != 0)
+		rv = (u16int)rv;
+	return rv;
+}
+
+int
+ttfenumchar(TTFont *fx, Rune r, Rune *rp)
+{
+	int i, j, k, rv;
+	TTChMap *p;
+	TTFontU *f;
+
+	f = fx->u;
+	i = 0;
+	j = f->ncmap - 1;
+	if(r > f->cmap[j].end) return 0;
+	while(i < j){
+		k = (i + j) / 2;
+		if(f->cmap[k].end < r)
+			i = k+1;
+		else if(f->cmap[k].start > r)
+			j = k-1;
+		else
+			i = j = k;
+	}
+	if(j < 0) j = 0;
+	for(p = &f->cmap[j]; p < &f->cmap[f->ncmap]; p++){
+		if((p->flags & TTCINVALID) != 0)
+			continue;
+		if(r < p->start)
+			r = p->start;
+		if(p->tab != nil){
+			SET(rv);
+			while(r <= p->end && (rv = p->tab[r - p->start], rv == 0))
+				r++;
+			if(r > p->end)
+				continue;
+			if(rp != nil)
+				*rp = r;
+			return rv;
+		}
+		while(r < p->end){
+			rv = r + p->delta;
+			if((p->flags & TTCDELTA16) != 0)
+				rv = (u16int) rv;
+			if(rv != 0){
+				if(rp != nil)
+					*rp = r;
+				return rv;
+			}
+		}
+	}
+	return 0;
+}
+
+static int
+ttfgotosub(TTFontU *f)
+{
+	int i;
+	u16int nsub, id, sid, off;
+	int rank, maxrank;
+	u32int maxoff;
+	#define SUBID(a,b) ((a)<<16|(b))
+
+	if(ttfgototable(f, "cmap") < 0)
+		return -1;
+	ttfunpack(f, ".. w", &nsub);
+	maxrank = 0;
+	maxoff = 0;
+	for(i = 0; i < nsub; i++){
+		ttfunpack(f, "wwl", &id, &sid, &off);
+		switch(id << 16 | sid){
+		case SUBID(0, 4): /* Unicode 2.0 or later (BMP and non-BMP) */
+			rank = 100;
+			break;
+		case SUBID(0, 0): /* Unicode default */
+		case SUBID(0, 1): /* Unicode 1.1 */
+		case SUBID(0, 2): /* ISO 10646 */
+		case SUBID(0, 3): /* Unicode 2.0 (BMP) */
+			rank = 80;
+			break;
+		case SUBID(3, 10): /* Windows, UCS-4 */
+			rank = 60;
+			break;
+		case SUBID(3, 1): /* Windows, UCS-2 */
+			rank = 40;
+			break;
+		case SUBID(3, 0): /* Windows, Symbol */
+			rank = 20;
+			break;
+		default:
+			rank = 0;
+			break;
+		}
+		if(rank > maxrank){
+			maxrank = rank;
+			maxoff = off;
+		}
+	}
+	if(maxrank == 0){
+		werrstr("no suitable character table");
+		return -1;
+	}
+	if(ttfgototable(f, "cmap") < 0)
+		return -1;
+	Bseek(f->bin, maxoff, 1);
+	return 0;
+
+}
+
+static int
+cmap0(TTFontU *f)
+{
+	u16int len;
+	int i;
+	u8int *p;
+	int *q;
+
+	ttfunpack(f, "w2", &len);
+	if(len < 262){
+		werrstr("character table too short");
+		return -1;
+	}
+	f->cmap = mallocz(sizeof(TTChMap), 1);
+	if(f->cmap == nil)
+		return -1;
+	f->ncmap = 1;
+	f->cmap->start = 0;
+	f->cmap->end = 0xff;
+	f->cmap->tab = mallocz(256 * sizeof(int), 1);
+	if(f->cmap->tab == nil)
+		return -1;
+	Bread(f->bin, f->cmap->tab, 256 * sizeof(int));
+	p = (u8int*)f->cmap->tab + 256;
+	q = f->cmap->tab + 256;
+	for(i = 255; i >= 0; i--)
+		*--q = *--p;
+	return 0;
+}
+
+static int
+cmap4(TTFontU *f)
+{
+	u16int len, ncmap;
+	int i, j, n, n0, off;
+	u16int v;
+	u8int *buf;
+
+	ttfunpack(f, "w2", &len);
+	if(len < 16){
+		werrstr("character table too short");
+		return -1;
+	}
+	ttfunpack(f, "w6", &ncmap);
+	ncmap /= 2;
+	if(len < 16 + 8 * ncmap){
+		werrstr("character table too short");
+		return -1;
+	}
+	f->cmap = mallocz(sizeof(TTChMap) * ncmap, 1);
+	if(f->cmap == nil) return -1;
+	f->ncmap = ncmap;
+	for(i = 0; i < ncmap; i++)
+		f->cmap[i].flags = TTCDELTA16;
+	for(i = 0; i < ncmap; i++)
+		ttfunpack(f, "W", &f->cmap[i].end);
+	ttfunpack(f, "..");
+	for(i = 0; i < ncmap; i++)
+		ttfunpack(f, "W", &f->cmap[i].start);
+	for(i = 0; i < ncmap; i++)
+		ttfunpack(f, "W", &f->cmap[i].delta);
+	for(i = 0; i < ncmap; i++)
+		ttfunpack(f, "W", &f->cmap[i].temp);
+	len -= 10 + 8 * ncmap;
+	buf = malloc(len);
+	if(buf == nil)
+		return -1;
+	Bread(f->bin, buf, len);
+	for(i = 0; i < ncmap; i++){
+		if(f->cmap[i].temp == 0) continue;
+		n0 = f->cmap[i].end - f->cmap[i].start + 1;
+		n = n0;
+		off = f->cmap[i].temp - (ncmap - i) * 2;
+		if(off + 2 * n > len) n = (len - off) / 2;
+		if(off < 0 || n <= 0){
+			f->cmap[i].flags |= TTCINVALID;
+			continue;
+		}
+		f->cmap[i].tab = mallocz(n0 * sizeof(int), 1);
+		if(f->cmap[i].tab == nil)
+			return -1;
+		for(j = 0; j < n; j++){
+			v = buf[off + 2*j] << 8 | buf[off + 2*j + 1];
+			if(v != 0) v += f->cmap[i].delta;
+			f->cmap[i].tab[j] = v;
+		}
+	}
+	free(buf);
+	return 0;
+}
+
+static int
+cmap6(TTFontU *f)
+{
+	u16int len, first, cnt, v;
+	int *p;
+	u8int *q;
+
+	ttfunpack(f, "w2", &len);
+	if(len < 12){
+		werrstr("character table too short");
+		return -1;
+	}
+	ttfunpack(f, "ww", &first, &cnt);
+	f->cmap = mallocz(sizeof(TTChMap), 1);
+	if(f->cmap == nil)
+		return -1;
+	f->ncmap = 1;
+	f->cmap->start = first;
+	f->cmap->end = first + len - 1;
+	f->cmap->tab = mallocz(cnt * sizeof(int), 1);
+	if(f->cmap->tab == nil)
+		return -1;
+	if(len < 10 + 2 * cnt){
+		werrstr("character table too short");
+		return -1;
+	}
+	Bread(f->bin, f->cmap->tab, 2 * cnt);
+	p = f->cmap->tab + cnt;
+	q = (u8int*) f->cmap->tab + 2 * cnt;
+	while(p > f->cmap->tab){
+		v = *--q;
+		v |= *--q << 8;
+		*--p = v;
+	}
+	return 0;
+}
+
+static int
+cmap12(TTFontU *f)
+{
+	u32int len;
+	u32int ncmap;
+	int i;
+	
+	ttfunpack(f, "2l4", &len);
+	if(len < 16){
+		werrstr("character table too short");
+		return -1;
+	}
+	ttfunpack(f, "l", &ncmap);
+	if(len < 16 + 12 * ncmap){
+		werrstr("character table too short");
+		return -1;
+	}
+	f->cmap = mallocz(sizeof(TTChMap) * ncmap, 1);
+	if(f->cmap == nil)
+		return -1;
+	f->ncmap = ncmap;
+	for(i = 0; i < ncmap; i++){
+		ttfunpack(f, "lll", &f->cmap[i].start, &f->cmap[i].end, &f->cmap[i].delta);
+		f->cmap[i].delta -= f->cmap[i].start;
+	}
+	return 0;
+}
+
+int (*cmaphand[])(TTFontU *) = {
+	[0] cmap0,
+	[4] cmap4,
+	[6] cmap6,
+	[12] cmap12,
+};
+
+int
+ttfparsecmap(TTFontU *f)
+{
+	u16int format;
+
+	if(ttfgotosub(f) < 0)
+		return -1;
+	ttfunpack(f, "w", &format);
+	if(format >= nelem(cmaphand) || cmaphand[format] == nil){
+		werrstr("character table in unknown format %d", format);
+		return -1;
+	}
+	if(cmaphand[format](f) < 0)
+		return -1;
+	return 0;
+}
--- /dev/null
+++ b/sys/src/libttf/glyf.c
@@ -1,0 +1,371 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <ctype.h>
+#include <ttf.h>
+#include "impl.h"
+
+void
+ttfputglyph(TTGlyph *g)
+{
+	if(g == nil) return;
+	free(g->pt);
+	free(g->ptorg);
+	free(g->confst);
+	free(g->bit);
+	free(g->hint);
+	free(g);
+}
+
+static void
+glyphscale(TTGlyph *g)
+{
+	TTFont *f;
+	int i;
+	TTPoint *p;
+	
+	f = g->font;
+	for(i = 0; i < g->npt; i++){
+		p = &g->pt[i];
+		p->x = ttfrounddiv(p->x * f->ppem * 64, f->u->emsize);
+		p->y = ttfrounddiv(p->y * f->ppem * 64, f->u->emsize);
+	}
+	memmove(g->ptorg, g->pt, sizeof(TTPoint) * g->npt);
+	g->pt[g->npt - 1].x = g->pt[g->npt - 1].x + 32 & -64;
+}
+
+static TTGlyph *
+emptyglyph(TTFont *fs, int glyph, int render)
+{
+	TTGlyph *g;
+
+	g = mallocz(sizeof(TTGlyph), 1);
+	if(g == nil)
+		return nil;
+	g->font = fs;
+	g->info = &fs->u->ginfo[glyph];
+	g->confst = malloc(sizeof(int));
+	g->npt = 2;
+	g->pt = mallocz(sizeof(TTPoint) * 2, 1);
+	g->ptorg = mallocz(sizeof(TTPoint) * 2, 1);
+	if(g->confst == nil || g->pt == nil || g->ptorg == nil){
+		ttfputglyph(g);
+		return nil;
+	}
+	g->pt[1].x = g->info->advanceWidth;
+	g->npt = 2;
+	if(render)
+		glyphscale(g);
+	g->xmin = 0;
+	g->ymin = 0;
+	g->xmax = g->info->advanceWidth;
+	g->ymax = 1;
+	if(render){
+		g->xminpx = 0;
+		g->xmaxpx = (g->xmax * fs->ppem + fs->u->emsize - 1) / fs->u->emsize;
+		g->yminpx = 0;
+		g->ymaxpx = 1;
+	}
+	return g;
+}
+
+static TTGlyph *
+simpglyph(TTFont *fs, int glyph, int nc, int render)
+{
+	u16int np;
+	short x;
+	u16int len;
+	u16int temp16;
+	u8int temp8;
+	u8int *flags, *fp, *fq;
+	TTPoint *p;
+	int i, j, r;
+	short lastx, lasty;
+	TTFontU *f;
+	TTGlyph *g;
+	
+	flags = nil;
+	f = fs->u;
+	g = mallocz(sizeof(TTGlyph), 1);
+	if(g == nil)
+		return nil;
+	g->font = fs;
+	g->info = &f->ginfo[glyph];
+	g->confst = malloc(sizeof(u16int) * (nc + 1));
+	if(g->confst == nil)
+		goto err;
+	x = -1;
+	for(i = g->ncon; i < nc; i++){
+		g->confst[i] = x + 1;
+		ttfunpack(f, "w", &x);
+	}
+	g->confst[i] = x + 1;
+	g->ncon = nc;
+
+	np = x + 1;
+	ttfunpack(f, "w", &len);
+	g->nhint = len;
+	g->hint = mallocz(len, 1);
+	if(g->hint == nil)
+		goto err;
+	Bread(f->bin, g->hint, len);
+	
+	flags = mallocz(np, 1);
+	if(flags == nil)
+		goto err;
+	for(i = 0; i < np; i++){
+		j = Bgetc(f->bin);
+		flags[i] = j;
+		if((j & 8) != 0){
+			r = Bgetc(f->bin);
+			while(r-- > 0)
+				flags[++i] = j;
+		}
+	}
+	
+	fp = flags;
+	fq = flags;
+	lastx = lasty = 0;
+	g->pt = malloc(sizeof(TTPoint) * (np + 2));
+	if(g->pt == nil)
+		goto err;
+	g->ptorg = malloc(sizeof(TTPoint) * (np + 2));
+	if(g->ptorg == nil)
+		goto err;
+	for(i = 0; i < np; i++){
+		p = &g->pt[g->npt + i];
+		p->flags = *fp & 1;
+		switch(*fp++ & 0x12){
+		case 0x00: ttfunpack(f, "w", &temp16); p->x = lastx += temp16; break;
+		case 0x02: ttfunpack(f, "b", &temp8); p->x = lastx -= temp8; break;
+		case 0x10: p->x = lastx; break;
+		case 0x12: ttfunpack(f, "b", &temp8); p->x = lastx += temp8; break;
+		}
+	}
+	for(i = 0; i < np; i++){
+		p = &g->pt[g->npt + i];
+		switch(*fq++ & 0x24){
+		case 0x00: ttfunpack(f, "w", &temp16); p->y = lasty += temp16; break;
+		case 0x04: ttfunpack(f, "b", &temp8); p->y = lasty -= temp8; break;
+		case 0x20: p->y = lasty; break;
+		case 0x24: ttfunpack(f, "b", &temp8); p->y = lasty += temp8; break;
+		}
+	}
+	g->pt[np] = (TTPoint){0,0,0};
+	g->pt[np+1] = (TTPoint){f->ginfo[glyph].advanceWidth,0,0};
+	g->npt = np + 2;
+	free(flags);
+	if(render){
+		glyphscale(g);
+		ttfhint(g);
+	}
+	return g;
+err:
+	free(flags);
+	ttfputglyph(g);
+	return nil;
+}
+
+static TTGlyph *getglyph(TTFont *, int, int);
+
+enum {
+	ARG_1_AND_2_ARE_WORDS = 1<<0,
+	ARGS_ARE_XY_VALUES = 1<<1,
+	ROUND_XY_TO_GRID = 1<<2,
+	WE_HAVE_A_SCALE = 1<<3,
+	MORE_COMPONENTS = 1<<5,
+	WE_HAVE_AN_X_AND_Y_SCALE = 1<<6,
+	WE_HAVE_A_TWO_BY_TWO = 1<<7,
+	WE_HAVE_INSTRUCTIONS = 1<<8,
+	USE_MY_METRICS = 1<<9,
+	OVERLAP_COMPOUND = 1<<10,
+};
+
+static int
+mergeglyph(TTGlyph *g, TTGlyph *h, int flags, int x, int y, int a, int b, int c, int d, int render)
+{
+	int i, m;
+	TTPoint *p;
+	TTFont *f;
+	int dx, dy;
+
+	f = g->font;
+	g->confst = realloc(g->confst, sizeof(int) * (g->ncon + h->ncon + 1));
+	for(i = 1; i <= h->ncon; i++)
+		g->confst[g->ncon + i] = g->confst[g->ncon] + h->confst[i];
+	g->ncon += h->ncon;
+	g->pt = realloc(g->pt, sizeof(TTPoint) * (g->npt + h->npt - 2));
+	if((flags & USE_MY_METRICS) == 0){
+		memmove(g->pt + g->npt + h->npt - 4, g->pt + g->npt - 2, 2 * sizeof(TTPoint));
+		m = h->npt - 2;
+	}else
+		m = h->npt;
+	for(i = 0; i < m; i++){
+		p = &g->pt[g->npt - 2 + i];
+		*p = h->pt[i];
+		dx = ttfrounddiv(p->x * a + p->y * b, 16384);
+		dy = ttfrounddiv(p->x * c + p->y * d, 16384);
+		p->x = dx;
+		p->y = dy;
+		if((flags & ARGS_ARE_XY_VALUES) != 0){
+			if(render){
+				dx = ttfrounddiv(x * f->ppem * 64, f->u->emsize);
+				dy = ttfrounddiv(y * f->ppem * 64, f->u->emsize);
+				if((flags & ROUND_XY_TO_GRID) != 0){
+					dx = dx + 32 & -64;
+					dy = dy + 32 & -64;
+				}
+			}
+			p->x += dx;
+			p->y += dy;
+		}else
+			abort();
+	}
+	g->npt += h->npt - 2;
+	return 0;
+}
+
+static TTGlyph *
+compglyph(TTFont *fs, int glyph, int render)
+{
+	u16int flags, idx;
+	int x, y;
+	int a, b, c, d;
+	TTFontU *f;
+	uvlong off;
+	TTGlyph *g, *h;
+	u16int len;
+
+	f = fs->u;
+	g = mallocz(sizeof(TTGlyph), 1);
+	if(g == nil)
+		return nil;
+	g->font = fs;
+	g->info = &f->ginfo[glyph];
+	g->pt = mallocz(sizeof(TTPoint) * 2, 1);
+	if(g->pt == nil){
+	err:
+		ttfputglyph(g);
+		return nil;
+	}
+	g->pt[1].x = ttfrounddiv(f->ginfo[glyph].advanceWidth * fs->ppem * 64, f->emsize);
+	g->npt = 2;
+	g->confst = mallocz(sizeof(int), 1);
+	if(g->confst == nil)
+		goto err;
+	do{
+		ttfunpack(f, "ww", &flags, &idx);
+		switch(flags & (ARG_1_AND_2_ARE_WORDS | ARGS_ARE_XY_VALUES)){
+		case 0: ttfunpack(f, "BB", &x, &y); break;
+		case ARGS_ARE_XY_VALUES: ttfunpack(f, "BB", &x, &y); x = (char)x; y = (char)y; break;
+		case ARG_1_AND_2_ARE_WORDS: ttfunpack(f, "WW", &x, &y); break;
+		case ARG_1_AND_2_ARE_WORDS | ARGS_ARE_XY_VALUES: ttfunpack(f, "WW", &x, &y); x = (short)x; y = (short)y; break;
+		}
+		if((flags & WE_HAVE_A_SCALE) != 0){
+			ttfunpack(f, "S", &a);
+			d = a;
+			b = c = 0;
+		}else if((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0){
+			ttfunpack(f, "SS", &a, &d);
+			b = c = 0;
+		}else if((flags & WE_HAVE_A_TWO_BY_TWO) != 0)
+			ttfunpack(f, "SSSS", &a, &b, &c, &d);
+		else{
+			a = d = 1<<14;
+			b = c = 0;
+		}
+		off = Bseek(f->bin, 0, 1);
+		h = getglyph(fs, idx, render);
+		if(h == nil){
+			ttfputglyph(g);
+			return nil;
+		}
+		if(mergeglyph(g, h, flags, x, y, a, b, c, d, render) < 0){
+			ttfputglyph(h);
+			ttfputglyph(g);
+			return nil;
+		}
+		ttfputglyph(h);
+		Bseek(f->bin, off, 0);
+	}while((flags & MORE_COMPONENTS) != 0);
+	g->ptorg = malloc(sizeof(TTPoint) * g->npt);
+	memmove(g->ptorg, g->pt, sizeof(TTPoint) * g->npt);
+//	g->pt[g->npt - 1].x = g->pt[g->npt - 1].x + 32 & -64;
+	if(render && (flags & WE_HAVE_INSTRUCTIONS) != 0){
+		ttfunpack(f, "w", &len);
+		g->nhint = len;
+		g->hint = mallocz(len, 1);
+		if(g->hint == nil)
+			goto err;
+		Bread(f->bin, g->hint, len);
+		ttfhint(g);
+	}
+	return g;
+}
+
+static TTGlyph *
+getglyph(TTFont *fs, int glyph, int render)
+{
+	int i;
+	short xmin, ymin, xmax, ymax, nc;
+	TTFontU *f;
+	TTGlyph *g;
+	
+	f = fs->u;
+	if((uint)glyph >= f->numGlyphs){
+		werrstr("no such glyph %d", glyph);
+		return nil;
+	}
+	if(f->ginfo[glyph].loca == f->ginfo[glyph+1].loca){
+		return emptyglyph(fs, glyph, render);
+	}
+	if(ttfgototable(f, "glyf") < 0)
+		return nil;
+	Bseek(f->bin, f->ginfo[glyph].loca, 1);
+	ttfunpack(f, "wwwww", &nc, &xmin, &ymin, &xmax, &ymax);
+	if(nc < 0)
+		g = compglyph(fs, glyph, render);
+	else
+		g = simpglyph(fs, glyph, nc, render);
+	if(g == nil)
+		return nil;
+	g->xmin = g->pt[0].x;
+	g->xmax = g->pt[0].x;
+	g->ymin = g->pt[0].y;
+	g->ymax = g->pt[0].y;
+	for(i = 1; i < g->npt - 2; i++){
+		if(g->pt[i].x < g->xmin)
+			g->xmin = g->pt[i].x;
+		if(g->pt[i].x > g->xmax)
+			g->xmax = g->pt[i].x;
+		if(g->pt[i].y < g->ymin)
+			g->ymin = g->pt[i].y;
+		if(g->pt[i].y > g->ymax)
+			g->ymax = g->pt[i].y;
+	}
+	if(render){
+		g->xminpx = g->xmin >> 6;
+		g->xmaxpx = g->xmax + 63 >> 6;
+		g->yminpx = g->ymin >> 6;
+		g->ymaxpx = g->ymax + 63 >> 6;
+	}
+	return g;
+}
+
+TTGlyph *
+ttfgetglyph(TTFont *fs, int glyph, int render)
+{
+	TTGlyph *g;
+	
+	g = getglyph(fs, glyph, render);
+	if(g == nil)
+		return nil;
+	g->idx = glyph;
+	if(render){
+		ttfscan(g);
+		g->advanceWidthpx = (g->pt[g->npt - 1].x - g->pt[g->npt - 2].x + 63) / 64;
+	}
+	setmalloctag(g, getcallerpc(&fs));
+	return g;
+}
--- /dev/null
+++ b/sys/src/libttf/head.c
@@ -1,0 +1,344 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <ctype.h>
+#include <ttf.h>
+#include "impl.h"
+
+void
+ttfunpack(TTFontU *f, char *p, ...)
+{
+	va_list va;
+	int n;
+	uchar *p1;
+	u16int *p2;
+	u32int *p4;
+	
+	va_start(va, p);
+	for(; *p != 0; p++)
+		switch(*p){
+		case 'b':
+			p1 = va_arg(va, u8int *);
+			*p1 = Bgetc(f->bin);
+			break;
+		case 'B':
+			p4 = va_arg(va, u32int *);
+			*p4 = Bgetc(f->bin);
+			break;
+		case 'w':
+			p2 = va_arg(va, u16int *);
+			*p2 = Bgetc(f->bin) << 8;
+			*p2 |= Bgetc(f->bin);
+			break;
+		case 'W':
+			p4 = va_arg(va, u32int *);
+			*p4 = Bgetc(f->bin) << 8;
+			*p4 |= Bgetc(f->bin);
+			break;
+		case 'S':
+			p4 = va_arg(va, u32int *);
+			*p4 = (char)Bgetc(f->bin) << 8;
+			*p4 |= Bgetc(f->bin);
+			break;
+		case 'l':
+			p4 = va_arg(va, u32int *);
+			*p4 = Bgetc(f->bin) << 24;
+			*p4 |= Bgetc(f->bin) << 16;
+			*p4 |= Bgetc(f->bin) << 8;
+			*p4 |= Bgetc(f->bin);
+			break;
+		case '.': Bgetc(f->bin); break;
+		case ' ': break;
+		default:
+			if(isdigit(*p)){
+				n = strtol(p, &p, 10);
+				p--;
+				Bseek(f->bin, n, 1);
+			}else abort();
+			break;
+		}
+}
+
+static int
+directory(TTFontU *f)
+{
+	u32int scaler;
+	int i;
+
+	ttfunpack(f, "lw .. .. ..", &scaler, &f->ntab);
+	if(scaler != 0x74727565 && scaler != 0x10000){
+		werrstr("unknown scaler type %#ux", scaler);
+		return -1;
+	}
+	f->tab = mallocz(sizeof(TTTable) * f->ntab, 1);
+	if(f->tab == nil) return -1;
+	for(i = 0; i < f->ntab; i++)
+		ttfunpack(f, "llll", &f->tab[i].tag, &f->tab[i].csum, &f->tab[i].offset, &f->tab[i].len);
+	return 0;
+}
+
+int
+ttfgototable(TTFontU *f, char *str)
+{
+	TTTable *t;
+	u32int tag;
+	
+	tag = (u8int)str[0] << 24 | (u8int)str[1] << 16 | (u8int)str[2] << 8 | (u8int)str[3];
+	for(t = f->tab; t < f->tab + f->ntab; t++)
+		if(t->tag == tag){
+			Bseek(f->bin, t->offset, 0);
+			return t->len;
+		}
+	werrstr("no such table '%s'", str);
+	return -1;
+}
+
+static int
+ttfparseloca(TTFontU *f)
+{
+	int len, i;
+	u32int x;
+	
+	len = ttfgototable(f, "loca");
+	if(len < 0) return -1;
+	x = 0;
+	if(f->longloca){
+		if(len > (f->numGlyphs + 1) * 4) len = (f->numGlyphs + 1) * 4;
+		for(i = 0; i < len/4; i++){
+			x = Bgetc(f->bin) << 24;
+			x |= Bgetc(f->bin) << 16;
+			x |= Bgetc(f->bin) << 8;
+			x |= Bgetc(f->bin);
+			f->ginfo[i].loca = x;
+		}
+	}else{
+		if(len > (f->numGlyphs + 1) * 2) len = (f->numGlyphs + 1) * 2;
+		for(i = 0; i < len/2; i++){
+			x = Bgetc(f->bin) << 8;
+			x |= Bgetc(f->bin);
+			f->ginfo[i].loca = x * 2;
+		}
+	}
+	for(; i < f->numGlyphs; i++)
+		f->ginfo[i].loca = x;
+	return 0;
+}
+
+static int
+ttfparsehmtx(TTFontU *f)
+{
+	int i;
+	u16int x, y;
+	int len;
+	int maxlsb;
+	
+	len = ttfgototable(f, "hmtx");
+	if(len < 0)
+		return -1;
+	if(f->numOfLongHorMetrics > f->numGlyphs){
+		werrstr("nonsensical header: numOfLongHorMetrics > numGlyphs");
+		return -1;
+	}
+	for(i = 0; i < f->numOfLongHorMetrics; i++){
+		ttfunpack(f, "ww", &x, &y);
+		f->ginfo[i].advanceWidth = x;
+		f->ginfo[i].lsb = y;
+	}
+	maxlsb = (len - 2 * f->numOfLongHorMetrics) / 2;
+	if(maxlsb > f->numGlyphs){
+		werrstr("nonsensical header: maxlsb > f->numGlyphs");
+		return -1;
+	}
+	for(; i < maxlsb; i++){
+		ttfunpack(f, "w", &y);
+		f->ginfo[i].advanceWidth = x;
+		f->ginfo[i].lsb = y;
+	}
+	for(; i < f->numGlyphs; i++){
+		f->ginfo[i].advanceWidth = x;
+		f->ginfo[i].lsb = y;
+	}
+	return 0;
+}
+
+int
+ttfparsecvt(TTFontU *f)
+{
+	int len;
+	int i;
+	int x;
+	u8int *p;
+	short *w;
+
+	len = ttfgototable(f, "cvt ");
+	if(len <= 0) return 0;
+	f->cvtu = mallocz(len, 1);
+	if(f->cvtu == 0) return -1;
+	Bread(f->bin, f->cvtu, len);
+	p = (u8int *) f->cvtu;
+	f->ncvtu = len / 2;
+	w = f->cvtu;
+	for(i = 0; i < f->ncvtu; i++){
+		x = (short)(p[0] << 8 | p[1]);
+		p += 2;
+		*w++ = x;
+	}
+	return 0;
+}
+
+static int
+ttfparseos2(TTFontU *f)
+{
+	int len;
+	u16int usWinAscent, usWinDescent;
+	
+	len = ttfgototable(f, "OS/2 ");
+	if(len < 0)
+		return -1;
+	if(len < 78){
+		werrstr("OS/2 table too short");
+		return -1;
+	}
+	ttfunpack(f, "68 6 ww", &usWinAscent, &usWinDescent);
+	f->ascent = usWinAscent;
+	f->descent = usWinDescent;
+	return 0;
+}
+
+static void
+ttfcloseu(TTFontU *u)
+{
+	int i;
+
+	if(u == nil) return;
+	Bterm(u->bin);
+	for(i = 0; i < u->ncmap; i++)
+		free(u->cmap[i].tab);
+	free(u->cmap);
+	free(u->ginfo);
+	free(u->tab);
+	free(u->cvtu);
+	free(u);
+}
+
+void
+ttfclose(TTFont *f)
+{
+	int i;
+
+	if(f == nil) return;
+	if(--f->u->ref <= 0)
+		ttfcloseu(f->u);
+	for(i = 0; i < f->u->maxFunctionDefs; i++)
+		free(f->func[i].pgm);
+	free(f->hintstack);
+	free(f->func);
+	free(f->storage);
+	free(f->twilight);
+	free(f->twiorg);
+	free(f->cvt);
+	free(f);
+}
+
+static TTFont *
+ttfscaleu(TTFontU *u, int ppem)
+{
+	TTFont *f;
+	int i;
+	
+	f = mallocz(sizeof(TTFont), 1);
+	if(f == nil) return nil;
+	f->u = u;
+	u->ref++;
+	f->ppem = ppem;
+	f->ncvt = u->ncvtu;
+	f->cvt = malloc(sizeof(int) * u->ncvtu);
+	if(f->cvt == nil) goto error;
+	for(i = 0; i < u->ncvtu; i++)
+		f->cvt[i] = ttfrounddiv(u->cvtu[i] * ppem * 64, u->emsize);
+	f->hintstack = mallocz(sizeof(u32int) * u->maxStackElements, 1);
+	f->func = mallocz(sizeof(TTFunction) * u->maxFunctionDefs, 1);
+	f->storage = mallocz(sizeof(u32int) * u->maxStorage, 1);
+	f->twilight = mallocz(sizeof(TTPoint) * u->maxTwilightPoints, 1);
+	f->twiorg = mallocz(sizeof(TTPoint) * u->maxTwilightPoints, 1);
+	if(f->hintstack == nil || f->func == nil || f->storage == nil || f->twilight == nil || f->twiorg == nil) goto error;
+	f->ascentpx = (u->ascent * ppem + u->emsize - 1) / (u->emsize);
+	f->descentpx = (u->descent * ppem + u->emsize - 1) / (u->emsize);
+	if(ttfrunfpgm(f) < 0) goto error;
+	if(ttfruncvt(f) < 0) goto error;
+	return f;
+
+error:
+	ttfclose(f);
+	return nil;
+}
+
+TTFont *
+ttfopen(char *name, int ppem, int)
+{
+	Biobuf *b;
+	TTFontU *u;
+	
+	if(ppem < 0){
+		werrstr("invalid ppem argument");
+		return nil;
+	}
+	b = Bopen(name, OREAD);
+	if(b == nil)
+		return nil;
+	u = mallocz(sizeof(TTFontU), 1);
+	if(u == nil)
+		return nil;
+	u->bin = b;
+	u->nkern = -1;
+	directory(u);
+	if(ttfgototable(u, "head") < 0) goto error;
+	ttfunpack(u, "16 w W 16 wwww 6 w", &u->flags, &u->emsize, &u->xmin, &u->ymin, &u->xmax, &u->ymax, &u->longloca);
+	if(ttfgototable(u, "maxp") < 0) goto error;
+	ttfunpack(u, "4 wwwwwwwwwwwwww",
+		&u->numGlyphs, &u->maxPoints, &u->maxCountours, &u->maxComponentPoints, &u->maxComponentCountours,
+		&u->maxZones, &u->maxTwilightPoints, &u->maxStorage, &u->maxFunctionDefs, &u->maxInstructionDefs,
+		&u->maxStackElements, &u->maxSizeOfInstructions, &u->maxComponentElements, &u->maxComponentDepth);
+	u->ginfo = mallocz(sizeof(TTGlyphInfo) * (u->numGlyphs + 1), 1);
+	if(u->ginfo == nil)
+		goto error;
+	if(ttfgototable(u, "hhea") < 0) goto error;
+	ttfunpack(u, "10 wwww 16 w", &u->advanceWidthMax, &u->minLeftSideBearing, &u->minRightSideBearing, &u->xMaxExtent, &u->numOfLongHorMetrics);
+	if(ttfparseloca(u) < 0) goto error;
+	if(ttfparsehmtx(u) < 0) goto error;
+	if(ttfparsecvt(u) < 0) goto error;
+	if(ttfparsecmap(u) < 0) goto error;
+	if(ttfparseos2(u) < 0) goto error;
+	return ttfscaleu(u, ppem);
+
+error:
+	ttfcloseu(u);
+	return nil;
+}
+
+TTFont *
+ttfscale(TTFont *f, int ppem, int)
+{
+	return ttfscaleu(f->u, ppem);
+}
+
+int
+ttfrounddiv(int a, int b)
+{
+	if(b < 0){ a = -a; b = -b; }
+	if(a > 0)
+		return (a + b/2) / b;
+	else
+		return (a - b/2) / b;
+}
+
+int
+ttfvrounddiv(vlong a, int b)
+{
+	if(b < 0){ a = -a; b = -b; }
+	if(a > 0)
+		return (a + b/2) / b;
+	else
+		return (a - b/2) / b;
+}
--- /dev/null
+++ b/sys/src/libttf/hint.c
@@ -1,0 +1,1668 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ttf.h>
+#include "impl.h"
+
+typedef struct Hint Hint;
+
+enum { debug = 0 };
+
+#pragma varargck type "π" TTPoint
+
+#define dprint(...) {if(debug) fprint(2, __VA_ARGS__);}
+
+static TTGState defstate = {
+	.fvx = 16384,
+	.fvy = 0,
+	.pvx = 16384,
+	.pvy = 0,
+	.dpvx = 16384,
+	.dpvy = 0,
+	.instctrl = 0,
+	.scanctrl = 0,
+	.rperiod = 64,
+	.rphase = 0,
+	.rthold = 32,
+	.zp = 7,
+	.cvci = 68,
+	.loop = 1,
+	.singlewval = 0,
+	.singlewci = 0,
+	.deltabase = 9,
+	.deltashift = 3,
+	.autoflip = 1,
+	.mindist = 64,
+};
+
+struct Hint {
+	TTFont *f;
+	TTGlyph *g;
+	u8int *shint, *ip, *ehint;
+	u32int *stack;
+	int sp, nstack;
+	int level;
+	char err[ERRMAX];
+	jmp_buf jmp;
+};
+
+int
+rounddiv(int a, int b)
+{
+	if(b < 0){ a = -a; b = -b; }
+	if(a > 0)
+		return (a + b/2) / b;
+	else
+		return (a - b/2) / b;
+}
+
+int
+vrounddiv(vlong a, int b)
+{
+	if(b < 0){ a = -a; b = -b; }
+	if(a > 0)
+		return (a + b/2) / b;
+	else
+		return (a - b/2) / b;
+}
+
+static void
+herror(Hint *h, char *fmt, ...)
+{
+	va_list va;
+	
+	va_start(va, fmt);
+	vsnprint(h->err, sizeof(h->err), fmt, va);
+	va_end(va);
+	dprint("error: %s\n", h->err);
+	longjmp(h->jmp, 1);
+}
+
+static void
+push(Hint *h, u32int w)
+{
+	assert(h->sp < h->nstack);
+	h->stack[h->sp++] = w;
+}
+
+static u32int
+pop(Hint *h)
+{
+	assert(h->sp > 0);
+	return h->stack[--h->sp];
+}
+
+static u8int
+fetch8(Hint *h)
+{
+	if(h->ip == h->ehint)
+		herror(h, "missing byte");
+	return *h->ip++;
+}
+
+enum {
+	RP0 = 0x10,
+	RP1 = 0x20,
+	RP2 = 0x30,
+	ZP0 = 0,
+	ZP1 = 0x100,
+	ZP2 = 0x200,
+	ORIG = 0x1000,
+	NOTOUCH = 0x2000,
+};
+
+static TTPoint
+getpoint(Hint *h, int n, int pi)
+{
+	if((n & RP2) != 0)
+		pi = h->f->rp[(n >> 4 & 3) - 1];
+	if((h->f->zp >> (n >> 8 & 3) & 1) != 0){
+		if(h->g == nil)
+			herror(h, "access to glyph zone from FPGM/CVT");
+		if((uint)pi >= h->g->npt)
+			herror(h, "glyph zone point index %d out of range", n);
+		dprint("G%s%d: %+π\n", n&ORIG?"O":"", pi, (n & ORIG) != 0 ? h->g->ptorg[pi] : h->g->pt[pi]);
+		return (n & ORIG) != 0 ? h->g->ptorg[pi] : h->g->pt[pi];
+	}else{
+		if((uint)pi >= h->f->u->maxTwilightPoints)
+			herror(h, "twilight zone point index %d out of range", pi);
+		return (n & ORIG) != 0 ? h->f->twiorg[pi] : h->f->twilight[pi];
+	}
+}
+
+static void
+setpoint(Hint *h, int n, int pi, TTPoint p)
+{
+	if((n & RP2) != 0)
+		pi = h->f->rp[(n >> 4 & 3) - 1];
+	if((n & NOTOUCH) == 0){
+		if(h->f->fvx != 0) p.flags |= 2;
+		if(h->f->fvy != 0) p.flags |= 4;
+	}
+	if((h->f->zp >> (n >> 8 & 3) & 1) != 0){
+		if(h->g == nil)
+			herror(h, "access to glyph zone from FPGM/CVT");
+		if((uint)pi >= h->g->npt)
+			herror(h, "glyph zone point index %d out of range", n);
+		dprint("G%d: %+π -> %+π\n", pi, h->g->pt[pi], p);
+		h->g->pt[pi] = p;
+	}else{
+		if((uint)pi >= h->f->u->maxTwilightPoints)
+			herror(h, "twilight zone point index %d out of range", n);
+		dprint("T%d: %+π -> %+π\n", pi, h->f->twilight[pi], p);
+		h->f->twilight[pi] = p;
+	}
+}
+
+static void
+debugprint(Hint *h, int skip)
+{
+	Fmt f;
+	char buf[256];
+
+	static char *opcnames[256] = {
+		[0x00] "SVTCA", "SVTCA", "SPVTCA", "SPVTCA", "SFVTCA", "SFVTCA", "SPVTL", "SPVTL",
+		[0x08] "SFVTL", "SFVTL", "SPVFS", "SFVFS", "GPV", "GFV", "SFVTPV", "ISECT",
+		[0x10] "SRP0", "SRP1", "SRP2", "SZP0", "SZP1", "SZP2", "SZPS", "SLOOP",
+		[0x18] "RTG", "RTHG", "SMD", "ELSE", "JMPR", "SCVTCI", "SSWCI", "SSW",
+		[0x20] "DUP", "POP", "CLEAR", "SWAP", "DEPTH", "CINDEX", "MINDEX", "ALIGNPTS",
+		[0x28] nil, "UTP", "LOOPCALL", "CALL", "FDEF", "ENDF", "MDAP", "MDAP",
+		[0x30] "IUP", "IUP", "SHP", "SHP", "SHC", "SHC", "SHZ", "SHZ",
+		[0x38] "SHPIX", "IP", "MSIRP", "MSIRP", "ALIGNRP", "RTDG", "MIAP", "MIAP",
+		[0x40] "NPUSHB", "NPUSHW", "WS", "RS", "WCVTP", "RCVT", "GC", "GC",
+		[0x48] "SCFS", "MD", "MD", "MPPEM", "MPS", "FLIPON", "FLIPOFF", "DEBUG",
+		[0x50] "LT", "LTEQ", "GT", "GTEQ", "EQ", "NEQ", "ODD", "EVEN",
+		[0x58] "IF", "EIF", "AND", "OR", "NOT", "DELTAP1", "SDB", "SDS",
+		[0x60] "ADD", "SUB", "DIV", "MUL", "ABS", "NEG", "FLOOR", "CEILING",
+		[0x68] "ROUND", "ROUND", "ROUND", "ROUND", "NROUND", "NROUND", "NROUND", "NROUND",
+		[0x70] "WCVTF", "DELTAP2", "DELTAP3", "DELTAC1", "DELTAC2", "DELTAC3", "SROUND", "S45ROUND",
+		[0x78] "JROT", "JROF", "ROFF", nil, "RUTG", "RDTG", "SANGW", "AA",
+		[0x80] "FLIPPT", "FLIPRGON", "FLIPRGOFF", [0x85] "SCANCTRL", "SDPVTL", "SDPVTL",
+		[0x88] "GETINFO", "IDEF", "ROLL", "MAX", "MIN", "SCANTYPE", "INSTCTRL", nil,
+		[0xB0] "PUSHB", "PUSHB", "PUSHB", "PUSHB", "PUSHB", "PUSHB", "PUSHB", "PUSHB", 
+		[0xB8] "PUSHW", "PUSHW", "PUSHW", "PUSHW", "PUSHW", "PUSHW", "PUSHW", "PUSHW",
+	};
+	static u8int argb[256] = {
+		[0x00] 1, 1, 1, 1, 1, 1, 1, 1,
+		[0x08] 1, 1, 1, 1, 1, 1,
+		[0x2e] 1, 1,
+		[0x30] 1, 1, 1, 1, 1, 1, 1, 1,
+		[0x38] 0, 0, 1, 1, 0, 0, 1, 1,
+		[0x46] 1, 1, 0, 1, 1,
+		[0x68] 2, 2, 2, 2, 2, 2, 2, 2,
+	};
+	u8int op;
+	int i;
+
+	fmtfdinit(&f, 2, buf, sizeof(buf));
+	op = *h->ip;
+	if(skip) fmtprint(&f, "** ");
+	fmtprint(&f, "%d %d ", h->level, (int)(h->ip - h->shint));
+	if(op >= 0xc0)
+		fmtprint(&f, "%s[%d]", op >= 0xe0 ? "MIRP" : "MDRP", op & 0x1f);
+	else if(opcnames[op] == nil)
+		fmtprint(&f, "???");
+	else
+		fmtprint(&f, argb[op] != 0 ? "%s[%d]" : "%s[]", opcnames[op], op & (1<<argb[op]) - 1);
+	if(!skip){
+		fmtprint(&f, " :: ");
+		for(i = 0; i < 8 && i < h->sp; i++)
+			fmtprint(&f, "%d ", h->stack[h->sp - 1 - i]);
+	}
+	fmtprint(&f, "\n");
+	fmtfdflush(&f);
+}
+
+static void
+h_npushb(Hint *h)
+{
+	u8int n, b;
+	
+	n = fetch8(h);
+	while(n-- > 0){
+		b = fetch8(h);
+		push(h, b);
+	}
+}
+
+static void
+h_npushw(Hint *h)
+{
+	u8int n;
+	u32int x;
+	
+	n = fetch8(h);
+	while(n-- > 0){
+		x = fetch8(h) << 8;
+		x |= fetch8(h);
+		push(h, (short)x);
+	}
+}
+
+static void
+h_pushb(Hint *h)
+{
+	int n;
+	u8int b;
+	
+	n = (h->ip[-1] & 7) + 1;
+	while(n-- > 0){
+		b = fetch8(h);
+		push(h, b);
+	}
+}
+
+static void
+h_pushw(Hint *h)
+{
+	int n;
+	u16int w;
+	
+	n = (h->ip[-1] & 7) + 1;
+	while(n-- > 0){
+		w = fetch8(h) << 8;
+		w |= fetch8(h);
+		push(h, (short)w);
+	}
+}
+
+static void
+skip(Hint *h, int mode)
+{
+	int level;
+
+	level = 0;
+	for(;;){
+		if(h->ip >= h->ehint)
+			herror(h, "reached end of stream during skip()");
+		if(debug) debugprint(h, 1);
+		switch(mode){
+		case 0:
+			if(*h->ip == 0x2d)
+				return;
+			break;
+		case 1:
+			if(level == 0 && (*h->ip == 0x1b || *h->ip == 0x59))
+				return;
+		}
+		switch(*h->ip++){
+		case 0x40:
+		case 0x41:
+			if(h->ip < h->ehint)
+				h->ip += *h->ip + 1;
+			break;
+		case 0x58: level++; break;
+		case 0x59: level--; break;
+		case 0xb0: case 0xb1: case 0xb2: case 0xb3:
+		case 0xb4: case 0xb5: case 0xb6: case 0xb7:
+			h->ip += (h->ip[-1] & 7) + 1;
+			break;
+		case 0xb8: case 0xb9: case 0xba: case 0xbb:
+		case 0xbc: case 0xbd: case 0xbe: case 0xbf:
+			h->ip += 2 * ((h->ip[-1] & 7) + 1);
+			break;
+		}
+	}
+}
+
+static void
+h_fdef(Hint *h)
+{
+	int i;
+	u8int *sp;
+	TTFont *f;
+	
+	f = h->f;
+	i = pop(h);
+	if((uint)i >= h->f->u->maxFunctionDefs)
+		herror(h, "function identifier out of range");
+	sp = h->ip;
+	skip(h, 0);
+	f->func[i].npgm = h->ip - sp;
+	f->func[i].pgm = mallocz(f->func[i].npgm, 1);
+	if(f->func[i].pgm == nil)
+		herror(h, "malloc: %r");
+	memcpy(f->func[i].pgm, sp, f->func[i].npgm);
+	h->ip++;
+}
+
+static void run(Hint *);
+
+static void
+h_call(Hint *h)
+{
+	int i;
+	u8int *lip, *lshint, *lehint;
+	
+	i = pop(h);
+	if((uint)i >= h->f->u->maxFunctionDefs || h->f->func[i].npgm == 0)
+		herror(h, "undefined funcion %d", i);
+	lip = h->ip;
+	lshint = h->shint;
+	lehint = h->ehint;
+	h->ip = h->shint = h->f->func[i].pgm;
+	h->ehint = h->ip + h->f->func[i].npgm;
+	h->level++;
+	run(h);
+	h->level--;
+	h->ip = lip;
+	h->shint = lshint;
+	h->ehint = lehint;
+}
+
+static void
+h_loopcall(Hint *h)
+{
+	int i, n;
+	u8int *lip, *lshint, *lehint;
+	
+	i = pop(h);
+	n = pop(h);
+	if((uint)i >= h->f->u->maxFunctionDefs || h->f->func[i].npgm == 0)
+		herror(h, "undefined funcion %d", i);
+	for(; n > 0; n--){
+		lip = h->ip;
+		lshint = h->shint;
+		lehint = h->ehint;
+		h->ip = h->shint = h->f->func[i].pgm;
+		h->ehint = h->ip + h->f->func[i].npgm;
+		h->level++;
+		run(h);
+		h->level--;
+		h->ip = lip;
+		h->shint = lshint;
+		h->ehint = lehint;
+	}
+}
+
+static void
+h_dup(Hint *h)
+{
+	u32int x;
+	
+	x = pop(h);
+	push(h, x);
+	push(h, x);
+}
+
+static void
+h_swap(Hint *h)
+{
+	u32int x, y;
+	
+	x = pop(h);
+	y = pop(h);
+	push(h, x);
+	push(h, y);
+}
+
+static void
+h_cindex(Hint *h)
+{
+	int n;
+	
+	n = pop(h);
+	if(n <= 0 || n > h->sp)
+		herror(h, "CINDEX[%d] out of range", n);
+	push(h, h->stack[h->sp - n]);
+}
+
+static void
+h_mindex(Hint *h)
+{
+	int n, x;
+	
+	n = pop(h);
+	if(n <= 0 || n > h->sp)
+		herror(h, "MINDEX[%d] out of range", n);
+	x = h->stack[h->sp - n];
+	memmove(&h->stack[h->sp - n], &h->stack[h->sp - n + 1], (n - 1) * sizeof(u32int));
+	h->stack[h->sp - 1] = x;
+}
+
+static void
+h_svtca(Hint *h)
+{
+	int a;
+	
+	a = h->ip[-1];
+	if(a < 2 || a >= 4){
+		h->f->fvx = 16384 * (a & 1);
+		h->f->fvy = 16384 * (~a & 1);
+	}
+	if(a < 4){
+		h->f->dpvx = h->f->pvx = 16384 * (a & 1);
+		h->f->dpvy = h->f->pvy = 16384 * (~a & 1);
+	}
+}
+
+static void
+h_instctrl(Hint *h)
+{
+	int s, v;
+	
+	s = pop(h);
+	v = pop(h);
+	if(v != 0)
+		h->f->instctrl |= 1<<s;
+	else
+		h->f->instctrl &= ~(1<<s);
+}
+
+static void
+h_mppem(Hint *h)
+{
+	push(h, h->f->ppem);
+}
+
+static int
+ttround(Hint *h, int x)
+{
+	int y;
+	
+	if(h->f->rperiod == 0) return x;
+	if(x >= 0){
+		y = x - h->f->rphase + h->f->rthold;
+		y -= y % h->f->rperiod;
+		y += h->f->rphase;
+		if(y < 0) y = h->f->rphase;
+	}else{
+		y = x + h->f->rphase - h->f->rthold;
+		y -= y % h->f->rperiod;
+		y -= h->f->rphase;
+		if(y > 0) y = -h->f->rphase;
+	}
+	return y;
+}
+
+static void
+h_binop(Hint *h)
+{
+	int a, b, r;
+	
+	b = pop(h);
+	a = pop(h);
+	switch(h->ip[-1]){
+	case 0x50: r = a < b; break;
+	case 0x51: r = a <= b; break;
+	case 0x52: r = a > b; break;
+	case 0x53: r = a >= b; break;
+	case 0x54: r = a == b; break;
+	case 0x55: r = a != b; break;
+	case 0x5a: r = a && b; break;
+	case 0x5b: r = a || b; break;
+	case 0x60: r = a + b; break;
+	case 0x61: r = a - b; break;
+	case 0x62: if(b == 0) herror(h, "division by zero"); r = (vlong)(int)a * 64 / (int)b; break;
+	case 0x63: r = (vlong)(int)a * (vlong)(int)b >> 6; break;
+	case 0x8b: r = a < b ? b : a; break;
+	case 0x8c: r = a < b ? a : b; break;
+	default: SET(r); abort();
+	}
+	push(h, r);
+}
+
+static void
+h_unop(Hint *h)
+{
+	u32int a, r;
+	
+	a = pop(h);
+	switch(h->ip[-1]){
+	case 0x56: r = (ttround(h, a) / 64 & 1) != 0; break;
+	case 0x57: r = (ttround(h, a) / 64 & 1) == 0; break;
+	case 0x5c: r = !a; break;
+	case 0x64: r = (int)a < 0 ? -a : a; break;
+	case 0x65: r = -a; break;
+	case 0x66: r = a & -64; break;
+	case 0x67: r = -(-a & -64); break;
+	case 0x68: case 0x69: case 0x6a: case 0x6b: r = ttround(h, a); break;
+	default: SET(r); abort();
+	}
+	push(h, r);
+}
+
+static void
+h_rs(Hint *h)
+{
+	int n;
+	
+	n = pop(h);
+	if((uint)n >= h->f->u->maxStorage)
+		herror(h, "RS[%d] out of bounds");
+	push(h, h->f->storage[n]);
+}
+
+static void
+h_ws(Hint *h)
+{
+	u32int v;
+	int n;
+	
+	v = pop(h);
+	n = pop(h);
+	if((uint)n >= h->f->u->maxStorage)
+		herror(h, "WS[%d] out of bounds");
+	h->f->storage[n] = v;
+}
+
+static void
+h_if(Hint *h)
+{
+	u32int x;
+	
+	x = pop(h);
+	if(!x){
+		skip(h, 1);
+		h->ip++;
+	}
+}
+
+static void
+h_else(Hint *h)
+{
+	skip(h, 1);
+	h->ip++;
+}
+
+static void
+h_nop(Hint *)
+{
+}
+
+static void
+h_getinfo(Hint *h)
+{
+	int s;
+	u32int r;
+	
+	s = pop(h);
+	r = 0;
+	if((s & 1) != 0) r |= 3;
+	push(h, r);
+}
+
+static void
+h_scanctrl(Hint *h)
+{
+	h->f->scanctrl = pop(h);
+}
+
+static void
+h_scantype(Hint *h)
+{
+	h->f->scantype = pop(h);
+}
+
+static void
+h_roundst(Hint *h)
+{
+	h->f->rperiod = 64;
+	h->f->rphase = 0;
+	h->f->rthold = 32;
+	switch(h->ip[-1]){
+	case 0x19: /* RTHG */
+		h->f->rphase = 32;
+		break;
+	case 0x3D: /* RTDG */
+		h->f->rperiod = 32;
+		h->f->rthold = 16;
+		break;
+	case 0x7C: /* RUTG */
+		h->f->rthold = 63;
+		break;
+	case 0x7D: /* RDTG */
+		h->f->rthold = 0;
+		break;
+	case 0x7A: /* ROFF */
+		h->f->rperiod = 0;
+		break;
+	}
+}
+
+static void
+h_sround(Hint *h)
+{
+	u8int n;
+	
+	n = pop(h);
+	if((n >> 6 & 3) == 3)
+		herror(h, "(S)ROUND: period set to reserved value 3");
+	if(h->ip[-1] == 0x77)
+		h->f->rperiod = 181 >> (2 - (n >> 6 & 3));
+	else
+		h->f->rperiod = 32 << (n >> 6 & 3);
+	h->f->rphase = h->f->rperiod * (n >> 4 & 3) / 4;
+	if((n & 15) == 0)
+		h->f->rthold = h->f->rperiod - 1;
+	else
+		h->f->rthold = h->f->rperiod * ((int)(n & 15) - 4) / 8;
+}
+
+static void
+h_srp(Hint *h)
+{
+	h->f->rp[h->ip[-1] & 3] = pop(h);
+}
+
+static void
+h_szp(Hint *h)
+{
+	int n, t;
+	
+	n = pop(h);
+	if(n>>1 != 0) herror(h, "SZP invalid argument %d", n);
+	t = h->ip[-1] - 0x13;
+	if(t == 3) h->f->zp = 7 * n;
+	else h->f->zp = h->f->zp & ~(1<<t) | n<<t;
+}
+
+static int
+project(Hint *h, TTPoint *p, TTPoint *q)
+{
+	if(q == nil)
+		return rounddiv(h->f->pvx * p->x + h->f->pvy * p->y, 16384);
+	return rounddiv(h->f->pvx * (p->x - q->x) + h->f->pvy * (p->y - q->y), 16384);
+}
+
+static int
+dualproject(Hint *h, TTPoint *p, TTPoint *q)
+{
+	if(q == nil)
+		return rounddiv(h->f->dpvx * p->x + h->f->dpvy * p->y, 16384);
+	return rounddiv(h->f->dpvx * (p->x - q->x) + h->f->dpvy * (p->y - q->y), 16384);
+}
+
+static TTPoint
+forceproject(Hint *h, TTPoint p, int d)
+{
+	TTFont *f;
+	TTPoint n;
+	int den;
+	vlong k;
+
+	f = h->f;
+	den = f->pvx * f->fvx + f->pvy * f->fvy;
+	if(den == 0) herror(h, "FV and PV orthogonal");
+	k = f->fvx * p.y - f->fvy * p.x;
+	n.x = vrounddiv(16384LL * d * f->fvx - k * f->pvy, den);
+	n.y = vrounddiv(16384LL * d * f->fvy + k * f->pvx, den);
+	n.flags = p.flags;
+	return n;
+}
+
+static void
+h_miap(Hint *h)
+{
+	int a, pi, di, d, d0, d1;
+	TTPoint p, n;
+	
+	a = h->ip[-1] & 1;
+	di = pop(h);
+	pi = pop(h);
+	if((uint)di >= h->f->ncvt) herror(h, "MIAP out of range");
+	p = getpoint(h, ZP0, pi);
+	d0 = h->f->cvt[di];
+	dprint("cvt %d\n", d0);
+	d1 = project(h, &p, nil);
+	dprint("old %d\n", d1);
+	d = d0;
+	if((h->f->zp & 1) != 0){
+		if(a && abs(d1 - d) > h->f->cvci)
+			d = d1;
+	}else{
+		/* fuck you microsoft */
+		h->f->twiorg[pi].x = rounddiv(d0 * h->f->pvx, 16384);
+		h->f->twiorg[pi].y = rounddiv(d0 * h->f->pvy, 16384);
+	}
+	if(a) d = ttround(h, d);
+	n = forceproject(h, p, d);
+	setpoint(h, 0x80, pi, n);
+	h->f->rp[0] = h->f->rp[1] = pi;
+}
+
+static void
+h_mdap(Hint *h)
+{
+	int pi;
+	TTPoint p;
+	
+	pi = pop(h);
+	p = getpoint(h, ZP0, pi);
+	if((h->ip[-1] & 1) != 0)
+		p = forceproject(h, p, ttround(h, project(h, &p, nil)));
+	setpoint(h, ZP0, pi, p);
+	h->f->rp[0] = h->f->rp[1] = pi;
+}
+
+static void
+h_ip(Hint *h)
+{
+	int i;
+	int pi;
+	TTPoint p1, op1, p2, op2, p, op, n;
+	int dp1, dp2, do12, d;
+
+	p1 = getpoint(h, RP1 | ZP0, 0);
+	op1 = getpoint(h, RP1 | ZP0 | ORIG, 0);
+	p2 = getpoint(h, RP2 | ZP1, 0);
+	op2 = getpoint(h, RP2 | ZP1 | ORIG, 0);
+	dp1 = project(h, &p1, nil);
+	dp2 = project(h, &p2, nil);
+	do12 = dualproject(h, &op1, &op2);
+	if(do12 == 0)
+		herror(h, "invalid IP[] call");
+	for(i = 0; i < h->f->loop; i++){
+		pi = pop(h);
+		p = getpoint(h, ZP2, pi);
+		op = getpoint(h, ZP2 | ORIG, pi);
+		d = ttfvrounddiv((vlong)dp1 * dualproject(h, &op, &op2) - (vlong)dp2 * dualproject(h, &op, &op1), do12);
+		n = forceproject(h, p, d);
+		setpoint(h, 0x82, pi, n);
+		dprint("(%d,%d) -> (%d,%d)\n", p.x, p.y, n.x, n.y);
+	}
+	h->f->loop = 1;
+}
+
+static void
+h_gc0(Hint *h)
+{
+	int pi;
+	TTPoint p;
+	
+	pi = pop(h);
+	p = getpoint(h, ZP2, pi);
+	push(h, project(h, &p, nil));
+}
+
+static void
+h_gc1(Hint *h)
+{
+	int pi;
+	TTPoint p;
+	
+	pi = pop(h);
+	p = getpoint(h, ZP2|ORIG, pi);
+	push(h, dualproject(h, &p, nil));
+}
+
+static void
+h_wcvtp(Hint *h)
+{
+	u32int v, l;
+	
+	v = pop(h);
+	l = pop(h);
+	if(l >= h->f->ncvt) herror(h, "WCVTP out of range");
+	h->f->cvt[l] = v;
+}
+
+static void
+h_wcvtf(Hint *h)
+{
+	u32int v, l;
+	
+	v = pop(h);
+	l = pop(h);
+	if(l >= h->f->ncvt) herror(h, "WCVTF out of range");
+	h->f->cvt[l] = rounddiv(v * h->f->ppem * 64, h->f->u->emsize);
+}
+
+static void
+h_rcvt(Hint *h)
+{
+	u32int l;
+	
+	l = pop(h);
+	if(l >= h->f->ncvt) herror(h, "RCVT out of range");
+	push(h, h->f->cvt[l]);
+}
+
+static void
+h_round(Hint *h)
+{
+	push(h, ttround(h, pop(h)));
+}
+
+static void
+h_roll(Hint *h)
+{
+	u32int a, b, c;
+	
+	a = pop(h);
+	b = pop(h);
+	c = pop(h);
+	push(h, b);
+	push(h, a);
+	push(h, c);
+}
+
+static void
+h_pop(Hint *h)
+{
+	pop(h);
+}
+
+static void
+h_clear(Hint *h)
+{
+	h->sp = 0;
+}
+
+static void
+h_depth(Hint *h)
+{
+	push(h, h->sp);
+}
+
+static void
+h_scvtci(Hint *h)
+{
+	h->f->cvci = pop(h);
+}
+
+static void
+h_mirp(Hint *h)
+{
+	int a;
+	u32int cvti, pi;
+	TTPoint n, p, p0, op, op0;
+	int d0, d;
+	
+	a = h->ip[-1] & 31;
+	cvti = pop(h);
+	pi = pop(h);
+	if(cvti >= h->f->ncvt)
+		herror(h, "MIRP out of bounds");
+	d = h->f->cvt[cvti];
+	dprint("cvt %d\n", d);
+	if(abs(d - h->f->singlewval) < h->f->singlewci)
+		d = d < 0 ? -h->f->singlewci : h->f->singlewci;
+	dprint("single %d\n", d);
+	p = getpoint(h, ZP1, pi);
+	p0 = getpoint(h, ZP0 | RP0, 0);
+	op = getpoint(h, ZP1 | ORIG, pi);
+	op0 = getpoint(h, ZP0 | RP0 | ORIG, 0);
+	d0 = dualproject(h, &op, &op0);
+	if(h->f->autoflip && (d0 ^ d) < 0)
+		d = -d;
+	if((a & 4) != 0){
+		if((h->f->zp + 1 & 3) <= 1 && abs(d - d0) > h->f->cvci)
+			d = d0;
+		dprint("cutin %d (%d)\n", d, h->f->cvci);
+		d = ttround(h, d);
+	}
+	dprint("round %d\n", d);
+	if((a & 8) != 0)
+		if(d0 >= 0){
+			if(d < h->f->mindist)
+				d = h->f->mindist;
+		}else{
+			if(d > -h->f->mindist)
+				d = -h->f->mindist;
+		}
+	dprint("mindist %d (%d)\n", d, h->f->mindist);
+	d += project(h, &p0, nil);
+	dprint("total %d\n", d);
+	n = forceproject(h, p, d);
+	setpoint(h, ZP1, pi, n);
+	h->f->rp[1] = h->f->rp[0];
+	h->f->rp[2] = pi;
+	if((a & 16) != 0)
+		h->f->rp[0] = pi;
+}
+
+static void
+h_msirp(Hint *h)
+{
+	int a;
+	u32int pi;
+	TTPoint n, p, p0;
+	int d;
+	
+	a = h->ip[-1] & 31;
+	d = pop(h);
+	pi = pop(h);
+	if(abs(d - h->f->singlewval) < h->f->singlewci)
+		d = d < 0 ? -h->f->singlewci : h->f->singlewci;
+	p = getpoint(h, ZP1, pi);
+	p0 = getpoint(h, ZP0 | RP0, 0);
+	d += project(h, &p0, nil);
+	n = forceproject(h, p, d);
+	setpoint(h, ZP1, pi, n);
+	h->f->rp[1] = h->f->rp[0];
+	h->f->rp[2] = pi;
+	if((a & 1) != 0)
+		h->f->rp[0] = pi;
+}
+
+static void
+h_deltac(Hint *h)
+{
+	int n, b, c, arg;
+	
+	n = pop(h);
+	b = (h->ip[-1] - 0x73) * 16 + h->f->deltabase;
+	while(n--){
+		c = pop(h);
+		arg = pop(h);
+		if(h->f->ppem != b + (arg >> 4)) continue;
+		arg &= 0xf;
+		arg = arg + (arg >> 3) - 8 << h->f->deltashift;
+		if((uint)c >= h->f->ncvt) herror(h, "DELTAC argument out of range");
+		h->f->cvt[c] += arg;
+	}
+}
+
+static void
+h_deltap(Hint *h)
+{
+	int cnt, b, pi, arg;
+	TTPoint p, n;
+	
+	cnt = pop(h);
+	b = (h->ip[-1] == 0x5d ? 0 : h->ip[-1] - 0x70) * 16 + h->f->deltabase;
+	while(cnt--){
+		pi = pop(h);
+		arg = pop(h);
+		if(h->f->ppem != b + (arg >> 4)) continue;
+		arg &= 0xf;
+		arg = arg + (arg >> 3) - 8 << h->f->deltashift;
+		p = getpoint(h, ZP0, pi);
+		n = forceproject(h, p, project(h, &p, nil) + arg);
+		setpoint(h, ZP0, pi, n);
+	}
+}
+
+static void
+h_jmpr(Hint *h)
+{
+	h->ip += (int)pop(h) - 1;
+	if(h->ip < h->shint || h->ip > h->ehint)
+		herror(h, "JMPR out of bounds");
+}
+
+static void
+h_jrcond(Hint *h)
+{
+	u32int e;
+	int n;
+	
+	e = pop(h);
+	n = pop(h) - 1;
+	if((e == 0) == (h->ip[-1] & 1)){
+		h->ip += n;
+		if(h->ip < h->shint || h->ip > h->ehint)
+			herror(h, "JROT/JROF out of bounds");
+	}
+}
+
+static void
+h_smd(Hint *h)
+{
+	h->f->mindist = pop(h);
+}
+
+static void
+h_alignrp(Hint *h)
+{
+	int i, pi;
+	TTPoint p, q, n;
+	int dq;
+	
+	q = getpoint(h, ZP0 | RP0, 0);
+	dq = project(h, &q, nil);
+	for(i = 0; i < h->f->loop; i++){
+		pi = pop(h);
+		p = getpoint(h, ZP1, pi);
+		n = forceproject(h, p, dq);
+		setpoint(h, ZP1, pi, n);
+	}
+	h->f->loop = 1;
+}
+
+static TTPoint
+dirvec(TTPoint a, TTPoint b)
+{
+	TTPoint r;
+	double d;
+	
+	r.x = a.x - b.x;
+	r.y = a.y - b.y;
+	if(r.x == 0 && r.y == 0) r.x = 1<<14;
+	else{
+		d = hypot(r.x, r.y);
+		r.x = r.x / d * 16384;
+		r.y = r.y / d * 16384;
+	}
+	return r;
+}
+
+static void
+h_sxvtl(Hint *h)
+{
+	int pi1, pi2;
+	TTPoint p1, p2;
+	TTPoint p;
+	int z;
+	
+	pi2 = pop(h);
+	pi1 = pop(h);
+	p1 = getpoint(h, ZP1, pi1);
+	p2 = getpoint(h, ZP2, pi2);
+	p = dirvec(p1, p2);
+	if((h->ip[-1] & 1) != 0){
+		z = p.x;
+		p.x = -p.y;
+		p.y = z;
+	}
+	if(h->ip[-1] >= 8){
+		h->f->fvx = p.x;
+		h->f->fvy = p.y;
+	}else{
+		h->f->dpvx = h->f->pvx = p.x;
+		h->f->dpvy = h->f->pvy = p.y;
+	}
+}
+
+static void
+h_sfvfs(Hint *h)
+{
+	h->f->fvy = pop(h);
+	h->f->fvx = pop(h);
+}
+
+static void
+h_spvfs(Hint *h)
+{
+	h->f->dpvy = h->f->pvy = pop(h);
+	h->f->dpvx = h->f->pvx = pop(h);
+}
+
+static void
+h_gfv(Hint *h)
+{
+	push(h, h->f->fvx);
+	push(h, h->f->fvy);
+}
+
+static void
+h_gpv(Hint *h)
+{
+	push(h, h->f->pvx);
+	push(h, h->f->pvy);
+}
+
+static void
+h_mdrp(Hint *h)
+{
+	int pi;
+	TTPoint p, p0, op, op0, n;
+	int d, d0;
+	
+	pi = pop(h);
+	p = getpoint(h, ZP1, pi);
+	p0 = getpoint(h, ZP0 | RP0, 0);
+	op = getpoint(h, ZP1 | ORIG, pi);
+	op0 = getpoint(h, ZP0 | RP0 | ORIG, 0);
+	d = d0 = dualproject(h, &op, &op0);
+	if(abs(d - h->f->singlewval) < h->f->singlewci)
+		d = d >= 0 ? -h->f->singlewci : h->f->singlewci;
+	if((h->ip[-1] & 4) != 0)
+		d = ttround(h, d);
+	if((h->ip[-1] & 8) != 0)
+		if(d0 >= 0){
+			if(d < h->f->mindist)
+				d = h->f->mindist;
+		}else{
+			if(d > -h->f->mindist)
+				d = -h->f->mindist;
+		}
+	n = forceproject(h, p, d + project(h, &p0, nil));
+	setpoint(h, ZP1, pi, n);
+	h->f->rp[1] = h->f->rp[0];
+	h->f->rp[2] = pi;
+	if((h->ip[-1] & 16) != 0)
+		h->f->rp[0] = pi;
+}
+
+static void
+h_sdpvtl(Hint *h)
+{
+	int pi1, pi2;
+	TTPoint p1, p2;
+	TTPoint op1, op2;
+	TTPoint p;
+	
+	pi2 = pop(h);
+	pi1 = pop(h);
+	p1 = getpoint(h, ZP1, pi1);
+	p2 = getpoint(h, ZP2, pi2);
+	op1 = getpoint(h, ZP1 | ORIG, pi1);
+	op2 = getpoint(h, ZP2 | ORIG, pi2);
+	p = dirvec(p1, p2);
+	if((h->ip[-1] & 1) != 0){
+		h->f->pvx = -p.y;
+		h->f->pvy = p.x;
+	}else{
+		h->f->pvx = p.x;
+		h->f->pvy = p.y;
+	}
+	p = dirvec(op1, op2);
+	if((h->ip[-1] & 1) != 0){
+		h->f->dpvx = -p.y;
+		h->f->dpvy = p.x;
+	}else{
+		h->f->dpvx = p.x;
+		h->f->dpvy = p.y;
+	}
+}
+
+static void
+h_sfvtpv(Hint *h)
+{
+	h->f->fvx = h->f->pvx;
+	h->f->fvy = h->f->pvy;
+}
+
+static void
+h_sdb(Hint *h)
+{
+	h->f->deltabase = pop(h);
+}
+
+static void
+h_sds(Hint *h)
+{
+	h->f->deltashift = pop(h);
+}
+
+static void
+h_ssw(Hint *h)
+{
+	h->f->singlewval = pop(h);
+}
+
+static void
+h_sswci(Hint *h)
+{
+	h->f->singlewci = pop(h);
+}
+
+static void
+h_fliponoff(Hint *h)
+{
+	h->f->autoflip = h->ip[-1] & 1;
+}
+
+static void
+h_md0(Hint *h)
+{
+	TTPoint p0, p1;
+	
+	p1 = getpoint(h, ZP1, pop(h));
+	p0 = getpoint(h, ZP0, pop(h));
+	push(h, project(h, &p0, &p1));
+}
+
+static void
+h_md1(Hint *h)
+{
+	TTPoint p0, p1;
+	
+	p1 = getpoint(h, ZP1 | ORIG, pop(h));
+	p0 = getpoint(h, ZP0 | ORIG, pop(h));
+	push(h, dualproject(h, &p0, &p1));
+}
+
+static void
+h_shpix(Hint *h)
+{
+	int i, d, pi, dx, dy;
+	TTPoint p;
+	
+	d = pop(h);
+	dx = vrounddiv((vlong)h->f->fvx * d, 16384);
+	dy = vrounddiv((vlong)h->f->fvy * d, 16384);
+	for(i = 0; i < h->f->loop; i++){
+		pi = pop(h);
+		p = getpoint(h, ZP2, pi);
+		p.x += dx;
+		p.y += dy;
+		setpoint(h, ZP2, pi, p);
+	}
+	h->f->loop = 1;
+}
+
+static void
+iup1(Hint *h, int ip, int iq, int i, int e)
+{
+	TTGlyph *g;
+	int z;
+	
+	g = h->g;
+	if(g->ptorg[ip].x == g->ptorg[iq].x)
+		for(; i <= e; i++)
+			g->pt[i].x = g->ptorg[i].x + g->pt[iq].x - g->ptorg[iq].x;
+	else
+		for(; i <= e; i++){
+			z = (g->ptorg[i].x - g->ptorg[iq].x) * 64 / (g->ptorg[ip].x - g->ptorg[iq].x);
+			if(z < 0) z = 0;
+			else if(z > 64) z = 64;
+			g->pt[i].x = g->ptorg[i].x + (((g->pt[ip].x - g->ptorg[ip].x) * z + (g->pt[iq].x - g->ptorg[iq].x) * (64 - z)) /  64);
+		}
+}
+
+static void
+iup0(Hint *h, int ip, int iq, int i, int e)
+{
+	TTGlyph *g;
+	int z;
+	
+	g = h->g;
+	if(g->ptorg[ip].y == g->ptorg[iq].y)
+		for(; i <= e; i++)
+			g->pt[i].y = g->ptorg[i].y + g->pt[iq].y - g->ptorg[iq].y;
+	else
+		for(; i <= e; i++){
+			z = (g->ptorg[i].y - g->ptorg[iq].y) * 64 / (g->ptorg[ip].y - g->ptorg[iq].y);
+			if(z < 0) z = 0;
+			else if(z > 64) z = 64;
+			g->pt[i].y = g->ptorg[i].y + (((g->pt[ip].y - g->ptorg[ip].y) * z + (g->pt[iq].y - g->ptorg[iq].y) * (64 - z)) / 64);
+		}
+}
+
+static void
+h_iup(Hint *h)
+{
+	int i, j, t0, t1;
+	TTPoint *p;
+	void (*iupp)(Hint *, int, int, int, int);
+
+	iupp = (h->ip[-1] & 1) != 0 ? iup1 : iup0;
+	for(i = 0; i < h->g->ncon; i++){
+		t0 = t1 = -1;
+		for(j = h->g->confst[i]; j < h->g->confst[i+1]; j++){
+			p = &h->g->pt[j];
+			if((p->flags & TOUCHY>>(h->ip[-1]&1)) != 0){
+				if(t0 < 0)
+					t0 = j;
+				if(t1 >= 0)
+					iupp(h, t1, j, t1 + 1, j - 1);
+				t1 = j;
+			}
+		}
+		if(t1 != t0){
+			iupp(h, t1, t0, h->g->confst[i], t0 - 1);
+			iupp(h, t1, t0, t1 + 1, h->g->confst[i+1]-1);
+		}else if(t0 >= 0)
+			iupp(h, t0, t0, h->g->confst[i], h->g->confst[i+1]-1);
+	}
+	
+	for(i = 0; i < h->g->npt; i++)
+		dprint("%d: %+π\n", i, h->g->pt[i]);
+}
+
+static void
+h_sloop(Hint *h)
+{
+	int n;
+	
+	n = pop(h);
+	if(n <= 0)
+		herror(h, "SLOOP invalid argument %d", n);
+	h->f->loop = n;
+}
+
+static void
+h_scfs(Hint *h)
+{
+	int d, pi;
+	TTPoint p, n;
+	
+	d = pop(h);
+	pi = pop(h);
+	p = getpoint(h, ZP2, pi);
+	n = forceproject(h, p, d);
+	setpoint(h, ZP2, pi, n);
+}
+
+static void
+h_fliprg(Hint *h)
+{
+	int i, e;
+	
+	e = pop(h);
+	i = pop(h);
+	if(h->g == nil)
+		herror(h, "FLIPRG without glyph");
+	for(; i <= e; i++)
+		if((int)i < h->g->npt)
+			h->g->pt[i].flags = h->g->pt[i].flags & ~1 | h->ip[-1] & 1;
+}
+
+static void
+h_isect(Hint *h)
+{
+	int a0i, a1i, b0i, b1i, pi;
+	TTPoint a0, a1, b0, b1, p;
+	int n0x, n0y;
+	vlong n0c;
+	int n1x, n1y;
+	vlong n1c;
+	int Δ;
+	
+	a0i = pop(h);
+	a1i = pop(h);
+	b0i = pop(h);
+	b1i = pop(h);
+	pi = pop(h);
+	a0 = getpoint(h, ZP0, a0i);
+	a1 = getpoint(h, ZP0, a1i);
+	b0 = getpoint(h, ZP1, b0i);
+	b1 = getpoint(h, ZP1, b1i);
+	p = getpoint(h, ZP2, pi);
+	n0x = a1.y - a0.y;
+	n0y = a0.x - a1.x;
+	n0c = (vlong)n0x * a0.x + (vlong)n0y * a0.y;
+	n1x = b1.y - b0.y;
+	n1y = b0.x - b1.x;
+	n1c = (vlong)n1x * b0.x + (vlong)n1y * b0.y;
+	Δ = (vlong)n1x * n0y - (vlong)n0x * n1y;
+	if(Δ == 0){
+		p.x = ((a0.x + a1.x) / 2 + (b0.x + b1.x) / 2) / 2;
+		p.y = ((a0.y + a1.y) / 2 + (b0.y + b1.y) / 2) / 2;
+	}else{
+		p.x = vrounddiv(n0y * n1c - n1y * n0c, Δ);
+		p.y = vrounddiv(n1x * n0c - n0x * n1c, Δ);
+	}
+	p.flags |= TOUCH;
+	setpoint(h, ZP2, pi, p);
+}
+
+static void
+h_shp(Hint *h)
+{
+	int i;
+	TTPoint rp, orp;
+	int pi;
+	TTPoint p, n;
+	int d, dp;
+
+	if((h->ip[-1] & 1) != 0){
+		rp = getpoint(h, RP1|ZP0, 0);
+		orp = getpoint(h, RP1|ZP0|ORIG, 0);
+	}else{
+		rp = getpoint(h, RP2|ZP1, 0);
+		orp = getpoint(h, RP2|ZP1|ORIG, 0);
+	}
+	
+	d = project(h, &rp, &orp);
+	for(i = 0; i < h->f->loop; i++){
+		pi = pop(h);
+		p = getpoint(h, ZP2, pi);
+		dp = project(h, &p, nil);
+		n = forceproject(h, p, dp + d);
+		setpoint(h, ZP2, pi, n);
+	}
+	h->f->loop = 1;
+}
+
+static void
+h_shc(Hint *h)
+{
+	int i, c;
+	int rpi;
+	TTPoint rp, orp;
+	TTPoint p, n;
+	int d, dp;
+
+	if((h->ip[-1] & 1) != 0){
+		rpi = h->f->rp[1];
+		if(((h->f->zp ^ h->f->zp >> 2) & 1) != 0)
+			rpi = -1;
+		rp = getpoint(h, RP1|ZP0, 0);
+		orp = getpoint(h, RP1|ZP0|ORIG, 0);
+	}else{
+		rpi = h->f->rp[2];
+		if(((h->f->zp ^ h->f->zp >> 1) & 1) != 0)
+			rpi = -1;
+		rp = getpoint(h, RP2|ZP1, 0);
+		orp = getpoint(h, RP2|ZP1|ORIG, 0);
+	}
+	c = pop(h);
+	if(h->g == nil)
+		herror(h, "SHC[] outside of glyf program");
+	if((uint)c >= h->g->ncon)
+		herror(h, "contour %d out of range", c);
+	d = project(h, &rp, &orp);
+	for(i = h->g->confst[c]; i < h->g->confst[c+1]; i++){
+		if(i == rpi) continue;
+		p = getpoint(h, ZP2, i);
+		dp = project(h, &p, nil);
+		n = forceproject(h, p, dp + d);
+		setpoint(h, ZP2, i, n);
+	}
+	h->f->loop = 1;
+}
+
+static void (*itable[256])(Hint *) = {
+	[0x00] h_svtca, h_svtca, h_svtca, h_svtca, h_svtca, h_svtca,
+	[0x06] h_sxvtl, h_sxvtl, h_sxvtl, h_sxvtl,
+	[0x0a] h_spvfs,
+	[0x0b] h_sfvfs,
+	[0x0c] h_gpv,
+	[0x0d] h_gfv,
+	[0x0e] h_sfvtpv,
+	[0x0f] h_isect,
+	[0x10] h_srp, h_srp, h_srp,
+	[0x13] h_szp, h_szp, h_szp, h_szp,
+	[0x17] h_sloop,
+	[0x18] h_roundst, h_roundst,
+	[0x1a] h_smd,
+	[0x1b] h_else,
+	[0x1c] h_jmpr,
+	[0x1d] h_scvtci,
+	[0x1e] h_sswci,
+	[0x1f] h_ssw,
+	[0x20] h_dup,
+	[0x21] h_pop,
+	[0x22] h_clear,
+	[0x23] h_swap,
+	[0x24] h_clear,
+	[0x25] h_cindex,
+	[0x26] h_mindex,
+	[0x2a] h_loopcall,
+	[0x2b] h_call,
+	[0x2c] h_fdef,
+	[0x2e] h_mdap, h_mdap,
+	[0x30] h_iup, h_iup,
+	[0x32] h_shp, h_shp,
+	[0x34] h_shc, h_shc,
+	[0x38] h_shpix,
+	[0x39] h_ip,
+	[0x3a] h_msirp, h_msirp,
+	[0x3c] h_alignrp,
+	[0x3d] h_roundst,
+	[0x3e] h_miap, h_miap,
+	[0x40] h_npushb,
+	[0x41] h_npushw,
+	[0x42] h_ws,
+	[0x43] h_rs,
+	[0x44] h_wcvtp,
+	[0x45] h_rcvt,
+	[0x46] h_gc0, h_gc1,
+	[0x48] h_scfs,
+	[0x49] h_md0, h_md1,
+	[0x4b] h_mppem,
+	[0x4d] h_fliponoff, h_fliponoff,
+	[0x4f] h_nop,
+	[0x50] h_binop, h_binop, h_binop, h_binop, h_binop, h_binop,
+	[0x56] h_unop, h_unop,
+	[0x58] h_if,
+	[0x59] h_nop, /* endif */
+	[0x5a] h_binop, h_binop,
+	[0x5c] h_unop,
+	[0x5d] h_deltap,
+	[0x5e] h_sdb,
+	[0x5f] h_sds,
+	[0x60] h_binop, h_binop, h_binop, h_binop, h_unop, h_unop, h_unop, h_unop,
+	[0x68] h_unop, h_unop, h_unop, h_unop, h_nop, h_nop, h_nop, h_nop,
+	[0x70] h_wcvtf,
+	[0x71] h_deltap, h_deltap,
+	[0x73] h_deltac, h_deltac, h_deltac,
+	[0x76] h_sround, h_sround,
+	[0x78] h_jrcond, h_jrcond,
+	[0x7a] h_roundst,
+	[0x7c] h_roundst, h_roundst,
+	[0x7e] h_pop,
+	[0x7f] h_pop,
+	[0x81] h_fliprg, h_fliprg,
+	[0x85] h_scanctrl,
+	[0x86] h_sdpvtl, h_sdpvtl,
+	[0x88] h_getinfo,
+	[0x8a] h_roll,
+	[0x8b] h_binop, h_binop,
+	[0x8d] h_scantype,
+	[0x8e] h_instctrl,
+	[0xb0] h_pushb, h_pushb, h_pushb, h_pushb,
+	       h_pushb, h_pushb, h_pushb, h_pushb,
+	[0xb8] h_pushw, h_pushw, h_pushw, h_pushw,
+	       h_pushw, h_pushw, h_pushw, h_pushw,
+	[0xc0] h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp,
+	       h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp,
+	       h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp,
+	       h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp, h_mdrp,
+	[0xe0] h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp,
+	       h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp,
+	       h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp,
+	       h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp, h_mirp,
+};
+
+static int
+pointfmt(Fmt *f)
+{
+	TTPoint p;
+	
+	p = va_arg(f->args, TTPoint);
+	if((f->flags & FmtSign) != 0)
+		return fmtprint(f, "(%.2f,%.2f,%d)", (float)p.x/64, (float)p.y/64, p.flags);
+	else
+		return fmtprint(f, "(%d,%d,%d)", p.x, p.y, p.flags);
+}
+
+static void
+run(Hint *h)
+{
+	while(h->ip < h->ehint){
+		if(debug) debugprint(h, 0);
+		if(itable[*h->ip] == nil)
+			sysfatal("unknown hint instruction %#.2x", *h->ip);
+		else
+			itable[*h->ip++](h);
+	}
+}
+
+static int
+runpg(TTFont *f, TTGlyph *g, uchar *buf, int n)
+{
+	Hint h;
+	static int didfmt;
+
+	if(debug && !didfmt){
+		fmtinstall(L'π', pointfmt);
+		didfmt = 1;
+	}
+	memset(&h, 0, sizeof(Hint));
+	if(setjmp(h.jmp) != 0){
+		errstr(h.err, sizeof(h.err));
+		return -1;
+	}
+	h.g = g;
+	h.f = f;
+	h.stack = f->hintstack;
+	h.nstack = f->u->maxStackElements;
+	h.ip = h.shint = buf;
+	h.ehint = buf + n;
+	run(&h);
+	return 0;
+}
+
+int
+ttfhint(TTGlyph *g)
+{
+	int rc, i;
+
+	if((g->font->defstate.instctrl & 1<<1) != 0)
+		return 0;
+	dprint("HINT:\n");
+	if((g->font->defstate.instctrl & 1<<2) != 0)
+		g->font->TTGState = defstate;
+	else
+		g->font->TTGState = g->font->defstate;
+	rc = runpg(g->font, g, g->hint, g->nhint);
+	if(debug && rc >= 0){
+		for(i = 0; i < g->npt; i++)
+			dprint("%d: %+π\n", i, g->pt[i]);
+	}
+	return rc;
+}
+
+int
+ttfrunfpgm(TTFont *f)
+{
+	int len, rc;
+	u8int *buf;
+
+	f->TTGState = defstate;
+	f->defstate = defstate;
+	len = ttfgototable(f->u, "fpgm");
+	if(len <= 0)
+		return 0;
+	buf = mallocz(len, 1);
+	if(buf == nil)
+		return -1;
+	Bread(f->u->bin, buf, len);
+	dprint("FPGM:\n");
+	rc = runpg(f, nil, buf, len);
+	free(buf);
+	return rc;
+}
+
+int
+ttfruncvt(TTFont *f)
+{
+	int len, rc;
+	u8int *buf;
+
+	f->TTGState = defstate;
+	f->defstate = defstate;
+	len = ttfgototable(f->u, "prep");
+	if(len <= 0)
+		return 0;
+	buf = mallocz(len, 1);
+	if(buf == nil)
+		return -1;
+	Bread(f->u->bin, buf, len);
+	dprint("CVT:\n");
+	rc = runpg(f, nil, buf, len);
+	free(buf);
+	if(rc >= 0){
+		f->zp = 7;
+		f->rp[0] = 0;
+		f->rp[1] = 0;
+		f->rp[2] = 0;
+		f->loop = 1;
+		f->rperiod = 64;
+		f->rphase = 0;
+		f->rthold = 32;
+		f->fvx = 16384;
+		f->fvy = 0;
+		f->pvx = 16384;
+		f->pvy = 0;
+		f->dpvx = 16384;
+		f->dpvy = 0;
+		f->defstate = f->TTGState;
+	}
+	return rc;
+}
--- /dev/null
+++ b/sys/src/libttf/mkfile
@@ -1,0 +1,23 @@
+</$objtype/mkfile
+
+LIB=/$objtype/lib/libttf.a
+
+OFILES=\
+	head.$O \
+	cmap.$O \
+	hint.$O \
+	scan.$O \
+	glyf.$O \
+	render.$O \
+	bit.$O \
+
+HFILES=\
+	/sys/include/ttf.h\
+	impl.h\
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mksyslib
--- /dev/null
+++ b/sys/src/libttf/render.c
@@ -1,0 +1,274 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ttf.h>
+#include "impl.h"
+
+static int
+ttfparsekern(TTFontU *f)
+{
+	u16int ver, len, cov, ntab;
+	int i;
+
+	if(ttfgototable(f, "kern") < 0)
+		return -1;
+	ttfunpack(f, "ww", &ver, &ntab);
+	if(ver != 0)
+		return -1;
+	if(ntab == 0)
+		return -1;
+	for(i = 0; i < ntab; i++){
+		ttfunpack(f, "www", &ver, &len, &cov);
+		if((cov & 1) != 0) break;
+		Bseek(f->bin, len - 6, 1);
+	}
+	ttfunpack(f, "w6", &len);
+	f->nkern = len;
+	f->kern = mallocz(sizeof(TTKern) * len, 1);
+	for(i = 0; i < len; i++)
+		ttfunpack(f, "lS", &f->kern[i].idx, &f->kern[i].val);
+	return 0;
+}
+
+static int
+ttfkern(TTFont *f, int l, int r)
+{
+	u32int idx;
+	int a, b, c;
+	TTFontU *u;
+	
+	u = f->u;
+	if(u->nkern == 0)
+		return 0;
+	idx = l << 16 | r;
+	a = 0;
+	b = u->nkern - 1;
+	if(u->kern[a].idx > idx || u->kern[b].idx < idx)
+		return 0;
+	while(a <= b){
+		c = (a + b) / 2;
+		if(u->kern[c].idx < idx){
+			a = c + 1;
+		}else if(u->kern[c].idx > idx){
+			b = c - 1;
+		}else
+			return ttfrounddiv(u->kern[c].val * f->ppem, u->emsize);
+	}
+	return 0;
+}
+
+typedef struct {
+	TTBitmap *b;
+	TTFont *font;
+	TTGlyph **glyph;
+	int *gwidth;
+	char **cpos;
+	char *pp;
+	int nglyph, aglyph;
+	int linew;
+	int adj;
+	int nspc;
+	int oy, lh;
+	int spcidx;
+	int flags;
+	int spcw;
+	int spcminus;
+} Render;
+
+static int
+addglyph(Render *r, char *p, TTGlyph *g)
+{
+	void *v;
+	int k;
+
+	if(r->nglyph >= r->aglyph){
+		r->aglyph += 32;
+		v = realloc(r->glyph, sizeof(TTGlyph *) * r->aglyph);
+		if(v == nil) return -1;
+		r->glyph = v;
+		v = realloc(r->gwidth, sizeof(int) * r->aglyph);
+		if(v == nil) return -1;
+		r->gwidth = v;
+		v = realloc(r->cpos, sizeof(char *) * r->aglyph);
+		if(v == nil) return -1;
+		r->cpos = v;
+	}
+	r->glyph[r->nglyph] = g;
+	r->cpos[r->nglyph] = p;
+	r->gwidth[r->nglyph] = g->advanceWidthpx;
+	if(r->nglyph > 0){
+		k = ttfkern(r->font, r->glyph[r->nglyph-1]->idx, g->idx);
+		r->gwidth[r->nglyph-1] += k;
+		r->linew += k;
+	}
+	r->nglyph++;
+	r->linew += r->gwidth[r->nglyph-1];
+	if(g->idx == r->spcidx)
+		r->nspc++;
+	return 0;
+}
+
+static void
+flushglyphs(Render *r, int justify)
+{
+	int i, n, k, x, y;
+	int llen;
+	int adj, spcw, nspc, c;
+	TTFont *f;
+
+	f = r->font;
+	if((r->flags & TTFMODE) == TTFLALIGN && !justify)
+		while(r->nglyph > 0 && r->glyph[r->nglyph - 1]->idx == r->spcidx){
+			r->linew -= r->gwidth[--r->nglyph];
+			r->nspc--;
+		}
+	llen = r->linew;
+	k = n = r->nglyph;
+	nspc = r->nspc;
+	adj = (nspc * r->spcminus + 63) / 64;
+	if(r->linew - adj > r->b->width){
+		n = r->nglyph;
+		while(n > 0 && r->glyph[n - 1]->idx != r->spcidx)
+			llen -= r->gwidth[--n];
+		k = n;
+		while(n > 0 && r->glyph[n - 1]->idx == r->spcidx){
+			llen -= r->gwidth[--n];
+			nspc--;
+		}
+		if(n == 0){
+			while(n < r->nglyph && llen + r->gwidth[n] < r->b->width)
+				llen += r->gwidth[n++];
+			k = n;
+		}
+	}
+	if(justify){
+		if(nspc == 0)
+			spcw = 0;
+		else
+			spcw = (r->b->width - llen + nspc * r->spcw) * 64 / nspc;
+	}else
+		spcw = r->spcw * 64;
+	switch(r->flags & TTFMODE | justify * TTFJUSTIFY){
+	case TTFRALIGN:
+		x = r->b->width - llen;
+		break;
+	case TTFCENTER:
+		x = (r->b->width - llen)/2;
+		break;
+	default:
+		x = 0;
+	}
+	y = r->oy + f->ascentpx;
+	c = 0;
+	for(i = 0; i < k; i++){
+		if(r->glyph[i]->idx == r->spcidx){
+			c += spcw;
+			x += c >> 6;
+			c &= 63;
+			r->nspc--;
+		}else{
+			ttfblit(r->b, x + r->glyph[i]->xminpx, y - r->glyph[i]->ymaxpx, r->glyph[i], 0, 0, r->glyph[i]->width, r->glyph[i]->height);
+			x += r->gwidth[i];
+		}
+		r->linew -= r->gwidth[i];
+		ttfputglyph(r->glyph[i]);
+	}
+	if(n > 0)
+		r->pp = r->cpos[n-1];
+	r->oy += r->lh;
+	memmove(r->glyph, r->glyph + k, (r->nglyph - k) * sizeof(TTGlyph *));
+	memmove(r->cpos, r->cpos + k, (r->nglyph - k) * sizeof(char *));
+	memmove(r->gwidth, r->gwidth + k, (r->nglyph - k) * sizeof(int));
+	r->nglyph -= k;
+}
+
+TTBitmap *
+_ttfrender(TTFont *f, int (*getrune)(Rune *, char *), char *p, char *end, int w, int h, int flags, char **rp)
+{
+	Render r;
+	Rune ch;
+	int i, adj;
+	TTGlyph *g;
+	
+	if(rp != nil) *rp = p;
+	if(f->u->nkern < 0 && ttfparsekern(f->u) < 0)
+		f->u->nkern = 0;
+	memset(&r, 0, sizeof(Render));
+	r.flags = flags;
+	r.font = f;
+	r.b = ttfnewbitmap(w, h);
+	if(r.b == nil) goto error;
+	r.oy = 0;
+	r.lh = f->ascentpx + f->descentpx;
+	r.pp = p;
+	
+	g = ttfgetglyph(f, ttffindchar(f, ' '), 1);
+	r.spcidx = g->idx;
+	r.spcw = g->advanceWidthpx;
+	if((flags & TTFJUSTIFY) != 0)
+		r.spcminus = r.spcw * 21;
+	else
+		r.spcminus = 0;
+		
+	while(p < end && r.oy + r.lh < h){
+		p += getrune(&ch, p);
+		if(ch == '\n'){
+			flushglyphs(&r, 0);
+			continue;
+		}
+		g = ttfgetglyph(f, ttffindchar(f, ch), 1);
+		if(g == nil){
+			g = ttfgetglyph(f, 0, 1);
+			if(g == nil)
+				continue;
+		}
+		if(addglyph(&r, p, g) < 0)
+			goto error;
+		adj = (r.nspc * r.spcminus + 63) / 64;
+		if(r.linew - adj > r.b->width){
+			flushglyphs(&r, (flags & TTFJUSTIFY) != 0);
+		}
+	}
+	if(r.oy + r.lh < h)
+		flushglyphs(&r, 0);
+	for(i = 0; i < r.nglyph; i++)
+		ttfputglyph(r.glyph[i]);
+	free(r.glyph);
+	free(r.gwidth);
+	free(r.cpos);
+	if(rp != nil)
+		*rp = r.pp;
+	return r.b;
+error:
+	ttffreebitmap(r.b);
+	free(r.glyph);
+	free(r.gwidth);
+	return nil;
+}
+
+TTBitmap *
+ttfrender(TTFont *f, char *str, char *end, int w, int h, int flags, char **rstr)
+{
+	if(str == nil)
+		end = nil;
+	else if(end == nil)
+		end = str + strlen(str);
+	return _ttfrender(f, chartorune, str, end, w, h, flags, rstr);
+}
+
+static int
+incrune(Rune *r, char *s)
+{
+	*r = *(Rune*)s;
+	return sizeof(Rune);
+}
+
+TTBitmap *
+ttfrunerender(TTFont *f, Rune *str, Rune *end, int w, int h, int flags, Rune **rstr)
+{
+	if(str == nil)
+		end = nil;
+	else if(end == nil)
+		end = str + runestrlen(str);
+	return _ttfrender(f, incrune, (char *) str, (char *) end, w, h, flags, (char **) rstr);
+}
--- /dev/null
+++ b/sys/src/libttf/scan.c
@@ -1,0 +1,478 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ttf.h>
+#include "impl.h"
+
+typedef struct Scan Scan;
+typedef struct TTLine TTLine;
+
+enum {
+	LINEBLOCK = 32,
+	PTBLOCK = 64,
+};
+
+struct TTLine {
+	int x0, y0;
+	int x1, y1;
+	int link;
+	u8int dir;
+};
+
+struct Scan {
+	enum {
+		DROPOUTS = 1,
+		STUBDET = 2,
+		SMART = 4,
+	} flags;
+
+	TTGlyph *g;
+	
+	TTLine *lines;
+	int nlines;
+	
+	int *hpts, *vpts;
+	int nhpts, nvpts;
+	int *hscanl, *vscanl;
+	
+	u8int *bit;
+	int width, height;
+	int stride;
+};
+
+static void
+dobezier(Scan *s, TTPoint p, TTPoint q, TTPoint r)
+{
+	vlong m, n;
+	TTLine *l;
+
+	m = (vlong)(q.x - p.x) * (r.y - p.y) - (vlong)(q.y - p.y) * (r.x - p.x);
+	n = (vlong)(r.x - p.x) * (r.x - p.x) + (vlong)(r.y - p.y) * (r.y - p.y);
+	if(m * m > 4 * n){
+		dobezier(s, p, (TTPoint){(p.x+q.x+1)/2, (p.y+q.y+1)/2, 0}, (TTPoint){(p.x+2*q.x+r.x+2)/4, (p.y+2*q.y+r.y+2)/4, 0});
+		dobezier(s, (TTPoint){(p.x+2*q.x+r.x+2)/4, (p.y+2*q.y+r.y+2)/4, 0}, (TTPoint){(r.x+q.x+1)/2, (r.y+q.y+1)/2, 0}, r);
+		return;
+	}
+	if((s->nlines & LINEBLOCK - 1) == 0)
+		s->lines = realloc(s->lines, sizeof(TTLine) * (s->nlines + LINEBLOCK));
+	l = &s->lines[s->nlines++];
+	if(p.y < r.y){
+		l->x0 = p.x;
+		l->y0 = p.y;
+		l->x1 = r.x;
+		l->y1 = r.y;
+		l->dir = 0;
+	}else{
+		l->x0 = r.x;
+		l->y0 = r.y;
+		l->x1 = p.x;
+		l->y1 = p.y;
+		l->dir = 1;
+	}
+	l->link = -1;
+}
+
+static int
+hlinecmp(void *va, void *vb)
+{
+	TTLine *a, *b;
+	
+	a = va;
+	b = vb;
+	if(a->y0 < b->y0) return -1;
+	if(a->y0 > b->y0) return 1;
+	return 0;
+}
+
+static int
+vlinecmp(void *va, void *vb)
+{
+	TTLine *a, *b;
+	
+	a = va;
+	b = vb;
+	if(a->x0 < b->x0) return -1;
+	if(a->x0 > b->x0) return 1;
+	return 0;
+}
+
+static int
+intcmp(void *va, void *vb)
+{
+	int a, b;
+	
+	a = *(int*)va;
+	b = *(int*)vb;
+	return (a>b) - (a<b);
+}
+
+static void
+hprep(Scan *s)
+{
+	int i, j, x, y;
+	TTLine *l;
+	int watch, act, *p;
+
+	qsort(s->lines, s->nlines, sizeof(TTLine), hlinecmp);
+	s->hscanl = calloc(sizeof(int), (s->height + 1));
+	act = -1;
+	watch = 0;
+	p = &act;
+	for(i = 0; i < s->height; i++){
+		y = 64 * i + 32;
+		for(; watch < s->nlines && s->lines[watch].y0 <= y; watch++){
+			if(s->lines[watch].y1 <= y || s->lines[watch].y0 == s->lines[watch].y1)
+				continue;
+			s->lines[watch].link = -1;
+			*p = watch;
+			p = &s->lines[watch].link;
+		}
+		s->hscanl[i] = s->nhpts;
+		p = &act;
+		while(j = *p, j >= 0){
+			l = &s->lines[j];
+			if(l->y1 <= y){
+				j = l->link;
+				l->link = -1;
+				*p = j;
+				continue;
+			}
+			x = l->x0 + ttfvrounddiv((vlong)(y - l->y0)*(l->x1 - l->x0), l->y1 - l->y0);
+			if((s->nhpts & PTBLOCK - 1) == 0)
+				s->hpts = realloc(s->hpts, (s->nhpts + PTBLOCK) * sizeof(int));
+			s->hpts[s->nhpts++] = x << 1 | l->dir;
+			p = &l->link;
+		}
+		qsort(s->hpts + s->hscanl[i], s->nhpts - s->hscanl[i], sizeof(int), intcmp);
+	}
+	s->hscanl[i] = s->nhpts;
+}
+
+static int
+iswhite(Scan *s, int x, int y)
+{
+	return (s->bit[(s->height - 1 - y) * s->stride + (x>>3)] >> 7-(x&7) & 1)==0;
+}
+
+static void
+pixel(Scan *s, int x, int y)
+{
+	assert(x >= 0 && x < s->width && y >= 0 && y < s->height);
+	s->bit[(s->height - 1 - y) * s->stride + (x>>3)] |= (1<<7-(x&7));
+}
+
+static int
+intersectsh(Scan *s, int x, int y)
+{
+	int a, b, c, vc, v;
+	
+	a = s->hscanl[y];
+	b = s->hscanl[y+1]-1;
+	v = x * 64 + 32;
+	if(a > b || s->hpts[a]>>1 > v + 64 || s->hpts[b]>>1 < v) return 0;
+	while(a <= b){
+		c = (a + b) / 2;
+		vc = s->hpts[c]>>1;
+		if(vc < v)
+			a = c + 1;
+		else if(vc > v + 64)
+			b = c - 1;
+		else
+			return 1;
+	}
+	return 0;
+}
+
+static int
+intersectsv(Scan *s, int x, int y)
+{
+	int a, b, c, vc, v;
+	
+	a = s->vscanl[x];
+	b = s->vscanl[x+1]-1;
+	v = y * 64 + 32;
+	if(a > b || s->vpts[a]>>1 > v + 64 || s->vpts[b]>>1 < v) return 0;
+	while(a <= b){
+		c = (a + b) / 2;
+		vc = s->vpts[c]>>1;
+		if(vc < v)
+			a = c + 1;
+		else if(vc > v + 64)
+			b = c - 1;
+		else
+			return 1;
+	}
+	return 0;
+}
+
+static void
+hscan(Scan *s)
+{
+	int i, j, k, e;
+	int wind, match, seen, x;
+	
+	for(i = 0; i < s->height; i++){
+		e = s->hscanl[i+1];
+		k = s->hscanl[i];
+		if(k == e) continue;
+		wind = 0;
+		for(j = 0; j < s->width; j++){
+			x = 64 * j + 32;
+			match = 0;
+			seen = 0;
+			while(k < e && (s->hpts[k] >> 1) <= x){
+				wind += (s->hpts[k] & 1) * 2 - 1;
+				seen |= 1<<(s->hpts[k] & 1);
+				if((s->hpts[k] >> 1) == x)
+					match++;
+				k++;
+			}
+			if(match || wind)
+				pixel(s, j, i);
+			else if((s->flags & DROPOUTS) != 0 && seen == 3 && j > 0 && iswhite(s, j-1, i)){
+				if((s->flags & STUBDET) == 0){
+					pixel(s, j-1, i);
+					continue;
+				}
+				if(i <= 0 || i > s->height - 1 || j <= 0 || j > s->width - 1)
+					continue;
+				if(!intersectsv(s, j-1, i-1) && !intersectsh(s, j-1, i-1) && !intersectsv(s, j, i-1) || !intersectsv(s, j-1, i) && !intersectsh(s, j-1, i+1) && !intersectsv(s, j, i))
+					continue;
+				pixel(s, j-1, i);
+			}
+		}
+	}
+}
+
+static void
+vprep(Scan *s)
+{
+	int i, j, x, y;
+	TTLine *l;
+	int watch, act, *p;
+
+	for(i = 0; i < s->nlines; i++){
+		l = &s->lines[i];
+		if(l->x0 > l->x1){
+			x = l->x0, l->x0 = l->x1, l->x1 = x;
+			x = l->y0, l->y0 = l->y1, l->y1 = x;
+			l->dir ^= 1;
+		}
+	}
+	qsort(s->lines, s->nlines, sizeof(TTLine), vlinecmp);
+	s->vscanl = calloc(sizeof(int), (s->width + 1));
+	act = -1;
+	watch = 0;
+	p = &act;
+	for(i = 0; i < s->width; i++){
+		x = 64 * i + 32;
+		for(; watch < s->nlines && s->lines[watch].x0 <= x; watch++){
+			if(s->lines[watch].x1 <= x || s->lines[watch].x0 == s->lines[watch].x1)
+				continue;
+			s->lines[watch].link = -1;
+			*p = watch;
+			p = &s->lines[watch].link;
+		}
+		s->vscanl[i] = s->nvpts;
+		p = &act;
+		while(j = *p, j >= 0){
+			l = &s->lines[j];
+			if(l->x1 <= x){
+				j = l->link;
+				l->link = -1;
+				*p = j;
+				continue;
+			}
+			y = l->y0 + ttfvrounddiv((vlong)(x - l->x0) * (l->y1 - l->y0), l->x1 - l->x0);
+			if((s->nvpts & PTBLOCK - 1) == 0)
+				s->vpts = realloc(s->vpts, (s->nvpts + PTBLOCK) * sizeof(int));
+			s->vpts[s->nvpts++] = y << 1 | l->dir;
+			p = &l->link;
+		}
+		qsort(s->vpts + s->vscanl[i], s->nvpts - s->vscanl[i], sizeof(int), intcmp);
+	}
+	s->vscanl[i] = s->nvpts;
+
+}
+
+static void
+vscan(Scan *s)
+{
+	int i, j, k, e;
+	int seen, y;
+	
+	for(i = 0; i < s->width; i++){
+		e = s->vscanl[i+1];
+		k = s->vscanl[i];
+		if(k == e) continue;
+		for(j = 0; j < s->height; j++){
+			y = 64 * j + 32;
+			seen = 0;
+			while(k < e && (s->vpts[k] >> 1) <= y){
+				seen |= 1<<(s->vpts[k] & 1);
+				k++;
+			}
+			if(seen == 3 && j > 0 && iswhite(s, i, j-1) && iswhite(s, i, j)){
+				if((s->flags & STUBDET) == 0){
+					pixel(s, j-1, i);
+					continue;
+				}
+				if(i <= 0 || i > s->width - 1 || j <= 0 || j > s->height - 1)
+					continue;
+				if(!intersectsv(s, i-1, j-1) & !intersectsh(s, i-1, j-1) & !intersectsh(s, i-1, j) | !intersectsv(s, i+1, j-1) & !intersectsh(s, i, j-1) & !intersectsh(s, i, j))
+					continue;
+				pixel(s, i, j-1);
+			}
+		}
+	}
+}
+	
+void
+ttfscan(TTGlyph *g)
+{
+	int i, j, c;
+	TTPoint p, q, r;
+	Scan s;
+
+	memset(&s, 0, sizeof(s));
+	s.g = g;
+	s.flags = 0;
+	c = g->font->scanctrl;
+	if((c & 1<<8) != 0 && g->font->ppem <= (c & 0xff))
+		s.flags |= DROPOUTS;
+	if((c & 1<<11) != 0 && g->font->ppem > (c & 0xff))
+		s.flags &= ~DROPOUTS;
+	if((c & 3<<12) != 0)
+		s.flags &= ~DROPOUTS;
+	if((s.flags & DROPOUTS) != 0)
+		switch(g->font->scantype){
+		case 0: break;
+		case 1: s.flags |= STUBDET; break;
+		case 2: case 3: case 6: case 7: s.flags &= ~DROPOUTS; break;
+		case 4: s.flags |= SMART; break;
+		case 5: s.flags |= SMART | STUBDET; break;
+		}
+	
+//	s.width = (g->pt[g->npt - 1].x + 63) / 64;
+//	s.height = g->font->ascentpx + g->font->descentpx;
+	s.width = -g->xminpx + g->xmaxpx;
+	s.height = -g->yminpx + g->ymaxpx;
+	s.stride = s.width + 7 >> 3;
+	s.bit = mallocz(s.height * s.stride, 1);
+	assert(s.bit != nil);
+	for(i = 0; i < g->npt; i++){
+		g->pt[i].x -= g->xminpx * 64;
+		g->pt[i].y -= g->yminpx * 64;
+//		g->pt[i].y += g->font->descentpx * 64;
+	}
+	for(i = 0; i < g->ncon; i++){
+		if(g->confst[i] + 1 >= g->confst[i+1]) continue;
+		p = g->pt[g->confst[i]];
+		assert((p.flags & 1) != 0);
+		for(j = g->confst[i]; j++ < g->confst[i+1]; ){
+			if(j < g->confst[i+1] && (g->pt[j].flags & 1) == 0)
+				q = g->pt[j++];
+			else
+				q = p;
+			if(j >= g->confst[i+1])
+				r = g->pt[g->confst[i]];
+			else{
+				r = g->pt[j];
+				if((g->pt[j].flags & 1) == 0){
+					r.x = (r.x + q.x) / 2;
+					r.y = (r.y + q.y) / 2;
+				}
+			}
+			dobezier(&s, p, q, r);
+			p = r;
+			if(j < g->confst[i+1] && (g->pt[j].flags & 1) == 0)
+				j--;
+		}
+	}
+	hprep(&s);
+	if((s.flags & DROPOUTS) != 0)
+		vprep(&s);
+	hscan(&s);
+	if((s.flags & DROPOUTS) != 0)
+		vscan(&s);
+	free(s.hpts);
+	free(s.vpts);
+	free(s.hscanl);
+	free(s.vscanl);
+	free(s.lines);
+	g->bit = s.bit;
+	g->width = s.width;
+	g->height = s.height;
+	g->stride = s.stride;
+}
+
+int
+ttfgetcontour(TTGlyph *g, int i, float **fp, int *np)
+{
+	float offx, offy, scale;
+	float *nf;
+	int n, j;
+	TTPoint p, q, r;
+
+	if((uint)i >= g->ncon)
+		return 0;
+	if(g->confst[i]+1 >= g->confst[i+1]){
+		if(np != nil)
+			*np = 0;
+		if(fp != nil)
+			*fp = malloc(0);
+		return g->ncon - i;
+	}
+	if(g->bit != nil){
+		scale = 1.0f / 64;
+		offx = g->xminpx;
+		offy = g->yminpx;
+	}else{
+		scale = 1.0f * g->font->ppem / g->font->u->emsize;
+		offx = 0;
+		offy = 0;
+	}
+	p = g->pt[g->confst[i]];
+	n = 1;
+	if(fp != nil){
+		*fp = malloc(2 * sizeof(float));
+		if(*fp == nil) return -1;
+		(*fp)[0] = p.x * scale;
+		(*fp)[1] = p.y * scale + offy;
+	}
+	assert((p.flags & 1) != 0);
+	for(j = g->confst[i]; j++ < g->confst[i+1]; ){
+		if(j < g->confst[i+1] && (g->pt[j].flags & 1) == 0)
+			q = g->pt[j++];
+		else
+			q = p;
+		if(j >= g->confst[i+1])
+			r = g->pt[g->confst[i]];
+		else{
+			r = g->pt[j];
+			if((g->pt[j].flags & 1) == 0){
+				r.x = (r.x + q.x) / 2;
+				r.y = (r.y + q.y) / 2;
+			}
+		}
+		if(fp != nil){
+			nf = realloc(*fp, sizeof(float) * 2 * (n + 2));
+			if(nf == nil){
+				free(*fp);
+				return -1;
+			}
+			*fp = nf;
+			nf[2*n] = q.x * scale;
+			nf[2*n+1] = q.y * scale + offy;
+			nf[2*n+2] = r.x * scale;
+			nf[2*n+3] = r.y * scale + offy;
+		}
+		p = r;
+		n += 2;
+		if(j < g->confst[i+1] && (g->pt[j].flags & 1) == 0)
+			j--;
+	}
+	if(np != nil)
+		*np = n;
+	return g->ncon - i;
+}