shithub: neatmkfn

ref: 7c1e51d85563e6726d0eecc3cc149a6e26584975
dir: /otf.c/

View raw version
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sbuf.h"
#include "trfn.h"

#define MAX(a, b)	((a) < (b) ? (b) : (a))
#define LEN(a)		(sizeof(a) / sizeof((a)[0]))

#define NGLYPHS		(1 << 14)
#define NLOOKUPS	(1 << 12)
#define GNLEN		(64)
#define NGRPS		2048

#define U32(buf, off)		(htonl(*(u32 *) ((buf) + (off))))
#define U16(buf, off)		(htons(*(u16 *) ((buf) + (off))))
#define U8(buf, off)		(*(u8 *) ((buf) + (off)))
#define S16(buf, off)		((s16) htons(*(u16 *) ((buf) + (off))))
#define S32(buf, off)		((s32) htonl(*(u32 *) ((buf) + (off))))

#define GCTXLEN		16	/* number of context backtrack coverage arrays */

typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
typedef int s32;
typedef short s16;

static char glyph_name[NGLYPHS][GNLEN];
static int glyph_code[NGLYPHS];
static int glyph_bbox[NGLYPHS][4];
static int glyph_wid[NGLYPHS];
static int glyph_n;
static int upm;			/* units per em */
static int res;			/* device resolution */
static int kmin;		/* minimum kerning value */
static int warn;		/* report unsupported tables */

static char *macset[];
static char *stdset[];

static int owid(int w)
{
	return (w < 0 ? w * 1000 - upm / 2 : w * 1000 + upm / 2) / upm;
}

static int uwid(int w)
{
	int d = 7200 / res;
	return (w < 0 ? owid(w) - d / 2 : owid(w) + d / 2) / d;
}

/* weather the script is right-to-left */
static int otf_r2l(char *feat)
{
	char *scrp = strchr(feat, ':') + 1;
	return !strcmp("arab", scrp) || !strcmp("hebr", scrp);
}

/* report unsupported otf tables */
static void otf_unsupported(char *sub, int type, int fmt)
{
	if (warn) {
		fprintf(stderr, "neatmkfn: unsupported %s lookup %d", sub, type);
		if (fmt > 0)
			fprintf(stderr, " format %d", fmt);
		fprintf(stderr, "\n");
	}
}

/* find the otf table with the given name */
static void *otf_table(void *otf, char *name)
{
	int nrecs = U16(otf, 4);
	int i;
	for (i = 0; i < nrecs; i++) {
		void *rec = otf + 12 + i * 16;	/* an otf table record */
		if (!strncmp(rec, name, 4))
			return otf + U32(rec, 8);
	}
	return NULL;
}

/* obtain postscript font name from name table */
static void otf_name(void *otf, void *tab)
{
	char name[256];
	void *str = tab + U16(tab, 4);		/* storage area */
	int n = U16(tab, 2);			/* number of name records */
	int i;
	for (i = 0; i < n; i++) {
		void *rec = tab + 6 + 12 * i;
		int pid = U16(rec, 0);		/* platform id */
		int eid = U16(rec, 2);		/* encoding id */
		int lid = U16(rec, 4);		/* language id */
		int nid = U16(rec, 6);		/* name id */
		int len = U16(rec, 8);		/* string length */
		int off = U16(rec, 10);		/* string offset  */
		if (pid == 1 && eid == 0 && lid == 0 && nid == 6) {
			memcpy(name, str + off, len);
			name[len] = '\0';
			trfn_psfont(name);
		}
	}
}

/* parse otf cmap format 4 subtable */
static void otf_cmap4(void *otf, void *cmap4)
{
	int nsegs;
	void *ends, *begs, *deltas, *offsets;
	int beg, end, delta, offset;
	int i, j;
	nsegs = U16(cmap4, 6) / 2;
	ends = cmap4 + 14;
	begs = ends + 2 * nsegs + 2;
	deltas = begs + 2 * nsegs;
	offsets = deltas + 2 * nsegs;
	for (i = 0; i < nsegs; i++) {
		beg = U16(begs, 2 * i);
		end = U16(ends, 2 * i);
		delta = U16(deltas, 2 * i);
		offset = U16(offsets, 2 * i);
		if (offset) {
			for (j = beg; j <= end; j++)
				glyph_code[(U16(offsets + 2 * i,
					offset + (j - beg) * 2) + delta) & 0xffff] = j;
		} else {
			for (j = beg; j <= end; j++)
				glyph_code[(j + delta) & 0xffff] = j;
		}
	}
}

/* parse otf cmap header */
static void otf_cmap(void *otf, void *cmap)
{
	int nrecs = U16(cmap, 2);
	int i;
	for (i = 0; i < nrecs; i++) {
		void *rec = cmap + 4 + i * 8;	/* a cmap record */
		int plat = U16(rec, 0);
		int enc = U16(rec, 2);
		void *tab = cmap + U32(rec, 4);	/* a cmap subtable */
		int fmt = U16(tab, 0);
		if (plat == 3 && enc == 1 && fmt == 4)
			otf_cmap4(otf, tab);
	}
}

static void otf_post(void *otf, void *post)
{
	void *post2;			/* version 2.0 header */
	void *index;			/* glyph name indices */
	void *names;			/* glyph names */
	int cname = 0;
	int i;
	post2 = post + 32;
	glyph_n = U16(post2, 0);
	if (U32(post, 0) != 0x20000)
		return;
	index = post2 + 2;
	names = index + 2 * glyph_n;
	for (i = 0; i < glyph_n; i++) {
		int idx = U16(index, 2 * i);
		if (idx < 258) {
			strcpy(glyph_name[i], macset[idx]);
		} else {
			memcpy(glyph_name[i], names + cname + 1,
				U8(names, cname));
			glyph_name[i][U8(names, cname)] = '\0';
			cname += U8(names, cname) + 1;
		}
	}
}

