shithub: neatmkfn

Download patch

ref: dfde766d2764c91c7c28c43d8c3b8244cd3b4fb1
parent: daf293759b5de8bc70c2a0e22b9e6516af7b6967
author: Ali Gholami Rudi <ali@rudi.ir>
date: Sat Jun 16 07:13:21 EDT 2018

mkfn: support OpenType font collections

--- a/afm.c
+++ b/afm.c
@@ -3,7 +3,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include "trfn.h"
+#include "mkfn.h"
 
 #define TOKLEN		256
 
@@ -17,6 +17,11 @@
 	return s;
 }
 
+static int uwid(int w)
+{
+	long div = 72000 / mkfn_res;
+	return (w < 0 ? w - div / 20 : w + div / 20) * (long) 10 / div;
+}
 int afm_read(void)
 {
 	char ln[1024];
@@ -25,13 +30,13 @@
 	char wid[TOKLEN] = "", field[TOKLEN] = "";
 	char llx[TOKLEN] = "0", lly[TOKLEN] = "0";
 	char urx[TOKLEN] = "0", ury[TOKLEN] = "0";
+	char fontname[128];
 	char *s;
 	while (fgets(ln, sizeof(ln), stdin)) {
 		if (ln[0] == '#')
 			continue;
 		if (!strncmp("FontName ", ln, 8)) {
-			sscanf(ln, "FontName %s", ch);
-			trfn_psfont(ch);
+			sscanf(ln, "FontName %s", fontname);
 			continue;
 		}
 		if (!strncmp("StartCharMetrics", ln, 16))
@@ -72,9 +77,11 @@
 			break;
 		}
 		if (ch[0] && pos[0] && wid[0])
-			trfn_char(ch, atoi(pos), 0, atoi(wid),
-				atoi(llx), atoi(lly), atoi(urx), atoi(ury));
+			mkfn_char(ch, atoi(pos), 0, uwid(atoi(wid)),
+				uwid(atoi(llx)), uwid(atoi(lly)),
+				uwid(atoi(urx)), uwid(atoi(ury)));
 	}
+	mkfn_header(fontname);
 	while (fgets(ln, sizeof(ln), stdin)) {
 		if (ln[0] == '#')
 			continue;
@@ -87,7 +94,7 @@
 		if (!strncmp("EndKernPairs", ln, 12))
 			break;
 		if (sscanf(ln, "KPX %s %s %s", c1, c2, wid) == 3)
-			trfn_kern(c1, c2, atoi(wid));
+			mkfn_kern(c1, c2, uwid(atoi(wid)));
 	}
 	return 0;
 }
--- a/mkfn.c
+++ b/mkfn.c
@@ -15,15 +15,29 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
+#include <ctype.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
-#include "trfn.h"
+#include "mkfn.h"
 
 #define LEN(a)		((sizeof(a) / sizeof((a)[0])))
 
-static char *trfn_scripts;	/* filtered scripts */
-static char *trfn_langs;	/* filtered languages */
+static char *mkfn_scripts;	/* filtered scripts */
+static char *mkfn_langs;	/* filtered languages */
+static char *mkfn_subfont;	/* filtered font */
+static char *mkfn_trname;	/* font troff name */
+static char *mkfn_psname;	/* font ps name */
+static char *mkfn_path;		/* font path */
+int mkfn_res = 720;		/* device resolution */
+int mkfn_warn;			/* warn about unsupported features */
+int mkfn_kmin;			/* minimum kerning value */
+int mkfn_swid;			/* space width */
+int mkfn_special;		/* special flag */
+int mkfn_bbox;			/* include bounding box */
+int mkfn_noligs;		/* suppress ligatures */
+int mkfn_pos = 1;		/* include glyph positions */
+int mkfn_dry;			/* generate no output */
 
 /* OpenType specifies a specific feature order for different scripts */
 static char *scriptorder[][2] = {
@@ -60,40 +74,55 @@
 };
 
 /* return 1 if the given script is to be included */
