ref: db71e19005574388bdc368081848327a5c104e5a
parent: 198f10bb25382882c4abd9081dac4dd74dbdbb9f
author: aiju <devnull@localhost>
date: Sat Jun 9 10:33:19 EDT 2018
add libttf
--- /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;
+}