static void otf_glyf(void *otf, void *glyf)
{
	void *maxp = otf_table(otf, "maxp");
	void *head = otf_table(otf, "head");
	void *loca = otf_table(otf, "loca");
	void *gdat;
	void *gdat_next;
	int n = U16(maxp, 4);
	int fmt = U16(head, 50);
	int i, j;
	for (i = 0; i < n; i++) {
		if (fmt) {
			gdat = glyf + U32(loca, 4 * i);
			gdat_next = glyf + U32(loca, 4 * (i + 1));
		} else {
			gdat = glyf + U16(loca, 2 * i) * 2;
			gdat_next = glyf + U16(loca, 2 * (i + 1)) * 2;
		}
		if (gdat < gdat_next)
			for (j = 0; j < 4; j++)
				glyph_bbox[i][j] = S16(gdat, 2 + 2 * j);
	}
}

static void otf_hmtx(void *otf, void *hmtx)
{
	void *hhea = otf_table(otf, "hhea");
	int n;
	int i;
	n = U16(hhea, 34);
	for (i = 0; i < n; i++)
		glyph_wid[i] = U16(hmtx, i * 4);
	for (i = n; i < glyph_n; i++)
		glyph_wid[i] = glyph_wid[n - 1];
}

static void otf_kern(void *otf, void *kern)
{
	int off = 4;
	int i, j;
	int n = U16(kern, 2);		/* number of kern subtables */
	for (i = 0; i < n; i++) {
		void *tab = kern + off;	/* a kern subtable */
		int cov = U16(tab, 4);
		off += U16(tab, 2);
		if ((cov >> 8) == 0 && (cov & 1)) {	/* format 0 */
			int npairs = U16(tab, 6);
			for (j = 0; j < npairs; j++) {
				int c1 = U16(tab, 14 + 6 * j);
				int c2 = U16(tab, 14 + 6 * j + 2);
				int val = S16(tab, 14 + 6 * j + 4);
				trfn_kern(glyph_name[c1], glyph_name[c2],
					owid(val));
			}
		}
	}
}

static int coverage(void *cov, int *out)
{
	int fmt = U16(cov, 0);
	int n = U16(cov, 2);
	int beg, end;
	int ncov = 0;
	int i, j;
	if (fmt == 1) {
		for (i = 0; i < n; i++)
			out[ncov++] = U16(cov, 4 + 2 * i);
	}
	if (fmt == 2) {
		for (i = 0; i < n; i++) {
			beg = U16(cov, 4 + 6 * i);
			end = U16(cov, 4 + 6 * i + 2);
			for (j = beg; j <= end; j++)
				out[ncov++] = j;
		}
	}
	return ncov;
}

static int classdef(void *tab, int *gl, int *cls)
{
	int fmt = U16(tab, 0);
	int ngl = 0;
	int i, j;
	if (fmt == 1) {
		int beg = U16(tab, 2);
		ngl = U16(tab, 4);
		for (i = 0; i < ngl; i++) {
			gl[i] = beg + i;
			cls[i] = U16(tab, 6 + 2 * i);
		}
	}
	if (fmt == 2) {
		int n = U16(tab, 2);
		for (i = 0; i < n; i++) {
			int beg = U16(tab, 4 + 6 * i);
			int end = U16(tab, 4 + 6 * i + 2);
			for (j = beg; j <= end; j++) {
				gl[ngl] = j;
				cls[ngl] = U16(tab, 4 + 6 * i + 4);
				ngl++;
			}
		}
	}
	return ngl;
}

static int intcmp(void *v1, void *v2)
{
	return *(int *) v1 - *(int *) v2;
}

static int ggrp_make(int *src, int n);

static int ggrp_class(int *src, int *cls, int nsrc, int id)
{
	int g[NGLYPHS];
	int n = 0;
	int i;
	for (i = 0; i < nsrc; i++)
		if (cls[i] == id)
			g[n++] = src[i];
	qsort(g, n, sizeof(g[0]), (void *) intcmp);
	return ggrp_make(g, n);
}

static int ggrp_coverage(int *g, int n)
{
	qsort(g, n, sizeof(g[0]), (void *) intcmp);
	return ggrp_make(g, n);
}

static int valuerecord_len(int fmt)
{
	int off = 0;
	int i;
	for (i = 0; i < 8; i++)
		if (fmt & (1 << i))
			off += 2;
	return off;
}

static void valuerecord_print(int fmt, void *rec)
{
	int vals[8] = {0};
	int off = 0;
	int i;
	for (i = 0; i < 8; i++) {
		if (fmt & (1 << i)) {
			vals[i] = uwid(S16(rec, off));
			off += 2;
		}
	}
	if (fmt)
		printf(":%+d%+d%+d%+d", vals[0], vals[1], vals[2], vals[3]);
}

static int valuerecord_small(int fmt, void *rec)
{
	int off = 0;
	int i;
	for (i = 0; i < 8; i++) {
		if (fmt & (1 << i)) {
			if (abs(uwid(S16(rec, off))) >= MAX(1, kmin))
				return 0;
			off += 2;
		}
	}
	return 1;
}

/* single adjustment positioning */
static void otf_gpostype1(void *otf, void *sub, char *feat)
{
	int fmt = U16(sub, 0);
	int vfmt = U16(sub, 4);
	int cov[NGLYPHS];
	int ncov, nvals;
	int vlen = valuerecord_len(vfmt);
	int i;
	ncov = coverage(sub + U16(sub, 2), cov);
	if (fmt == 1) {
		for (i = 0; i < ncov; i++) {
			if (valuerecord_small(vfmt, sub + 6))
				continue;
			printf("gpos %s 1 %s", feat, glyph_name[cov[i]]);
			valuerecord_print(vfmt, sub + 6);
			printf("\n");
		}
	}
	if (fmt == 2) {
		nvals = U16(sub, 6);
		for (i = 0; i < nvals; i++) {
			if (valuerecord_small(vfmt, sub + 6))
				continue;
			printf("gpos %s 1 %s", feat, glyph_name[cov[i]]);
			valuerecord_print(vfmt, sub + 8 + i * vlen);
			printf("\n");
		}
	}
}