-int trfn_script(char *script, int nscripts)
+int mkfn_script(char *script, int nscripts)
 {
-	/* fill trfn_scripts (if unspecified) in the first call */
-	if (!trfn_scripts) {
+	/* fill mkfn_scripts (if unspecified) in the first call */
+	if (!mkfn_scripts) {
 		if (nscripts == 1 || !script)
 			return 1;
 		if (!strcmp("DFLT", script))
-			trfn_scripts = "DFLT";
+			mkfn_scripts = "DFLT";
 		else
-			trfn_scripts = "latn";
+			mkfn_scripts = "latn";
 	}
-	if (!strcmp("help", trfn_scripts))
-		fprintf(stderr, "script: %s\n", script ? script : "");
+	if (!strcmp("list", mkfn_scripts))
+		printf("%s\n", script ? script : "");
 	if (strchr(script, ' '))
 		*strchr(script, ' ') = '\0';
-	return !!strstr(trfn_scripts, script);
+	return !!strstr(mkfn_scripts, script);
 }
 
 /* return 1 if the given language is to be included */
-int trfn_lang(char *lang, int nlangs)
+int mkfn_lang(char *lang, int nlangs)
 {
-	if (!trfn_langs)
+	if (!mkfn_langs)
 		return 1;
 	if (!lang)
 		lang = "";
-	if (!strcmp("help", trfn_langs))
-		fprintf(stderr, "lang: %s\n", lang);
+	if (!strcmp("list", mkfn_langs))
+		printf("%s\n", lang);
 	if (strchr(lang, ' '))
 		*strchr(lang, ' ') = '\0';
-	return !!strstr(trfn_langs, lang);
+	return !!strstr(mkfn_langs, lang);
 }
 
+/* return 1 if the given font is to be included */
+int mkfn_font(char *font)
+{
+	static int idx;			/* font index */
+	idx++;
+	if (!mkfn_subfont)
+		return idx == 1;
+	if (!strcmp("list", mkfn_subfont))
+		printf("%s\n", font);
+	if (mkfn_subfont[0] && isdigit((unsigned char) mkfn_subfont[0]))
+		if (atoi(mkfn_subfont) == idx)
+			return 1;
+	return !strcmp(mkfn_subfont, font);
+}
+
 /* return the rank of the given feature, for the current script */
-int trfn_featrank(char *scrp, char *feat)
+int mkfn_featrank(char *scrp, char *feat)
 {
 	static char **order;
 	int i;
@@ -108,9 +137,26 @@
 	return 1000;
 }
 
+void mkfn_header(char *fontname)
+{
+	if (mkfn_dry)
+		return;
+	if (mkfn_trname)
+		printf("name %s\n", mkfn_trname);
+	if (mkfn_psname)
+		printf("fontname %s\n", mkfn_psname);
+	if (!mkfn_psname && fontname && fontname[0])
+		printf("fontname %s\n", fontname);
+	if (mkfn_path)
+		printf("fontpath %s\n", mkfn_path);
+	trfn_header();
+	if (mkfn_special)
+		printf("special\n");
+	trfn_cdefs();
+}
+
 int otf_read(void);
 int afm_read(void);
-void otf_feat(int res, int kmin, int warn);
 
 static char *usage =
 	"Usage: mktrfn [options] <input >output\n"
@@ -126,20 +172,14 @@
 	"  -b      \tinclude glyph bounding boxes\n"
 	"  -l      \tsuppress the ligatures line\n"
 	"  -n      \tsuppress glyph positions\n"
-	"  -S scrs \tcomma-separated list of scripts to include (help to list)\n"
-	"  -L langs\tcomma-separated list of languages to include (help to list)\n"
+	"  -S scrs \tcomma-separated list of scripts to include (list to list)\n"
+	"  -L langs\tcomma-separated list of languages to include (list to list)\n"
+	"  -F font \tfont name or index in a font collection (list to list)\n"
 	"  -w      \twarn about unsupported font features\n";
 
 int main(int argc, char *argv[])
 {
 	int afm = 1;
-	int res = 720;
-	int spc = 0;
-	int kmin = 0;
-	int bbox = 0;
-	int warn = 0;
-	int ligs = 1;
-	int pos = 1;
 	int i;
 	for (i = 1; i < argc && argv[i][0] == '-'; i++) {
 		switch (argv[i][1]) {
@@ -147,43 +187,49 @@
 			afm = 1;
 			break;
 		case 'b':
-			bbox = 1;
+			mkfn_bbox = 1;
 			break;
 		case 'f':
-			trfn_pspath(argv[i][2] ? argv[i] + 2 : argv[++i]);
+			mkfn_path = argv[i][2] ? argv[i] + 2 : argv[++i];
 			break;
+		case 'F':
+			mkfn_subfont = argv[i][2] ? argv[i] + 2 : argv[++i];
+			mkfn_dry = !strcmp("list", mkfn_subfont);
+			break;
 		case 'k':
-			kmin = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
+			mkfn_kmin = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
 			break;
 		case 'l':
-			ligs = 0;
+			mkfn_noligs = 1;
 			break;
 		case 'L':
-			trfn_langs = argv[i][2] ? argv[i] + 2 : argv[++i];
+			mkfn_langs = argv[i][2] ? argv[i] + 2 : argv[++i];
+			mkfn_dry = !strcmp("list", mkfn_langs);
 			break;
 		case 'n':
-			pos = 0;
+			mkfn_pos = 0;
 			break;
 		case 'o':
 			afm = 0;
 			break;
 		case 'p':
-			trfn_psfont(argv[i][2] ? argv[i] + 2 : argv[++i]);
+			mkfn_psname = argv[i][2] ? argv[i] + 2 : argv[++i];
 			break;
 		case 'r':
-			res = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
+			mkfn_res = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]);
 			break;
 		case 's':
-			spc = 1;
+			mkfn_special = 1;
 			break;
 		case 'S':
-			trfn_scripts = argv[i][2] ? argv[i] + 2 : argv[++i];
+			mkfn_scripts = argv[i][2] ? argv[i] + 2 : argv[++i];
+			mkfn_dry = !strcmp("list", mkfn_scripts);
 			break;
 		case 't':
-			trfn_trfont(argv[i][2] ? argv[i] + 2 : argv[++i]);
+			mkfn_trname = argv[i][2] ? argv[i] + 2 : argv[++i];
 			break;
 		case 'w':
-			warn = 1;
+			mkfn_warn = 1;
 			break;
 		default:
 			printf("%s", usage);
@@ -190,14 +236,12 @@
 			return 0;
 		}
 	}
