diff --git a/sys/include/ttf.h b/sys/include/ttf.h new file mode 100644 index 000000000..560aefe4f --- /dev/null +++ b/sys/include/ttf.h @@ -0,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); diff --git a/sys/man/2/ttf b/sys/man/2/ttf new file mode 100644 index 000000000..174f1b8aa --- /dev/null +++ b/sys/man/2/ttf @@ -0,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 +#include +#include +#include +.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. diff --git a/sys/src/libttf/bit.c b/sys/src/libttf/bit.c new file mode 100644 index 000000000..a88818436 --- /dev/null +++ b/sys/src/libttf/bit.c @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#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); +} diff --git a/sys/src/libttf/cmap.c b/sys/src/libttf/cmap.c new file mode 100644 index 000000000..94092f03e --- /dev/null +++ b/sys/src/libttf/cmap.c @@ -0,0 +1,322 @@ +#include +#include +#include +#include +#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; +} diff --git a/sys/src/libttf/glyf.c b/sys/src/libttf/glyf.c new file mode 100644 index 000000000..0d8f7324d --- /dev/null +++ b/sys/src/libttf/glyf.c @@ -0,0 +1,371 @@ +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/sys/src/libttf/head.c b/sys/src/libttf/head.c new file mode 100644 index 000000000..9a50c7f16 --- /dev/null +++ b/sys/src/libttf/head.c @@ -0,0 +1,344 @@ +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/sys/src/libttf/hint.c b/sys/src/libttf/hint.c new file mode 100644 index 000000000..19d36632c --- /dev/null +++ b/sys/src/libttf/hint.c @@ -0,0 +1,1668 @@ +#include +#include +#include +#include +#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<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<f->instctrl &= ~(1<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<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; +} diff --git a/sys/src/libttf/mkfile b/sys/src/libttf/mkfile new file mode 100644 index 000000000..f38ca1498 --- /dev/null +++ b/sys/src/libttf/mkfile @@ -0,0 +1,23 @@ + +#include +#include +#include +#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); +} diff --git a/sys/src/libttf/scan.c b/sys/src/libttf/scan.c new file mode 100644 index 000000000..c76143555 --- /dev/null +++ b/sys/src/libttf/scan.c @@ -0,0 +1,478 @@ +#include +#include +#include +#include +#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) - (alines, 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; +}