/* pair adjustment positioning */
static void otf_gpostype2(void *otf, void *sub, char *feat)
{
	int fmt = U16(sub, 0);
	int vfmt1 = U16(sub, 4);	/* valuerecord 1 */
	int vfmt2 = U16(sub, 6);	/* valuerecord 2 */
	int fmtoff1, fmtoff2;
	int vrlen;			/* the length of vfmt1 and vfmt2 */
	int i, j;
	vrlen = valuerecord_len(vfmt1) + valuerecord_len(vfmt2);
	if (fmt == 1) {
		int cov[NGLYPHS];
		int nc1 = U16(sub, 8);
		coverage(sub + U16(sub, 2), cov);
		for (i = 0; i < nc1; i++) {
			void *c2 = sub + U16(sub, 10 + 2 * i);
			int nc2 = U16(c2, 0);
			for (j = 0; j < nc2; j++) {
				int second = U16(c2 + 2 + (2 + vrlen) * j, 0);
				fmtoff1 = 2 + (2 + vrlen) * j + 2;
				fmtoff2 = fmtoff1 + valuerecord_len(vfmt1);
				if (valuerecord_small(vfmt1, c2 + fmtoff1) &&
					valuerecord_small(vfmt2, c2 + fmtoff2))
					continue;
				printf("gpos %s 2", feat);
				printf(" %s", glyph_name[cov[i]]);
				valuerecord_print(vfmt1, c2 + fmtoff1);
				printf(" %s", glyph_name[second]);
				valuerecord_print(vfmt2, c2 + fmtoff2);
				printf("\n");
			}
		}
	}
	if (fmt == 2) {
		int gl1[NGLYPHS], gl2[NGLYPHS];
		int cls1[NGLYPHS], cls2[NGLYPHS];
		int grp1[NGLYPHS], grp2[NGLYPHS];
		int ngl1 = classdef(sub + U16(sub, 8), gl1, cls1);
		int ngl2 = classdef(sub + U16(sub, 10), gl2, cls2);
		int ncls1 = U16(sub, 12);
		int ncls2 = U16(sub, 14);
		for (i = 0; i < ncls1; i++)
			grp1[i] = ggrp_class(gl1, cls1, ngl1, i);
		for (i = 0; i < ncls2; i++)
			grp2[i] = ggrp_class(gl2, cls2, ngl2, i);
		for (i = 0; i < ncls1; i++) {
			for (j = 0; j < ncls2; j++) {
				fmtoff1 = 16 + (i * ncls2 + j) * vrlen;
				fmtoff2 = fmtoff1 + valuerecord_len(vfmt1);
				if (valuerecord_small(vfmt1, sub + fmtoff1) &&
					valuerecord_small(vfmt2, sub + fmtoff2))
					continue;
				printf("gpos %s %d", feat, 2);
				printf(" @%d", grp1[i]);
				valuerecord_print(vfmt1, sub + fmtoff1);
				printf(" @%d", grp2[j]);
				valuerecord_print(vfmt2, sub + fmtoff2);
				printf("\n");
			}
		}
	}
}

/* cursive attachment positioning */
static void otf_gpostype3(void *otf, void *sub, char *feat)
{
	int fmt = U16(sub, 0);
	int cov[NGLYPHS];
	int icov[NGLYPHS];
	int ocov[NGLYPHS];
	int i, n;
	int icnt = 0;
	int ocnt = 0;
	int igrp, ogrp;
	coverage(sub + U16(sub, 2), cov);
	if (fmt != 1)
		return;
	n = U16(sub, 4);
	for (i = 0; i < n; i++)
		if (U16(sub, 6 + 4 * i))
			ocov[ocnt++] = cov[i];
	for (i = 0; i < n; i++)
		if (U16(sub, 6 + 4 * i + 2))
			icov[icnt++] = cov[i];
	igrp = ggrp_coverage(icov, icnt);
	ogrp = ggrp_coverage(ocov, ocnt);
	for (i = 0; i < n; i++) {
		int prev = U16(sub, 6 + 4 * i);
		int next = U16(sub, 6 + 4 * i + 2);
		if (prev) {
			int dx = -uwid(S16(sub, prev + 2));
			int dy = -uwid(S16(sub, prev + 4));
			if (otf_r2l(feat)) {
				dx += uwid(glyph_wid[cov[i]]);
			}
			printf("gpos %s 2 @%d %s:%+d%+d%+d%+d\n",
				feat, igrp, glyph_name[cov[i]],
				0, 0, dx, dy);
		}
		if (next) {
			int dx = uwid(S16(sub, next + 2)) - uwid(glyph_wid[cov[i]]);
			int dy = uwid(S16(sub, next + 4));
			if (otf_r2l(feat)) {
				dx += uwid(glyph_wid[cov[i]]);
			}
			printf("gpos %s 2 %s @%d:%+d%+d%+d%+d\n",
				feat, glyph_name[cov[i]], ogrp,
				0, 0, dx, dy);
		}
	}
}