-	trfn_init(res, spc, kmin, bbox, ligs, pos);
-	if (afm)
-		afm_read();
-	else
-		otf_read();
-	trfn_print();
-	if (!afm)
-		otf_feat(res, kmin, warn);
+	trfn_init();
+	if ((afm ? afm_read() : otf_read())) {
+		fprintf(stderr, "neatmkfn: cannot parse the font\n");
+		trfn_done();
+		return 1;
+	}
 	trfn_done();
 	return 0;
 }
--- /dev/null
+++ b/mkfn.h
@@ -1,0 +1,42 @@
+/* functions used by afm.c and otf.c */
+void mkfn_trfont(char *name);
+void mkfn_psfont(char *fontname);
+void mkfn_header(char *fontname);
+void mkfn_char(char *c, int n, int u, int wid, int llx, int lly, int urx, int ury);
+void mkfn_kern(char *c1, char *c2, int x);
+int mkfn_font(char *font);
+int mkfn_script(char *script, int nscripts);
+int mkfn_lang(char *lang, int nlangs);
+int mkfn_featrank(char *scrp, char *feat);
+
+/* functions defined in trfn.c and used by mkfn.c */
+void trfn_init(void);
+void trfn_cdefs(void);
+void trfn_header(void);
+void trfn_done(void);
+
+/* global variables */
+extern int mkfn_res;		/* device resolution */
+extern int mkfn_warn;		/* warn about unsupported features */
+extern int mkfn_kmin;		/* minimum kerning value */
+extern int mkfn_swid;		/* space width */
+extern int mkfn_special;	/* special flag */
+extern int mkfn_bbox;		/* include bounding box */
+extern int mkfn_noligs;		/* suppress ligatures */
+extern int mkfn_pos;		/* include glyph positions */
+extern int mkfn_dry;		/* generate no output */
+
+/* variable length string buffer */
+struct sbuf *sbuf_make(void);
+void sbuf_free(struct sbuf *sb);
+char *sbuf_done(struct sbuf *sb);
+void sbuf_str(struct sbuf *sbuf, char *s);
+void sbuf_mem(struct sbuf *sbuf, char *s, int len);
+char *sbuf_buf(struct sbuf *sb);
+void sbuf_printf(struct sbuf *sbuf, char *s, ...);
+
+/* dictionary */
+struct tab *tab_alloc(int sz);
+void tab_free(struct tab *tab);
+void tab_put(struct tab *tab, char *k, void *v);
+void *tab_get(struct tab *tab, char *k);
--- a/otf.c
+++ b/otf.c
@@ -5,8 +5,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include "sbuf.h"
-#include "trfn.h"
+#include "mkfn.h"
 
 #define MAX(a, b)	((a) < (b) ? (b) : (a))
 #define LEN(a)		(sizeof(a) / sizeof((a)[0]))
@@ -36,10 +35,13 @@
 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 */
 
+struct otf {
+	void *otf;		/* TTC header or offset table */
+	void *off;		/* offset table */
+	char name[128];		/* font name */
+};
+
 static char *macset[];
 static char *stdset[];
 
@@ -50,7 +52,7 @@
 
 static int uwid(int w)
 {
-	int d = 72000 / res;
+	int d = 72000 / mkfn_res;
 	return (w < 0 ? owid(w) - d / 20 : owid(w) + d / 20) * 10 / d;
 }
 
@@ -64,7 +66,7 @@
 /* report unsupported otf tables */
 static void otf_unsupported(char *sub, int type, int fmt)
 {
-	if (warn) {
+	if (mkfn_warn) {
 		fprintf(stderr, "neatmkfn: unsupported %s lookup %d", sub, type);
 		if (fmt > 0)
 			fprintf(stderr, " format %d", fmt);
@@ -73,20 +75,20 @@
 }
 
 /* find the otf table with the given name */
-static void *otf_table(void *otf, char *name)
+static void *otf_table(struct otf *otf, char *name)
 {
-	int nrecs = U16(otf, 4);
+	int nrecs = U16(otf->off, 4);
 	int i;
 	for (i = 0; i < nrecs; i++) {
-		void *rec = otf + 12 + i * 16;	/* an otf table record */
+		void *rec = otf->off + 12 + i * 16;	/* an otf table record */
 		if (!strncmp(rec, name, 4))
-			return otf + U32(rec, 8);
+			return otf->otf + U32(rec, 8);
 	}
 	return NULL;
 }
 
 /* obtain postscript font name from name table */
-static void otf_name(void *otf, void *tab)
+static void otf_name(struct otf *otf, void *tab)
 {
 	char name[256];
 	void *str = tab + U16(tab, 4);		/* storage area */
@@ -103,13 +105,13 @@
 		if (pid == 1 && eid == 0 && lid == 0 && nid == 6) {
 			memcpy(name, str + off, len);
 			name[len] = '\0';
-			trfn_psfont(name);
+			snprintf(otf->name, sizeof(otf->name), "%s", name);
 		}
 	}
 }
 
 /* parse otf cmap format 4 subtable */
-static void otf_cmap4(void *otf, void *cmap4)
+static void otf_cmap4(struct otf *otf, void *cmap4)
 {
 	int nsegs;
 	void *ends, *begs, *deltas, *offsets;
@@ -137,7 +139,7 @@
 }
 
 /* parse otf cmap header */
-static void otf_cmap(void *otf, void *cmap)
+static void otf_cmap(struct otf *otf, void *cmap)
 {
 	int nrecs = U16(cmap, 2);
 	int i;
@@ -152,7 +154,7 @@
 	}
 }
 
-static void otf_post(void *otf, void *post)
+static void otf_post(struct otf *otf, void *post)
 {
 	void *post2;			/* version 2.0 header */
 	void *index;			/* glyph name indices */
@@ -178,7 +180,7 @@
 	}
 }
 
-static void otf_glyf(void *otf, void *glyf)
+static void otf_glyf(struct otf *otf, void *glyf)
 {
 	void *maxp = otf_table(otf, "maxp");
 	void *head = otf_table(otf, "head");
@@ -204,7 +206,7 @@
 	}
 }
 
-static void otf_hmtx(void *otf, void *hmtx)
+static void otf_hmtx(struct otf *otf, void *hmtx)
 {
 	void *hhea = otf_table(otf, "hhea");
 	int n;
@@ -216,7 +218,7 @@
 		glyph_wid[i] = glyph_wid[n - 1];
 }
 
-static void otf_kern(void *otf, void *kern)
+static void otf_kern(struct otf *otf, void *kern)
 {
 	int off = 4;
 	int i, j;
@@ -231,8 +233,8 @@
 				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));
+				mkfn_kern(glyph_name[c1], glyph_name[c2],
+					uwid(val));
 			}
 		}
 	}
@@ -350,7 +352,7 @@
 	int i;
 	for (i = 0; i < 8; i++) {
 		if (fmt & (1 << i)) {
-			if (abs(uwid(S16(rec, off))) >= MAX(1, kmin))
+			if (abs(uwid(S16(rec, off))) >= MAX(1, mkfn_kmin))
 				return 0;
 			off += 2;
 		}
@@ -359,7 +361,7 @@
 }
 
 /* single adjustment positioning */
-static void otf_gpostype1(void *otf, void *sub, char *feat)
+static void otf_gpostype1(struct otf *otf, void *sub, char *feat)
 {
 	int fmt = U16(sub, 0);
 	int vfmt = U16(sub, 4);
@@ -391,7 +393,7 @@
 }
 
 /* pair adjustment positioning */
-static void otf_gpostype2(void *otf, void *sub, char *feat)
+static void otf_gpostype2(struct otf *otf, void *sub, char *feat)
 {
 	int fmt = U16(sub, 0);
 	int vfmt1 = U16(sub, 4);	/* valuerecord 1 */
@@ -454,7 +456,7 @@
 }
 
 /* cursive attachment positioning */