/* mark-to-base attachment positioning */
static void otf_gpostype4(void *otf, void *sub, char *feat)
{
	int fmt = U16(sub, 0);
	int mcov[NGLYPHS];	/* mark coverage */
	int bcov[NGLYPHS];	/* base coverage */
	int cgrp[1024];		/* glyph groups assigned to classes */
	int bgrp;		/* the group assigned to base glyphs */
	int mcnt;		/* mark coverage size */
	int bcnt;		/* base coverage size */
	int ccnt;		/* class count */
	void *marks;		/* mark array table */
	void *bases;		/* base array table */
	int i, j;
	if (fmt != 1)
		return;
	mcnt = coverage(sub + U16(sub, 2), mcov);
	bcnt = coverage(sub + U16(sub, 4), bcov);
	ccnt = U16(sub, 6);
	marks = sub + U16(sub, 8);
	bases = sub + U16(sub, 10);
	bgrp = ggrp_coverage(bcov, bcnt);
	for (i = 0; i < ccnt; i++) {
		int grp[NGLYPHS];
		int cnt = 0;
		for (j = 0; j < mcnt; j++)
			if (U16(marks, 2 + 4 * j) == i)
				grp[cnt++] = mcov[j];
		cgrp[i] = ggrp_coverage(grp, cnt);
	}
	for (i = 0; i < mcnt; i++) {
		void *mark = marks + U16(marks, 2 + 4 * i + 2);	/* mark anchor */
		int dx = -uwid(S16(mark, 2));
		int dy = -uwid(S16(mark, 4));
		if (otf_r2l(feat)) {
			dx += uwid(glyph_wid[mcov[i]]);
			dy = -dy;
		}
		printf("gpos %s 2 @%d %s:%+d%+d%+d%+d\n",
			feat, bgrp, glyph_name[mcov[i]], dx, dy, 0, 0);
	}
	for (i = 0; i < bcnt; i++) {
		for (j = 0; j < ccnt; j++) {
			void *base = bases + U16(bases, 2 + ccnt * 2 * i + 2 * j);
			int dx = uwid(S16(base, 2)) - uwid(glyph_wid[bcov[i]]);
			int dy = uwid(S16(base, 4));
			if (otf_r2l(feat)) {
				dx += uwid(glyph_wid[bcov[i]]);
				dy = -dy;
			}
			printf("gpos %s 2 %s @%d:%+d%+d%+d%+d\n",
				feat, glyph_name[bcov[i]], cgrp[j], dx, dy, 0, 0);
		}
	}
}

/* gsub context */
struct gctx {
	int bgrp[GCTXLEN];	/* backtrack coverage arrays */
	int igrp[GCTXLEN];	/* input coverage arrays */
	int lgrp[GCTXLEN];	/* lookahead coverage arrays*/
	int bn, in, ln;		/* size of b[], i[], l[] */
	int seqidx;		/* sequence index */
};

static int gctx_len(struct gctx *ctx, int patlen)
{
	return ctx ? ctx->bn + ctx->in + ctx->ln - patlen : 0;
}

static void gctx_backtrack(struct gctx *ctx)
{
	int i;
	if (!ctx)
		return;
	for (i = 0; i < ctx->bn; i++)
		printf(" =@%d", ctx->bgrp[i]);
	for (i = 0; i < ctx->seqidx; i++)
		printf(" =@%d", ctx->igrp[i]);
}

static void gctx_lookahead(struct gctx *ctx, int patlen)
{
	int i;
	if (!ctx)
		return;
	for (i = ctx->seqidx + patlen; i < ctx->in; i++)
		printf(" =@%d", ctx->igrp[i]);
	for (i = 0; i < ctx->ln; i++)
		printf(" =@%d", ctx->lgrp[i]);
}

/* single substitution */
static void otf_gsubtype1(void *otf, void *sub, char *feat, struct gctx *ctx)
{
	int cov[NGLYPHS];
	int fmt = U16(sub, 0);
	int ncov;
	int i;
	ncov = coverage(sub + U16(sub, 2), cov);
	if (fmt == 1) {
		for (i = 0; i < ncov; i++) {
			printf("gsub %s %d", feat, 2 + gctx_len(ctx, 1));
			gctx_backtrack(ctx);
			printf(" -%s +%s", glyph_name[cov[i]],
				glyph_name[cov[i] + S16(sub, 4)]);
			gctx_lookahead(ctx, 1);
			printf("\n");
		}
	}
	if (fmt == 2) {
		int n = U16(sub, 4);
		for (i = 0; i < n; i++) {
			printf("gsub %s %d", feat, 2 + gctx_len(ctx, 1));
			gctx_backtrack(ctx);
			printf(" -%s +%s", glyph_name[cov[i]],
				glyph_name[U16(sub, 6 + 2 * i)]);
			gctx_lookahead(ctx, 1);
			printf("\n");
		}
	}
}

/* alternate substitution */
static void otf_gsubtype3(void *otf, void *sub, char *feat, struct gctx *ctx)
{
	int cov[NGLYPHS];
	int fmt = U16(sub, 0);
	int n, i, j;
	if (fmt != 1)
		return;
	coverage(sub + U16(sub, 2), cov);
	n = U16(sub, 4);
	for (i = 0; i < n; i++) {
		void *alt = sub + U16(sub, 6 + 2 * i);
		int nalt = U16(alt, 0);
		for (j = 0; j < nalt; j++) {
			printf("gsub %s %d", feat, 2 + gctx_len(ctx, 1));
			gctx_backtrack(ctx);
			printf(" -%s +%s", glyph_name[cov[i]],
				glyph_name[U16(alt, 2 + 2 * j)]);
			gctx_lookahead(ctx, 1);
			printf("\n");
		}
	}
}

/* ligature substitution */
static void otf_gsubtype4(void *otf, void *sub, char *feat, struct gctx *ctx)
{
	int fmt = U16(sub, 0);
	int cov[NGLYPHS];
	int n, i, j, k;
	if (fmt != 1)
		return;
	coverage(sub + U16(sub, 2), cov);
	n = U16(sub, 4);
	for (i = 0; i < n; i++) {
		void *set = sub + U16(sub, 6 + 2 * i);
		int nset = U16(set, 0);
		for (j = 0; j < nset; j++) {
			void *lig = set + U16(set, 2 + 2 * j);
			int nlig = U16(lig, 2);
			printf("gsub %s %d", feat, nlig + 1 + gctx_len(ctx, nlig));
			gctx_backtrack(ctx);
			printf(" -%s", glyph_name[cov[i]]);
			for (k = 0; k < nlig - 1; k++)
				printf(" -%s", glyph_name[U16(lig, 4 + 2 * k)]);
			printf(" +%s", glyph_name[U16(lig, 0)]);
			gctx_lookahead(ctx, nlig);
			printf("\n");
		}
	}
}