-static void otf_gpostype3(void *otf, void *sub, char *feat)
+static void otf_gpostype3(struct otf *otf, void *sub, char *feat)
 {
 	int fmt = U16(sub, 0);
 	int *cov, *icov, *ocov;
@@ -484,9 +486,8 @@
 		if (prev) {
 			int dx = -uwid(S16(sub, prev + 2));
 			int dy = -uwid(S16(sub, prev + 4));
-			if (otf_r2l(feat)) {
+			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);
@@ -506,7 +507,7 @@
 }
 
 /* mark-to-base attachment positioning */
-static void otf_gpostype4(void *otf, void *sub, char *feat)
+static void otf_gpostype4(struct otf *otf, void *sub, char *feat)
 {
 	int fmt = U16(sub, 0);
 	int *mcov;		/* mark coverage */
@@ -601,7 +602,7 @@
 }
 
 /* single substitution */
-static void otf_gsubtype1(void *otf, void *sub, char *feat, struct gctx *ctx)
+static void otf_gsubtype1(struct otf *otf, void *sub, char *feat, struct gctx *ctx)
 {
 	int *cov;
 	int fmt = U16(sub, 0);
@@ -635,7 +636,7 @@
 }
 
 /* alternate substitution */
-static void otf_gsubtype3(void *otf, void *sub, char *feat, struct gctx *ctx)
+static void otf_gsubtype3(struct otf *otf, void *sub, char *feat, struct gctx *ctx)
 {
 	int *cov;
 	int fmt = U16(sub, 0);
@@ -660,7 +661,7 @@
 }
 
 /* ligature substitution */
-static void otf_gsubtype4(void *otf, void *sub, char *feat, struct gctx *ctx)
+static void otf_gsubtype4(struct otf *otf, void *sub, char *feat, struct gctx *ctx)
 {
 	int fmt = U16(sub, 0);
 	int *cov;
@@ -689,7 +690,7 @@
 }
 
 /* chaining contextual substitution */
-static void otf_gsubtype6(void *otf, void *sub, char *feat, void *gsub)
+static void otf_gsubtype6(struct otf *otf, void *sub, char *feat, void *gsub)
 {
 	struct gctx ctx = {{0}};
 	void *lookups = gsub + U16(gsub, 8);
@@ -755,7 +756,7 @@
 };
 
 /* parse the given gsub/gpos feature table */
-static int otf_featrec(void *otf, void *gtab, void *featrec,
+static int otf_featrec(struct otf *otf, void *gtab, void *featrec,
 			char *stag, char *ltag,
 			struct otflookup *lookups, int lookups_n)
 {
@@ -783,7 +784,7 @@
 }
 
 /* parse the given language table and its feature tables */
-static int otf_lang(void *otf, void *gtab, void *lang, char *stag, char *ltag,
+static int otf_lang(struct otf *otf, void *gtab, void *lang, char *stag, char *ltag,
 		struct otflookup *lookups, int lookups_n)
 {
 	void *feats = gtab + U16(gtab, 6);
@@ -815,13 +816,13 @@
 	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);
+	if (mkfn_featrank(l1->scrp, l1->feat) != mkfn_featrank(l1->scrp, l2->feat))
+		return mkfn_featrank(l1->scrp, l1->feat) - mkfn_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)
+static int otf_gtab(struct otf *otf, void *gpos, struct otflookup *lookups)
 {
 	void *scripts = gpos + U16(gpos, 4);
 	int nscripts, nlangs;
@@ -834,11 +835,11 @@
 		void *grec = scripts + 2 + 6 * i;
 		memcpy(stag, grec, 4);
 		stag[4] = '\0';
-		if (!trfn_script(stag, nscripts))
+		if (!mkfn_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)))
+		if (U16(script, 0) && mkfn_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++) {
@@ -845,7 +846,7 @@
 			void *lrec = script + 4 + 6 * j;
 			memcpy(ltag, lrec, 4);
 			ltag[4] = '\0';
-			if (trfn_lang(ltag, nlangs + (U16(script, 0) != 0)))
+			if (mkfn_lang(ltag, nlangs + (U16(script, 0) != 0)))
 				n = otf_lang(otf, gpos, script + U16(lrec, 4),
 						stag, ltag, lookups, n);
 		}
@@ -854,12 +855,14 @@
 	return n;
 }
 
-static void otf_gpos(void *otf, void *gpos)
+static void otf_gpos(struct otf *otf, void *gpos)
 {
 	struct otflookup lookups[NLOOKUPS];
 	void *lookuplist = gpos + U16(gpos, 8);
 	int nlookups = otf_gtab(otf, gpos, lookups);
 	int i, j;
+	if (mkfn_dry)
+		return;
 	for (i = 0; i < nlookups; i++) {
 		void *lookup = lookuplist + U16(lookuplist, 2 + 2 * lookups[i].lookup);
 		int ltype = U16(lookup, 0);
@@ -892,12 +895,14 @@
 	}
 }
 
-static void otf_gsub(void *otf, void *gsub)
+static void otf_gsub(struct otf *otf, void *gsub)
 {
 	struct otflookup lookups[NLOOKUPS];
 	void *lookuplist = gsub + U16(gsub, 8);
 	int nlookups = otf_gtab(otf, gsub, lookups);
 	int i, j;
+	if (mkfn_dry)
+		return;
 	for (i = 0; i < nlookups; i++) {
 		void *lookup = lookuplist + U16(lookuplist, 2 + 2 * lookups[i].lookup);
 		int ltype = U16(lookup, 0);
@@ -1040,7 +1045,7 @@
 	dst[len] = '\0';
 }
 
-static void otf_cff(void *otf, void *cff)
+static void otf_cff(struct otf *otf, void *cff)
 {
 	void *nameidx;		/* name index */
 	void *topidx;		/* top dict index */
@@ -1067,8 +1072,8 @@
 	if (cffidx_cnt(nameidx) > 0) {
 		char name[256] = "";
 		memcpy(name, cffidx_get(nameidx, 0), cffidx_len(nameidx, 0));
-		if (name[0])
-			trfn_psfont(name);
+		if (name[0] && !otf->name[0])
+			snprintf(otf->name, sizeof(otf->name), "%s", name);
 	}
 	/* read charset: glyph to character name */
 	if (!badcff && U8(charset, 0) == 0) {
@@ -1100,20 +1105,30 @@
 	return sbuf_done(sb);
 }
 
-static char *otf_buf;
+static void otf_feat(struct otf *otf)
+{
+	if (otf_table(otf, "GSUB"))
+		otf_gsub(otf, otf_table(otf, "GSUB"));
+	if (otf_table(otf, "GPOS"))
+		otf_gpos(otf, otf_table(otf, "GPOS"));
+}
 
-int otf_read(void)
+int otf_offsettable(void *otf_otf, void *otf_off)
 {
 	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 "));
+	unsigned tag = U32(otf_off, 0);
+	struct otf otf_cur = {otf_otf, otf_off};
+	struct otf *otf = &otf_cur;
+	if (tag != 0x00010000 && tag != 0x4F54544F)
+		return 1;
+	upm = U16(otf_table(otf, "head"), 18);
+	otf_name(otf, otf_table(otf, "name"));
+	otf_cmap(otf, otf_table(otf, "cmap"));
+	otf_post(otf, otf_table(otf, "post"));
+	if (otf_table(otf, "glyf"))
+		otf_glyf(otf, otf_table(otf, "glyf"));
+	if (otf_table(otf, "CFF "))
+		otf_cff(otf, otf_table(otf, "CFF "));
 	for (i = 0; i < glyph_n; i++) {
 		if (!glyph_name[i][0]) {
 			if (glyph_code[i])
@@ -1122,28 +1137,39 @@
 				sprintf(glyph_name[i], "gl%05X", i);
 		}
 	}
-	otf_hmtx(otf_buf, otf_table(otf_buf, "hmtx"));
+	otf_hmtx(otf, otf_table(otf, "hmtx"));
+	if (!mkfn_font(otf->name))
+		return 0;
 	for (i = 0; i < glyph_n; i++) {
-		trfn_char(glyph_name[i], -1,
+		mkfn_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]));
+			uwid(glyph_wid[i]),
+			uwid(glyph_bbox[i][0]), uwid(glyph_bbox[i][1]),
+			uwid(glyph_bbox[i][2]), uwid(glyph_bbox[i][3]));
 	}
-	if (otf_table(otf_buf, "kern"))
-		otf_kern(otf_buf, otf_table(otf_buf, "kern"));
+	mkfn_header(otf->name);
+	if (otf_table(otf, "kern"))
+		otf_kern(otf, otf_table(otf, "kern"));
+	otf_feat(otf);
 	return 0;
 }
 
-void otf_feat(int r, int k, int w)
+int otf_read(void)
 {
-	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"));
+	char *otf_buf = otf_input(0);
+	unsigned tag = U32(otf_buf, 0);
+	int n, i;
+	if (tag == 0x00010000 || tag == 0x4F54544F)
+		return otf_offsettable(otf_buf, otf_buf);
+	if (tag != 0x74746366)
+		return 1;
+	/* OpenType Collections */
+	n = U32(otf_buf, 8);
+	for (i = 0; i < n; i++) {
+		void *off = otf_buf + U32(otf_buf, 12 + i * 4);
+		otf_offsettable(otf_buf, off);
+	}
+	return 0;
 }
 
 /* glyph groups */
--- a/sbuf.c
+++ b/sbuf.c
@@ -3,7 +3,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include "sbuf.h"
+#include "mkfn.h"
 
 #define MAX(a, b)	((a) < (b) ? (b) : (a))
 #define ALIGN(n, a)	(((n) + (a) - 1) & ~((a) - 1))
--- a/sbuf.h
+++ /dev/null
@@ -1,8 +1,0 @@
-/* variable length string buffer */
-struct sbuf *sbuf_make(void);
-void sbuf_free(struct sbuf *sb);
-char *sbuf_done(struct sbuf *sb);
-void sbuf_str(struct sbuf *sbuf, char *s);
-void sbuf_mem(struct sbuf *sbuf, char *s, int len);
-char *sbuf_buf(struct sbuf *sb);
-void sbuf_printf(struct sbuf *sbuf, char *s, ...);
--- a/tab.c
+++ b/tab.c
@@ -1,7 +1,7 @@
 /* A Dictionary */
 #include <stdlib.h>
 #include <string.h>