/* chaining contextual substitution */
static void otf_gsubtype6(void *otf, void *sub, char *feat, void *gsub)
{
	struct gctx ctx = {{0}};
	void *lookups = gsub + U16(gsub, 8);
	int fmt = U16(sub, 0);
	int cov[NGLYPHS];
	int i, j, nsub, ncov;
	int off = 2;
	if (fmt != 3) {
		otf_unsupported("GSUB", 6, fmt);
		return;
	}
	ctx.bn = U16(sub, off);
	for (i = 0; i < ctx.bn; i++) {
		ncov = coverage(sub + U16(sub, off + 2 + 2 * i), cov);
		ctx.bgrp[i] = ggrp_coverage(cov, ncov);
	}
	off += 2 + 2 * ctx.bn;
	ctx.in = U16(sub, off);
	for (i = 0; i < ctx.in; i++) {
		ncov = coverage(sub + U16(sub, off + 2 + 2 * i), cov);
		ctx.igrp[i] = ggrp_coverage(cov, ncov);
	}
	off += 2 + 2 * ctx.in;
	ctx.ln = U16(sub, off);
	for (i = 0; i < ctx.ln; i ++) {
		ncov = coverage(sub + U16(sub, off + 2 + 2 * i), cov);
		ctx.lgrp[i] = ggrp_coverage(cov, ncov);
	}
	off += 2 + 2 * ctx.ln;
	nsub = U16(sub, off);	/* nsub > 1 is not supported */
	for (i = 0; i < nsub && i < 1; i++) {
		int lidx = U16(sub, off + 2 + 4 * i + 2);
		void *lookup = lookups + U16(lookups, 2 + 2 * lidx);
		int ltype = U16(lookup, 0);
		int ntabs = U16(lookup, 4);
		ctx.seqidx = U16(sub, off + 2 + 4 * i);
		for (j = 0; j < ntabs; j++) {
			void *tab = lookup + U16(lookup, 6 + 2 * j);
			int type = ltype;
			if (type == 7) {	/* extension substitution */
				type = U16(tab, 2);
				tab = tab + U32(tab, 4);
			}
			if (type == 1)
				otf_gsubtype1(otf, tab, feat, &ctx);
			if (type == 3)
				otf_gsubtype3(otf, tab, feat, &ctx);
			if (type == 4)
				otf_gsubtype4(otf, tab, feat, &ctx);
		}
	}
}

/* an otf gsub/gpos lookup */
struct otflookup {
	char scrp[8];		/* script name */
	char lang[8];		/* language name */
	char feat[8];		/* feature name */
	int lookup;		/* index into the lookup table */
};

/* parse the given gsub/gpos feature table */
static int otf_featrec(void *otf, void *gtab, void *featrec,
			char *stag, char *ltag,
			struct otflookup *lookups, int lookups_n)
{
	void *feats = gtab + U16(gtab, 6);
	void *feat = feats + U16(featrec, 4);
	int n = U16(feat, 2);
	int i, j;
	for (i = 0; i < n; i++) {
		int lookup = U16(feat, 4 + 2 * i);	/* lookup index */
		/* do not store features common to all languages in a script */
		for (j = 0; j < lookups_n; j++)
			if (lookups[j].lookup == lookup && !lookups[j].lang[0])
				if (!strcmp(lookups[j].scrp, stag))
					break;
		if (j == lookups_n) {
			memcpy(lookups[j].feat, featrec, 4);
			lookups[j].feat[4] = '\0';
			strcpy(lookups[j].scrp, stag);
			strcpy(lookups[j].lang, ltag);
			lookups[j].lookup = U16(feat, 4 + 2 * i);
			lookups_n++;
		}
	}
	return lookups_n;
}

/* parse the given language table and its feature tables */
static int otf_lang(void *otf, void *gtab, void *lang, char *stag, char *ltag,
		struct otflookup *lookups, int lookups_n)
{
	void *feats = gtab + U16(gtab, 6);
	int featidx = U16(lang, 2);
	int nfeat = U16(lang, 4);
	int i;
	if (featidx != 0xffff)
		lookups_n = otf_featrec(otf, gtab, feats + 2 + 6 * featidx,
				stag, ltag, lookups, lookups_n);
	for (i = 0; i < nfeat; i++)
		lookups_n = otf_featrec(otf, gtab, feats + 2 + 6 * U16(lang, 6 + 2 * i),
				stag, ltag, lookups, lookups_n);
	return lookups_n;
}

/* return lookup table tag (i.e. liga:latn:ENG); returns a static buffer */
static char *lookuptag(struct otflookup *lu)
{
	static char tag[16];
	sprintf(tag, "%s:%s", lu->feat, lu->scrp[0] ? lu->scrp : "DFLT");
	if (lu->lang[0])
		sprintf(strchr(tag, '\0'), ":%s", lu->lang);
	return tag;
}

static int lookupcmp(void *v1, void *v2)
{
	struct otflookup *l1 = v1;
	struct otflookup *l2 = v2;
	if (strcmp(l1->scrp, l2->scrp))
		return strcmp(l1->scrp, l2->scrp);
	if (trfn_featrank(l1->scrp, l1->feat) != trfn_featrank(l1->scrp, l2->feat))
		return trfn_featrank(l1->scrp, l1->feat) - trfn_featrank(l1->scrp, l2->feat);
	return l1->lookup - l2->lookup;
}