-#include "tab.h"
+#include "mkfn.h"
 
 struct tab {
 	char **keys;
--- a/tab.h
+++ /dev/null
@@ -1,4 +1,0 @@
-struct tab *tab_alloc(int sz);
-void tab_free(struct tab *tab);
-void tab_put(struct tab *tab, char *k, void *v);
-void *tab_get(struct tab *tab, char *k);
--- a/trfn.c
+++ b/trfn.c
@@ -2,37 +2,23 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include "sbuf.h"
-#include "tab.h"
-#include "trfn.h"
+#include "mkfn.h"
 #include "trfn_agl.h"
 #include "trfn_ch.h"
 
-#define WX(w)		(((w) < 0 ? (w) - trfn_div / 20 : (w) + trfn_div / 20) * (long) 10 / trfn_div)
 #define LEN(a)		((sizeof(a) / sizeof((a)[0])))
 #define HEXDIGS		"0123456789ABCDEF"
 #define NCHAR		8	/* number of characters per glyph */
 #define GNLEN		64	/* glyph name length */
-#define AGLLEN		8192	/* adobe glyphlist length */
 
-static struct sbuf *sbuf_char;	/* characters */
-static struct sbuf *sbuf_kern;	/* kerning pairs */
-static int trfn_div;		/* divisor of widths x 10 */
-static int trfn_swid;		/* space width */
-static int trfn_special;	/* special flag */
-static int trfn_kmin;		/* minimum kerning value */
-static int trfn_bbox;		/* include bounding box */
-static int trfn_noligs;		/* suppress ligatures */
-static int trfn_pos;		/* include glyph positions */
-static char trfn_ligs[8192];	/* font ligatures */
-static char trfn_ligs2[8192];	/* font ligatures, whose length is two */
-static char trfn_trname[256];	/* font troff name */
-static char trfn_psname[256];	/* font ps name */
-static char trfn_path[4096];	/* font path */
+/* for buffering the output */
+static struct sbuf *sbuf_char;	/* character definitions */
+/* handling ligatures */
+static char mkfn_ligs[8192];	/* font ligatures */
+static char mkfn_ligs2[8192];	/* font ligatures, whose length is two */
 /* character type */
-static int trfn_asc;		/* minimum height of glyphs with ascender */
-static int trfn_desc;		/* minimum depth of glyphs with descender */
-
+static int mkfn_asc;		/* minimum height of glyphs with ascender */
+static int mkfn_desc;		/* minimum depth of glyphs with descender */
 /* lookup tables */
 static struct tab *tab_agl;	/* adobe glyph list table */
 static struct tab *tab_alts;	/* character aliases table */
@@ -224,7 +210,7 @@
 
 static void trfn_ligput(char *c)
 {
-	char *dst = strlen(c) == 2 ? trfn_ligs2 : trfn_ligs;
+	char *dst = strlen(c) == 2 ? mkfn_ligs2 : mkfn_ligs;
 	sprintf(strchr(dst, '\0'), "%s ", c);
 }
 
@@ -247,25 +233,25 @@
 {
 	int typ = 0;
 	int c = !s[0] || s[1] ? 0 : (unsigned char) *s;
-	if (c == 't' && !trfn_asc)
-		trfn_asc = ury;
+	if (c == 't' && !mkfn_asc)
+		mkfn_asc = ury;
 	if ((c == 'g' || c == 'j' || c == 'p' || c == 'q' || c == 'y') &&
-			(!trfn_desc || trfn_desc < lly))
-		trfn_desc = lly;
-	if (!trfn_desc || !trfn_asc) {
+			(!mkfn_desc || mkfn_desc < lly))
+		mkfn_desc = lly;
+	if (!mkfn_desc || !mkfn_asc) {
 		if (c > 0 && c < 128)
 			return ctype_ascii[c];
 		return 3;
 	}
-	if (!trfn_desc || lly <= trfn_desc)
+	if (!mkfn_desc || lly <= mkfn_desc)
 		typ |= 1;
-	if (!trfn_asc || ury >= trfn_asc)
+	if (!mkfn_asc || ury >= mkfn_asc)
 		typ |= 2;
 	return typ;
 }
 
 /* n is the position and u is the unicode codepoint */
-void trfn_char(char *psname, int n, int u, int wid,
+void mkfn_char(char *psname, int n, int u, int wid,
 		int llx, int lly, int urx, int ury)
 {
 	char uc[GNLEN];			/* mapping unicode character */
@@ -276,23 +262,22 @@
 	if (trfn_name(uc, psname, u))
 		strcpy(uc, "---");
 	trfn_aglexceptions(uc);
-	if (trfn_pos && n >= 0 && n < 256)
+	if (mkfn_pos && n >= 0 && n < 256)
 		sprintf(pos, "%d", n);
-	if (trfn_pos && n < 0 && !uc[1] && uc[0] >= 32 && uc[0] <= 125)
+	if (mkfn_pos && n < 0 && !uc[1] && uc[0] >= 32 && uc[0] <= 125)
 		if (!strchr(psname, '.'))
 			sprintf(pos, "%d", uc[0]);
 	typ = trfn_type(!strchr(psname, '.') ? uc : "", lly, ury);
-	if (!trfn_swid && (!strcmp(" ", uc) || !strcmp(" ", uc)))
-		trfn_swid = WX(wid);
+	if (!mkfn_swid && (!strcmp(" ", uc) || !strcmp(" ", uc)))
+		mkfn_swid = wid;
 	/* printing troff charset */
 	if (isspace((unsigned char) uc[0]) || strchr(uc, ' '))
 		strcpy(uc, "---");	/* space not allowed in char names */
 	if (strcmp("---", uc))
 		trfn_lig(uc);
-	sbuf_printf(sbuf_char, "char %s\t%d", uc, WX(wid));
-	if (trfn_bbox && (llx || lly || urx || ury))
-		sbuf_printf(sbuf_char, ",%d,%d,%d,%d",
-			WX(llx), WX(lly), WX(urx), WX(ury));
+	sbuf_printf(sbuf_char, "char %s\t%d", uc, wid);
+	if (mkfn_bbox && (llx || lly || urx || ury))
+		sbuf_printf(sbuf_char, ",%d,%d,%d,%d", llx, lly, urx, ury);
 	sbuf_printf(sbuf_char, "\t%d\t%s\t%s\n", typ, psname, pos);
 	a_tr = tab_get(tab_alts, uc);
 	while (a_tr && *a_tr)
@@ -299,58 +284,31 @@
 		sbuf_printf(sbuf_char, "char %s\t\"\n", *a_tr++);
 }
 
-void trfn_kern(char *c1, char *c2, int x)
+void mkfn_kern(char *c1, char *c2, int x)
 {
-	if (WX(x) && abs(WX(x)) >= trfn_kmin)
-		sbuf_printf(sbuf_kern, "kern %s\t%s\t%d\n", c1, c2, WX(x));
+	if (x && abs(x) >= mkfn_kmin)
+		if (!mkfn_dry)
+			printf("kern %s\t%s\t%d\n", c1, c2, x);
 }
 
-void trfn_trfont(char *name)
+/* print spacewidth and ligature lines */
+void trfn_header(void)
 {
-	if (!trfn_trname[0])
-		strcpy(trfn_trname, name);
+	printf("spacewidth %d\n", mkfn_swid);
+	if (!mkfn_noligs)
+		printf("ligatures %s%s0\n", mkfn_ligs, mkfn_ligs2);
 }
 
-void trfn_psfont(char *name)
+/* print character definitions */
+void trfn_cdefs(void)
 {
-	if (!trfn_psname[0])
-		strcpy(trfn_psname, name);
-}
-
-void trfn_pspath(char *name)
-{
-	if (!trfn_path[0])
-		strcpy(trfn_path, name);
-}
-
-void trfn_print(void)
-{
-	if (trfn_trname[0])
-		printf("name %s\n", trfn_trname);
-	if (trfn_psname[0])
-		printf("fontname %s\n", trfn_psname);
-	if (trfn_path[0])
-		printf("fontpath %s\n", trfn_path);
-	printf("spacewidth %d\n", trfn_swid);
-	if (!trfn_noligs)
-		printf("ligatures %s%s0\n", trfn_ligs, trfn_ligs2);
-	if (trfn_special)
-		printf("special\n");
 	fputs(sbuf_buf(sbuf_char), stdout);
-	fputs(sbuf_buf(sbuf_kern), stdout);
 }
 
-void trfn_init(int res, int spc, int kmin, int bbox, int ligs, int pos)
+void trfn_init(void)
 {
 	int i;
-	trfn_div = 72000 / res;
-	trfn_special = spc;
-	trfn_kmin = kmin;
-	trfn_bbox = bbox;
-	trfn_noligs = !ligs;
-	trfn_pos = pos;
 	sbuf_char = sbuf_make();
-	sbuf_kern = sbuf_make();
 	tab_agl = tab_alloc(LEN(agl));
 	for (i = 0; i < LEN(agl); i++)
 		tab_put(tab_agl, agl[i][0], agl[i][1]);
@@ -362,7 +320,6 @@
 void trfn_done(void)
 {
 	sbuf_free(sbuf_char);
-	sbuf_free(sbuf_kern);
 	tab_free(tab_alts);
 	if (tab_agl)
 		tab_free(tab_agl);
--- a/trfn.h
+++ /dev/null
@@ -1,11 +1,0 @@
-void trfn_init(int res, int special, int kmin, int bbox, int ligs, int pos);
-void trfn_done(void);
-void trfn_trfont(char *name);
-void trfn_psfont(char *fontname);
-void trfn_print(void);
-void trfn_char(char *c, int n, int u, int wid, int llx, int lly, int urx, int ury);
-void trfn_kern(char *c1, char *c2, int x);
-int trfn_script(char *script, int nscripts);
-int trfn_lang(char *lang, int nlangs);
-int trfn_featrank(char *scrp, char *feat);
-void trfn_pspath(char *name);