/* extract lookup tables for all features of the given gsub/gpos table */
static int otf_gtab(void *otf, void *gpos, struct otflookup *lookups)
{
	void *scripts = gpos + U16(gpos, 4);
	int nscripts, nlangs;
	void *script;
	char stag[8], ltag[8];		/* script and language tags */
	int i, j;
	int n = 0;
	nscripts = U16(scripts, 0);
	for (i = 0; i < nscripts; i++) {
		void *grec = scripts + 2 + 6 * i;
		memcpy(stag, grec, 4);
		stag[4] = '\0';
		if (!trfn_script(stag, nscripts))
			continue;
		script = scripts + U16(grec, 4);
		nlangs = U16(script, 2);
		if (U16(script, 0) && trfn_lang(NULL, nlangs + (U16(script, 0) != 0)))
			n = otf_lang(otf, gpos, script + U16(script, 0),
						stag, "", lookups, n);
		for (j = 0; j < nlangs; j++) {
			void *lrec = script + 4 + 6 * j;
			memcpy(ltag, lrec, 4);
			ltag[4] = '\0';
			if (trfn_lang(ltag, nlangs + (U16(script, 0) != 0)))
				n = otf_lang(otf, gpos, script + U16(lrec, 4),
						stag, ltag, lookups, n);
		}
	}
	qsort(lookups, n, sizeof(lookups[0]), (void *) lookupcmp);
	return n;
}

static void otf_gpos(void *otf, void *gpos)
{
	struct otflookup lookups[NLOOKUPS];
	void *lookuplist = gpos + U16(gpos, 8);
	int nlookups = otf_gtab(otf, gpos, lookups);
	int i, j;
	for (i = 0; i < nlookups; i++) {
		void *lookup = lookuplist + U16(lookuplist, 2 + 2 * lookups[i].lookup);
		int ltype = U16(lookup, 0);
		int ntabs = U16(lookup, 4);
		char *tag = lookuptag(&lookups[i]);
		for (j = 0; j < ntabs; j++) {
			void *tab = lookup + U16(lookup, 6 + 2 * j);
			int type = ltype;
			if (type == 9) {	/* extension positioning */
				type = U16(tab, 2);
				tab = tab + U32(tab, 4);
			}
			switch (type) {
			case 1:
				otf_gpostype1(otf, tab, tag);
				break;
			case 2:
				otf_gpostype2(otf, tab, tag);
				break;
			case 3:
				otf_gpostype3(otf, tab, tag);
				break;
			case 4:
				otf_gpostype4(otf, tab, tag);
				break;
			default:
				otf_unsupported("GPOS", type, 0);
			}
		}
	}
}

static void otf_gsub(void *otf, void *gsub)
{
	struct otflookup lookups[NLOOKUPS];
	void *lookuplist = gsub + U16(gsub, 8);
	int nlookups = otf_gtab(otf, gsub, lookups);
	int i, j;
	for (i = 0; i < nlookups; i++) {
		void *lookup = lookuplist + U16(lookuplist, 2 + 2 * lookups[i].lookup);
		int ltype = U16(lookup, 0);
		int ntabs = U16(lookup, 4);
		char *tag = lookuptag(&lookups[i]);
		for (j = 0; j < ntabs; j++) {
			void *tab = lookup + U16(lookup, 6 + 2 * j);
			int type = ltype;
			if (type == 7) {	/* extension substitution */
				type = U16(tab, 2);
				tab = tab + U32(tab, 4);
			}
			switch (type) {
			case 1:
				otf_gsubtype1(otf, tab, tag, NULL);
				break;
			case 3:
				otf_gsubtype3(otf, tab, tag, NULL);
				break;
			case 4:
				otf_gsubtype4(otf, tab, tag, NULL);
				break;
			case 6:
				otf_gsubtype6(otf, tab, tag, gsub);
				break;
			default:
				otf_unsupported("GSUB", type, 0);
			}
		}
	}
}

/* read a cff offset, which has sz bytes */
static int cff_int(void *tab, int off, int sz)
{
	int i;
	int n = 0;
	for (i = 0; i < sz; i++)
		n = n * 256 + U8(tab, off + i);
	return n;
}

/* cff dict operand/operator */
static int cff_op(void *tab, int off, int *val)
{
	int b0 = U8(tab, off);
	int i;
	if (b0 >= 32 && b0 <= 246) {
		*val = b0 - 139;
		return 1;
	}
	if (b0 >= 247 && b0 <= 250) {
		*val = (b0 - 247) * 256 + U8(tab, off + 1) + 108;
		return 2;
	}
	if (b0 >= 251 && b0 <= 254) {
		*val = -(b0 - 251) * 256 - U8(tab, off + 1) - 108;
		return 2;
	}
	if (b0 == 28) {
		*val = (U8(tab, off + 1) << 8) | U8(tab, off + 2);
		return 3;
	}
	if (b0 == 29) {
		*val = (U8(tab, off + 1) << 24) | (U8(tab, off + 2) << 16) |
			(U8(tab, off + 3) << 8) | U8(tab, off + 4);
		return 5;
	}
	if (b0 == 30) {
		for (i = 1; i < 32; i++) {
			int nib = U8(tab, off + i);
			if ((nib & 0x0f) == 0x0f || (nib & 0xf0) == 0xf0)
				break;
		}
		*val = 0;
		return i + 1;
	}
	*val = b0;
	return 1;
}

static int cffidx_cnt(void *idx)
{
	return U16(idx, 0);
}

static void *cffidx_get(void *idx, int i)
{
	int cnt = U16(idx, 0);
	int sz = U8(idx, 2);
	return idx + 3 + (cnt + 1) * sz - 1 + cff_int(idx, 3 + i * sz, sz);
}

static int cffidx_len(void *idx, int i)
{
	return cffidx_get(idx, i + 1) - cffidx_get(idx, i);
}

static void *cffidx_end(void *idx)
{
	return cffidx_get(idx, cffidx_cnt(idx));
}

/* obtain the value of the given key from a cff dict */
static int cffdict_get(void *dict, int len, int key, int *args)
{
	int off = 0;
	int op = 0;
	int val = 0;
	/* operators: keys (one or two bytes); operands: values */
	while (off < len) {
		val = op;
		if (args) {
			memmove(args + 1, args + 0, 3 * sizeof(args[0]));
			args[0] = val;
		}
		off += cff_op(dict, off, &op);
		if (op == 12) {			/* two-byte operator */
			off += cff_op(dict, off, &op);
			op += 1200;
		}
		if (op == key)
			return val;
	}
	return 0;
}

static void cff_char(void *stridx, int id, char *dst)
{
	int len;
	if (id < 391) {
		strcpy(dst, stdset[id]);
		return;
	}
	id -= 391;
	len = cffidx_len(stridx, id);
	memcpy(dst, cffidx_get(stridx, id), len);
	dst[len] = '\0';
}

static void otf_cff(void *otf, void *cff)
{
	void *nameidx;		/* name index */
	void *topidx;		/* top dict index */
	void *stridx;		/* string idx */
	void *chridx;		/* charstrings index */
	void *charset;		/* charset offset of top dict table */
	int bbox[4] = {0};
	int i, j;
	if (U8(cff, 0) != 1)
		return;
	nameidx = cff + U8(cff, 2);
	topidx = cffidx_end(nameidx);
	if (cffidx_cnt(nameidx) < 1)
		return;
	stridx = cffidx_end(topidx);
	chridx = cff + cffdict_get(cffidx_get(topidx, 0),
			cffidx_len(topidx, 0), 17, NULL);
	charset = cff + cffdict_get(cffidx_get(topidx, 0),
			cffidx_len(topidx, 0), 15, NULL);
	glyph_n = cffidx_cnt(chridx);
	strcpy(glyph_name[0], ".notdef");
	if (U8(charset, 0) == 0) {
		for (i = 0; i < glyph_n; i++)
			cff_char(stridx, U16(charset, 1 + i * 2),
				glyph_name[i + 1]);
	}
	if (U8(charset, 0) == 1 || U8(charset, 0) == 2) {
		int g = 1;
		int sz = U8(charset, 0) == 1 ? 3 : 4;
		for (i = 0; g < glyph_n; i++) {
			int sid = U16(charset, 1 + i * sz);
			int cnt = cff_int(charset, 1 + i * sz + 2, sz - 2);
			for (j = 0; j <= cnt && g < glyph_n; j++) {
				cff_char(stridx, sid + j, glyph_name[g]);
				g++;
			}
		}
	}
	/* use font bbox for all glyphs */
	cffdict_get(cffidx_get(topidx, 0), cffidx_len(topidx, 0), 5, bbox);
	for (i = 1; i < glyph_n; i++)
		for (j = 0; j < 4; j++)
			glyph_bbox[i][j] = bbox[3 - j];
}

static void *otf_input(int fd)
{
	struct sbuf *sb = sbuf_make();
	char buf[1 << 12];
	int nr = 0;
	while ((nr = read(fd, buf, sizeof(buf))) > 0)
		sbuf_mem(sb, buf, nr);
	return sbuf_done(sb);
}

static char *otf_buf;

int otf_read(void)
{
	int i;
	otf_buf = otf_input(0);
	upm = U16(otf_table(otf_buf, "head"), 18);
	otf_name(otf_buf, otf_table(otf_buf, "name"));
	otf_cmap(otf_buf, otf_table(otf_buf, "cmap"));
	otf_post(otf_buf, otf_table(otf_buf, "post"));
	if (otf_table(otf_buf, "glyf"))
		otf_glyf(otf_buf, otf_table(otf_buf, "glyf"));
	if (otf_table(otf_buf, "CFF "))
		otf_cff(otf_buf, otf_table(otf_buf, "CFF "));
	otf_hmtx(otf_buf, otf_table(otf_buf, "hmtx"));
	for (i = 0; i < glyph_n; i++) {
		trfn_char(glyph_name[i], -1,
			glyph_code[i] != 0xffff ? glyph_code[i] : 0,
			owid(glyph_wid[i]),
			owid(glyph_bbox[i][0]), owid(glyph_bbox[i][1]),
			owid(glyph_bbox[i][2]), owid(glyph_bbox[i][3]));
	}
	if (otf_table(otf_buf, "kern"))
		otf_kern(otf_buf, otf_table(otf_buf, "kern"));
	return 0;
}

void otf_feat(int r, int k, int w)
{
	res = r;
	kmin = k;
	warn = w;
	if (otf_table(otf_buf, "GSUB"))
		otf_gsub(otf_buf, otf_table(otf_buf, "GSUB"));
	if (otf_table(otf_buf, "GPOS"))
		otf_gpos(otf_buf, otf_table(otf_buf, "GPOS"));
}

/* glyph groups */
static int *ggrp_g[NGRPS];
static int ggrp_len[NGRPS];
static int ggrp_n;

static int ggrp_find(int *src, int n)
{
	int i, j;
	for (i = 0; i < ggrp_n; i++) {
		if (ggrp_len[i] == n) {
			for (j = 0; j < n; j++)
				if (src[j] != ggrp_g[i][j])
					break;
			if (j == n)
				return i;
		}
	}
	return -1;
}

static int ggrp_make(int *src, int n)
{
	int id = ggrp_find(src, n);
	int i;
	if (id >= 0)
		return id;
	id = ggrp_n++;
	ggrp_g[id] = malloc(n * sizeof(ggrp_g[id][0]));
	ggrp_len[id] = n;
	for (i = 0; i < n; i++)
		ggrp_g[id][i] = src[i];
	printf("ggrp %d %d", id, n);
	for (i = 0; i < n; i++)
		printf(" %s", glyph_name[src[i]]);
	printf("\n");
	return id;
}

static char *macset[] = {
	".notdef", ".null", "nonmarkingreturn", "space", "exclam",
	"quotedbl", "numbersign", "dollar", "percent", "ampersand",
	"quotesingle", "parenleft", "parenright", "asterisk", "plus",
	"comma", "hyphen", "period", "slash", "zero",
	"one", "two", "three", "four", "five",
	"six", "seven", "eight", "nine", "colon",
	"semicolon", "less", "equal", "greater", "question",
	"at", "A", "B", "C", "D",
	"E", "F", "G", "H", "I",
	"J", "K", "L", "M", "N",
	"O", "P", "Q", "R", "S",
	"T", "U", "V", "W", "X",
	"Y", "Z", "bracketleft", "backslash", "bracketright",
	"asciicircum", "underscore", "grave", "a", "b",
	"c", "d", "e", "f", "g",
	"h", "i", "j", "k", "l",
	"m", "n", "o", "p", "q",
	"r", "s", "t", "u", "v",
	"w", "x", "y", "z", "braceleft",
	"bar", "braceright", "asciitilde", "Adieresis", "Aring",
	"Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis",
	"aacute", "agrave", "acircumflex", "adieresis", "atilde",
	"aring", "ccedilla", "eacute", "egrave", "ecircumflex",
	"edieresis", "iacute", "igrave", "icircumflex", "idieresis",
	"ntilde", "oacute", "ograve", "ocircumflex", "odieresis",
	"otilde", "uacute", "ugrave", "ucircumflex", "udieresis",
	"dagger", "degree", "cent", "sterling", "section",
	"bullet", "paragraph", "germandbls", "registered", "copyright",
	"trademark", "acute", "dieresis", "notequal", "AE",
	"Oslash", "infinity", "plusminus", "lessequal", "greaterequal",
	"yen", "mu", "partialdiff", "summation", "product",
	"pi", "integral", "ordfeminine", "ordmasculine", "Omega",
	"ae", "oslash", "questiondown", "exclamdown", "logicalnot",
	"radical", "florin", "approxequal", "Delta", "guillemotleft",
	"guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde",
	"Otilde", "OE", "oe", "endash", "emdash",
	"quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide",
	"lozenge", "ydieresis", "Ydieresis", "fraction", "currency",
	"guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl",
	"periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
	"Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute",
	"Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex",
	"apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave",
	"dotlessi", "circumflex", "tilde", "macron", "breve",
	"dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek",
	"caron", "Lslash", "lslash", "Scaron", "scaron",
	"Zcaron", "zcaron", "brokenbar", "Eth", "eth",
	"Yacute", "yacute", "Thorn", "thorn", "minus",
	"multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf",
	"onequarter", "threequarters", "franc", "Gbreve", "gbreve",
	"Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute",
	"Ccaron", "ccaron", "dcroat",
};

static char *stdset[] = {
	".notdef", "space", "exclam", "quotedbl", "numbersign",
	"dollar", "percent", "ampersand", "quoteright", "parenleft",
	"parenright", "asterisk", "plus", "comma", "hyphen",
	"period", "slash", "zero", "one", "two",
	"three", "four", "five", "six", "seven",
	"eight", "nine", "colon", "semicolon", "less",
	"equal", "greater", "question", "at", "A",
	"B", "C", "D", "E", "F",
	"G", "H", "I", "J", "K",
	"L", "M", "N", "O", "P",
	"Q", "R", "S", "T", "U",
	"V", "W", "X", "Y", "Z",
	"bracketleft", "backslash", "bracketright", "asciicircum", "underscore",
	"quoteleft", "a", "b", "c", "d",
	"e", "f", "g", "h", "i",
	"j", "k", "l", "m", "n",
	"o", "p", "q", "r", "s",
	"t", "u", "v", "w", "x",
	"y", "z", "braceleft", "bar", "braceright",
	"asciitilde", "exclamdown", "cent", "sterling", "fraction",
	"yen", "florin", "section", "currency", "quotesingle",
	"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi",
	"fl", "endash", "dagger", "daggerdbl", "periodcentered",
	"paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright",
	"guillemotright", "ellipsis", "perthousand", "questiondown", "grave",
	"acute", "circumflex", "tilde", "macron", "breve",
	"dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
	"ogonek", "caron", "emdash", "AE", "ordfeminine",
	"Lslash", "Oslash", "OE", "ordmasculine", "ae",
	"dotlessi", "lslash", "oslash", "oe", "germandbls",
	"onesuperior", "logicalnot", "mu", "trademark", "Eth",
	"onehalf", "plusminus", "Thorn", "onequarter", "divide",
	"brokenbar", "degree", "thorn", "threequarters", "twosuperior",
	"registered", "minus", "eth", "multiply", "threesuperior",
	"copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
	"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex",
	"Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis",
	"Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis",
	"Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex",
	"Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron",
	"aacute", "acircumflex", "adieresis", "agrave", "aring",
	"atilde", "ccedilla", "eacute", "ecircumflex", "edieresis",
	"egrave", "iacute", "icircumflex", "idieresis", "igrave",
	"ntilde", "oacute", "ocircumflex", "odieresis", "ograve",
	"otilde", "scaron", "uacute", "ucircumflex", "udieresis",
	"ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall",
	"Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
	"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "zerooldstyle",
	"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
	"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior",
	"threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior",
	"centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior",
	"msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
	"tsuperior", "ff", "ffi", "ffl", "parenleftinferior",
	"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall",
	"Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall",
	"Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall",
	"Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
	"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall",
	"Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall",
	"colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall",
	"centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall",
	"Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash",
	"hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall",
	"oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird",
	"twothirds", "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior",
	"sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior",
	"twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior",
	"seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior",
	"periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall",
	"Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall",
	"Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
	"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
	"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall",
	"OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall",
	"Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000",
	"001.001", "001.002", "001.003", "Black", "Bold",
	"Book", "Light", "Medium", "Regular", "Roman",
	"Semibold",
};