shithub: neatpost

Download patch

ref: fa29204bd5df54b40e2f0f81cca94e55dcfb801c
parent: 20d081c279e125781a24e2ddc97662d10915ad60
author: Ali Gholami Rudi <ali@rudi.ir>
date: Tue Mar 27 13:57:54 EDT 2018

pdf: a basic pdf post-processor

--- a/Makefile
+++ b/Makefile
@@ -4,12 +4,15 @@
 CC = cc
 CFLAGS = -Wall -O2 "-DTROFFFDIR=\"$(FDIR)\""
 LDFLAGS =
-OBJS = post.o out.o ps.o font.o dev.o clr.o dict.o iset.o
+OBJS = post.o ps.o font.o dev.o clr.o dict.o iset.o
+OBJSPDF = post.o pdf.o font.o dev.o clr.o dict.o iset.o sbuf.o
 
-all: post
+all: post pdf
 %.o: %.c post.h
 	$(CC) -c $(CFLAGS) $<
 post: $(OBJS)
 	$(CC) -o $@ $(OBJS) $(LDFLAGS)
+pdf: $(OBJSPDF)
+	$(CC) -o $@ $(OBJSPDF) $(LDFLAGS)
 clean:
-	rm -f *.o post
+	rm -f *.o post pdf
--- a/dict.c
+++ b/dict.c
@@ -25,7 +25,7 @@
 
 struct dict *dict_make(int notfound, int dupkeys)
 {
-	struct dict *d = xmalloc(sizeof(*d));
+	struct dict *d = malloc(sizeof(*d));
 	memset(d, 0, sizeof(*d));
 	d->n = 1;
 	d->dupkeys = dupkeys;
@@ -54,7 +54,7 @@
 		dict_extend(d, d->n + CNTMIN);
 	if (d->dupkeys) {
 		int len = strlen(key) + 1;
-		char *dup = xmalloc(len);
+		char *dup = malloc(len);
 		memcpy(dup, key, len);
 		key = dup;
 	}
--- a/font.c
+++ b/font.c
@@ -7,6 +7,7 @@
 struct font {
 	char name[FNLEN];
 	char fontname[FNLEN];
+	char fontpath[1024];
 	int spacewid;
 	struct glyph *gl;		/* glyphs present in the font */
 	int gl_n, gl_sz;		/* number of glyphs in the font */
@@ -108,7 +109,7 @@
 	fin = fopen(path, "r");
 	if (!fin)
 		return NULL;
-	fn = xmalloc(sizeof(*fn));
+	fn = malloc(sizeof(*fn));
 	if (!fn) {
 		fclose(fin);
 		return NULL;
@@ -126,6 +127,12 @@
 			fscanf(fin, "%s", fn->name);
 		} else if (!strcmp("fontname", tok)) {
 			fscanf(fin, "%s", fn->fontname);
+		} else if (!strcmp("fontpath", tok)) {
+			int c = fgetc(fin);
+			while (c == ' ')
+				c = fgetc(fin);
+			ungetc(c, fin);
+			tilleol(fin, fn->fontpath);
 		} else if (!strcmp("ligatures", tok)) {
 			while (fscanf(fin, "%s", tok) == 1)
 				if (!strcmp("0", tok))
@@ -165,4 +172,19 @@
 char *font_name(struct font *fn)
 {
 	return fn->fontname;
+}
+
+char *font_path(struct font *fn)
+{
+	return fn->fontpath;
+}
+
+int font_glnum(struct font *fn, struct glyph *g)
+{
+	return g - fn->gl;
+}
+
+struct glyph *font_glget(struct font *fn, int id)
+{
+	return id >= 0 && id < fn->gl_n ? &fn->gl[id] : NULL;
 }
--- a/iset.c
+++ b/iset.c
@@ -24,7 +24,7 @@
 
 struct iset *iset_make(void)
 {
-	struct iset *iset = xmalloc(sizeof(*iset));
+	struct iset *iset = malloc(sizeof(*iset));
 	memset(iset, 0, sizeof(*iset));
 	iset_extend(iset, CNTMIN);
 	return iset;
@@ -60,7 +60,7 @@
 	if (key >= 0 && key < iset->cnt && iset->len[key] + 1 >= iset->sz[key]) {
 		int olen = iset->sz[key];
 		int nlen = iset->sz[key] * 2 + 8;
-		void *nset = xmalloc(nlen * sizeof(iset->set[key][0]));
+		void *nset = malloc(nlen * sizeof(iset->set[key][0]));
 		if (iset->set[key]) {
 			memcpy(nset, iset->set[key],
 				olen * sizeof(iset->set[key][0]));
--- a/out.c
+++ /dev/null
@@ -1,354 +1,0 @@
-#include <ctype.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include "post.h"
-
-static int o_f, o_s, o_m;	/* font and size */
-static int o_h, o_v;		/* current user position */
-static int p_f, p_s, p_m;	/* output postscript font */
-static int o_qtype;		/* queued character type */
-static int o_qv, o_qh, o_qend;	/* queued character position */
-static int o_rh, o_rv, o_rdeg;	/* previous rotation position and degree */
-static int o_gname;		/* use glyphshow for all glyphs */
-
-char o_fonts[FNLEN * NFONTS] = " ";
-
-static void outvf(char *s, va_list ap)
-{
-	vfprintf(stdout, s, ap);
-}
-
-static void outf(char *s, ...)
-{
-	va_list ap;
-	va_start(ap, s);
-	outvf(s, ap);
-	va_end(ap);
-}
-
-static void o_flush(void)
-{
-	if (o_qtype == 1)
-		outf(") %d %d w\n", o_qh, o_qv);
-	if (o_qtype == 2)
-		outf("] %d %d g\n", o_qh, o_qv);
-	o_qtype = 0;
-}
-
-void outgname(int g)
-{
-	o_gname = g;
-}
-
-void outpage(void)
-{
-	o_flush();
-	o_v = 0;
-	o_h = 0;
-	p_s = 0;
-	p_f = 0;
-	p_m = 0;
-	o_rdeg = 0;
-}
-
-static void o_queue(struct glyph *g)
-{
-	int type = 1 + (g->pos <= 0 || o_gname);
-	if (o_qtype != type || o_qend != o_h || o_qv != o_v) {
-		o_flush();
-		o_qh = o_h;
-		o_qv = o_v;
-		o_qtype = type;
-		outf(type == 1 ? "(" : "[");
-	}
-	if (o_qtype == 1) {
-		if (g->pos >= ' ' && g->pos <= '~')
-			outf("%s%c", strchr("()\\", g->pos) ? "\\" : "", g->pos);
-		else
-			outf("\\%d%d%d", (g->pos >> 6) & 7,
-					(g->pos >> 3) & 7, g->pos & 7);
-	} else {
-		outf("/%s", g->id);
-	}
-	o_qend = o_h + font_wid(g->font, o_s, g->wid);
-}
-
-/* calls o_flush() if necessary */
-void out(char *s, ...)
-{
-	va_list ap;
-	o_flush();
-	va_start(ap, s);
-	outvf(s, ap);
-	va_end(ap);
-}
-
-static void out_fontup(int fid)
-{
-	char fnname[FNLEN];
-	struct font *fn;
-	if (o_m != p_m) {
-		out("%d %d %d rgb\n", CLR_R(o_m), CLR_G(o_m), CLR_B(o_m));
-		p_m = o_m;
-	}
-	if (fid != p_f || o_s != p_s) {
-		fn = dev_font(fid);
-		out("%d /%s f\n", o_s, font_name(fn));
-		p_f = fid;
-		p_s = o_s;
-		sprintf(fnname, " %s ", font_name(fn));
-		if (!strstr(o_fonts, fnname))
-			sprintf(strchr(o_fonts, '\0'), "%s ", font_name(fn));
-	}
-}
-
-void outc(char *c)
-{
-	struct glyph *g;
-	struct font *fn;
-	g = dev_glyph(c, o_f);
-	fn = g ? g->font : dev_font(o_f);
-	if (!g) {
-		outrel(*c == ' ' && fn ? font_swid(fn, o_s) : 1, 0);
-		return;
-	}
-	out_fontup(dev_fontid(fn));
-	o_queue(g);
-}
-
-void outh(int h)
-{
-	o_h = h;
-}
-
-void outv(int v)
-{
-	o_v = v;
-}
-
-void outrel(int h, int v)
-{
-	o_h += h;
-	o_v += v;
-}
-
-void outfont(int f)
-{
-	if (dev_font(f))
-		o_f = f;
-}
-
-/* a font was mounted at pos f */
-void outmnt(int f)
-{
-	if (p_f == f)
-		p_f = -1;
-}
-
-void outsize(int s)
-{
-	if (s > 0)
-		o_s = s;
-}
-
-void outcolor(int c)
-{
-	o_m = c;
-}
-
-void outrotate(int deg)
-{
-	o_flush();
-	out_fontup(o_f);
-	if (o_rdeg)
-		outf("%d %d %d rot\n", -o_rdeg, o_rh, o_rv);
-	o_rdeg = deg;
-	o_rh = o_h;
-	o_rv = o_v;
-	outf("%d %d %d rot\n", deg, o_h, o_v);
-}
-
-static int draw_path;	/* number of path segments */
-static int draw_point;	/* point was set for postscript newpath */
-
-static void drawmv(void)
-{
-	if (!draw_point)
-		outf("%d %d m ", o_h, o_v);
-	draw_point = 1;
-}
-
-/* start a multi-segment path */
-void drawmbeg(char *s)
-{
-	o_flush();
-	out_fontup(o_f);
-	draw_path = 1;
-	outf("gsave newpath %s\n", s);
-}
-
-/* end a multi-segment path */
-void drawmend(char *s)
-{
-	draw_path = 0;
-	draw_point = 0;
-	outf("%s grestore\n", s);
-}
-
-void drawbeg(void)
-{
-	o_flush();
-	out_fontup(o_f);
-	if (draw_path)
-		return;
-	outf("newpath ");
-}
-
-void drawend(int close, int fill)
-{
-	if (draw_path)
-		return;
-	draw_point = 0;
-	if (close)
-		outf("closepath ");
-	if (fill)
-		outf("fill\n");
-	else
-		outf("stroke\n");
-}
-
-void drawl(int h, int v)
-{
-	drawmv();
-	outrel(h, v);
-	outf("%d %d drawl ", o_h, o_v);
-}
-
-void drawc(int c)
-{
-	drawmv();
-	outrel(c, 0);
-	outf("%d %d drawe ", c, c);
-}
-
-void drawe(int h, int v)
-{
-	drawmv();
-	outrel(h, 0);
-	outf("%d %d drawe ", h, v);
-}
-
-void drawa(int h1, int v1, int h2, int v2)
-{
-	drawmv();
-	outf("%d %d %d %d drawa ", h1, v1, h2, v2);
-	outrel(h1 + h2, v1 + v2);
-}
-
-void draws(int h1, int v1, int h2, int v2)
-{
-	drawmv();
-	outf("%d %d %d %d %d %d draws ", o_h, o_v, o_h + h1, o_v + v1,
-		o_h + h1 + h2, o_v + v1 + v2);
-	outrel(h1, v1);
-}
-
-static char *strcut(char *dst, char *src)
-{
-	while (*src == ' ' || *src == '\n')
-		src++;
-	if (src[0] == '"') {
-		src++;
-		while (*src && (src[0] != '"' || src[1] == '"')) {
-			if (*src == '"')
-				src++;
-			*dst++ = *src++;
-		}
-		if (*src == '"')
-			src++;
-	} else {
-		while (*src && *src != ' ' && *src != '\n')
-			*dst++ = *src++;
-	}
-	*dst = '\0';
-	return src;
-}
-
-void outeps(char *spec)
-{
-	char eps[1 << 12];
-	char buf[1 << 12];
-	int llx, lly, urx, ury;
-	int hwid, vwid;
-	FILE *filp;
-	int nspec, nbb;
-	spec = strcut(eps, spec);
-	if (!eps[0])
-		return;
-	nspec = sscanf(spec, "%d %d", &hwid, &vwid);
-	if (nspec < 1)
-		hwid = 0;
-	if (nspec < 2)
-		vwid = 0;
-	if (!(filp = fopen(eps, "r")))
-		return;
-	if (!fgets(buf, sizeof(buf), filp) ||
-			(strcmp(buf, "%!PS-Adobe-2.0 EPSF-1.2\n") &&
-			strcmp(buf, "%!PS-Adobe-2.0 EPSF-2.0\n") &&
-			strcmp(buf, "%!PS-Adobe-3.0 EPSF-3.0\n"))) {
-		fclose(filp);
-		return;
-	}
-	nbb = 0;
-	while (fgets(buf, sizeof(buf), filp))
-		if (!strncmp(buf, "%%BoundingBox: ", 15))
-			if ((nbb = sscanf(buf + 15, "%d %d %d %d",
-					&llx, &lly, &urx, &ury)) == 4)
-				break;
-	fclose(filp);
-	if (nbb < 4)		/* no BoundingBox comment */
-		return;
-	if (hwid <= 0 && vwid <= 0)
-		hwid = (urx - llx) * dev_res / 72;
-	if (vwid <= 0)
-		vwid = (ury - lly) * hwid / (urx - llx);
-	if (hwid <= 0)
-		hwid = (urx - llx) * vwid / (ury - lly);
-	/* output the EPS file */
-	o_flush();
-	out_fontup(o_f);
-	outf("%d %d %d %d %d %d %d %d EPSFBEG\n",
-		llx, lly, hwid, urx - llx, vwid, ury - lly, o_h, o_v);
-	outf("%%%%BeginDocument: %s\n", eps);
-	filp = fopen(eps, "r");
-	while (fgets(buf, sizeof(buf), filp))
-		out("%s", buf);
-	fclose(filp);
-	outf("%%%%EndDocument\n");
-	outf("EPSFEND\n");
-}
-
-void outlink(char *spec)
-{
-	char lnk[1 << 12];
-	int hwid, vwid;
-	int nspec;
-	spec = strcut(lnk, spec);
-	if (!lnk[0] || (nspec = sscanf(spec, "%d %d", &hwid, &vwid)) != 2)
-		return;
-	o_flush();
-	if (lnk[0] == '#' || isdigit((unsigned char) lnk[0])) {
-		outf("[ /Rect [ %d %d t %d %d t ] %s%s "
-			"/Subtype /Link /LNK pdfmark\n",
-			o_h, o_v, o_h + hwid, o_v + vwid,
-			lnk[0] == '#' ? "/Dest /" : "/Page ",
-			lnk[0] == '#' ? lnk + 1 : lnk);
-	} else {
-		outf("[ /Rect [ %d %d t %d %d t ] "
-			"/Action << /Subtype /URI /URI (%s) >> /Open true "
-			"/Subtype /Link /LNK pdfmark\n",
-			o_h, o_v, o_h + hwid, o_v + vwid, lnk);
-	}
-}
--- /dev/null
+++ b/pdf.c
@@ -1,0 +1,489 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "post.h"
+
+static char *pdf_title;		/* document title */
+static int pdf_width;		/* page width */
+static int pdf_height;		/* page height */
+static int pdf_pages;		/* pages object id */
+static int pdf_root;		/* root object id */
+static int pdf_pos;		/* current pdf file offset */
+static int *obj_off;		/* object offsets */
+static int obj_sz, obj_n;	/* number of pdf objects */
+static int *page_id;		/* page object ids */
+static int page_sz, page_n;	/* number of pages */
+static char **font_ps;		/* document font names */
+static int *font_id;		/* font object */
+static int *font_ct;		/* font content stream object */
+static int *font_ix;		/* font index */
+static int font_sz, font_n;	/* number of fonts */
+
+static struct sbuf *pg;		/* current page contents */
+static int o_f, o_s, o_m;	/* font and size */
+static int o_h, o_v;		/* current user position */
+static int p_h, p_v;		/* current output position */
+static int o_pf, p_pf;		/* output and pdf fonts; indices into o_fs[] */
+static int p_f, p_s, p_m;	/* output font */
+static int o_queued;		/* queued character type */
+static int *o_fs;		/* page fonts */
+static int o_fsn, o_fssz;	/* page fonts */
+
+/* print pdf output */
+static void pdfout(char *s, ...)
+{
+	va_list ap;
+	va_start(ap, s);
+	pdf_pos += vprintf(s, ap);
+	va_end(ap);
+}
+
+/* allocate an object number */
+static int obj_map(void)
+{
+	if (obj_n == obj_sz) {
+		obj_sz += 1024;
+		obj_off = mextend(obj_off, obj_n, obj_sz, sizeof(obj_off[0]));
+	}
+	return obj_n++;
+}
+
+/* start the definition of an object */
+static int obj_beg(int id)
+{
+	if (id <= 0)
+		id = obj_map();
+	obj_off[id] = pdf_pos;
+	pdfout("%d 0 obj\n", id);
+	return id;
+}
+
+/* end an object definition */
+static void obj_end(void)
+{
+	pdfout("endobj\n\n");
+}
+
+/* embed font; return stream object identifier */
+static int font_outdat(char *path, char *name, int ix)
+{
+	struct sbuf *sb;
+	FILE *fp;
+	int c, i, id;
+	for (i = 0; i < font_n; i++)
+		if (!strcmp(name, font_ps[i]) && font_ct[i] >= 0)
+			return font_ct[i];
+	fp = fopen(path, "r");
+	if (!fp)
+		return -1;
+	sb = sbuf_make();
+	c = fgetc(fp);
+	for (i = 0; c != EOF; i++) {
+		sbuf_printf(sb, "%02x", c);
+		c = fgetc(fp);
+		if (i % 40 == 39 && c != EOF)
+			sbuf_chr(sb, '\n');
+	}
+	sbuf_str(sb, ">\n");
+	fclose(fp);
+	id = obj_beg(0);
+	pdfout("<<\n");
+	pdfout("  /Filter /ASCIIHexDecode\n");
+	pdfout("  /Length %d\n", sbuf_len(sb));
+	pdfout("  /Length1 %d\n", i);
+	pdfout(">>\n");
+	pdfout("stream\n");
+	pdfout("%s", sbuf_buf(sb));
+	pdfout("endstream\n");
+	obj_end();
+	sbuf_free(sb);
+	return id;
+}
+
+/* write the object corresponding to font font_id[f] */
+static void font_out(struct font *fn, int f)
+{
+	int i;
+	int enc_obj, des_obj;
+	char *path = font_path(fn);
+	char *ext = path ? strrchr(path, '.') : NULL;
+	/* the encoding object */
+	enc_obj = obj_beg(0);
+	pdfout("<<\n");
+	pdfout("  /Type /Encoding\n");
+	pdfout("  /Differences [ 0");
+	for (i = 0; i < 256; i++) {
+		struct glyph *g = font_glget(fn, font_ix[f] * 256 + i);
+		pdfout(" /%s", g ? g->id : ".notdef");
+	}
+	pdfout(" ]\n");
+	pdfout(">>\n");
+	obj_end();
+	/* embedding the font */
+	if (ext && !strcmp(".ttf", ext))
+		font_ct[f] = font_outdat(path, font_ps[f], font_ix[f]);
+	/* the font descriptor */
+	des_obj = obj_beg(0);
+	pdfout("<<\n");
+	pdfout("  /Type /FontDescriptor\n");
+	pdfout("  /FontName /%s\n", font_ps[f]);
+	pdfout("  /Flags 0\n");
+	pdfout("  /MissingWidth 255\n");
+	pdfout("  /StemV 100\n");
+	pdfout("  /StemH 100\n");
+	pdfout("  /CapHeight 100\n");
+	pdfout("  /Ascent 100\n");
+	pdfout("  /Descent 100\n");
+	if (font_ct[f] >= 0)
+		pdfout("  /FontFile2 %d 0 R\n", font_ct[f]);
+	pdfout(">>\n");
+	obj_end();
+	/* the font object */
+	obj_beg(font_id[f]);
+	pdfout("<<\n");
+	pdfout("  /Type /Font\n");
+	pdfout("  /Subtype /%s\n",
+		ext && !strcmp(".ttf", ext) ? "TrueType" : "Type1");
+	pdfout("  /BaseFont /%s\n", font_ps[f]);
+	pdfout("  /FirstChar 0\n");
+	pdfout("  /LastChar 255\n");
+	pdfout("  /Widths [");
+	for (i = 0; i < 256; i++) {
+		struct glyph *g = font_glget(fn, font_ix[f] * 256 + i);
+		pdfout(" %d", (g ? g->wid : 0) * dev_res / 72);
+	}
+	pdfout(" ]\n");
+	pdfout("  /FontDescriptor %d 0 R\n", des_obj);
+	pdfout("  /Encoding %d 0 R\n", enc_obj);
+	pdfout(">>\n");
+	obj_end();
+}
+
+static int font_put(struct font *fn, int ix)
+{
+	int i;
+	char *name = font_name(fn);
+	for (i = 0; i < font_n; i++)
+		if (!strcmp(font_ps[i], font_name(fn)) && font_ix[i] == ix)
+			return i;
+	if (font_n == font_sz) {
+		font_sz += 128;
+		font_id = mextend(font_id, font_n, font_sz, sizeof(font_id[0]));
+		font_ps = mextend(font_ps, font_n, font_sz, sizeof(font_ps[0]));
+		font_ix = mextend(font_ix, font_n, font_sz, sizeof(font_ix[0]));
+		font_ct = mextend(font_ct, font_n, font_sz, sizeof(font_ct[0]));
+	}
+	font_id[font_n] = obj_map();
+	font_ix[font_n] = ix;
+	font_ps[font_n] = malloc(strlen(name) + 1);
+	font_ct[font_n] = -1;
+	strcpy(font_ps[font_n], name);
+	font_n++;
+	font_out(fn, font_n - 1);
+	return font_n - 1;
+}
+
+void out(char *s, ...)
+{
+}
+
+static void o_flush(void)
+{
+	if (o_queued == 1)
+		sbuf_printf(pg, ") Tj\n");
+	o_queued = 0;
+}
+
+static int o_loadfont(struct glyph *g)
+{
+	struct font *fn = g ? g->font : dev_font(o_f);
+	int ix = font_glnum(fn, g) / 256;
+	char *name = font_name(fn);
+	int i;
+	int id;
+	for (i = 0; i < o_fsn; i++)
+		if (!strcmp(name, font_ps[o_fs[i]]) && font_ix[o_fs[i]] == ix)
+			return i;
+	id = font_put(fn, ix);
+	if (o_fsn == o_fssz) {
+		o_fssz += 128;
+		o_fs = mextend(o_fs, o_fsn, o_fssz, sizeof(o_fs[0]));
+	}
+	o_fs[o_fsn++] = id;
+	return o_fsn - 1;
+}
+
+#define PREC		1000
+#define PRECN		"3"
+
+static void o_queue(struct glyph *g)
+{
+	int pos;
+	if (o_h != p_h || o_v != p_v) {
+		long h = o_h * PREC * 72 / dev_res;
+		long v = pdf_height * PREC - (o_v * PREC * 72 / dev_res);
+		o_flush();
+		sbuf_printf(pg, "1 0 0 1 %d.%0" PRECN "d %d.%0" PRECN "d Tm\n",
+			h / PREC, h % PREC, v / PREC, v % PREC);
+		p_h = o_h;
+		p_v = o_v;
+	}
+	if (!o_queued)
+		sbuf_printf(pg, "(");
+	o_queued = 1;
+	pos = font_glnum(g->font, g) % 256;
+	sbuf_printf(pg, "\\%d%d%d", (pos >> 6) & 7, (pos >> 3) & 7, pos & 7);
+	p_h += font_wid(g->font, o_s, g->wid);
+}
+
+static void out_fontup(void)
+{
+	if (o_m != p_m) {
+		o_flush();
+		p_m = o_m;
+	}
+	if (o_pf != p_pf || o_s != p_s) {
+		int f = o_fs[o_pf];
+		o_flush();
+		sbuf_printf(pg, "/%s.%d %d Tf\n", font_ps[f], font_ix[f], o_s);
+		p_pf = o_pf;
+		p_s = o_s;
+	}
+}
+
+void outc(char *c)
+{
+	struct glyph *g;
+	struct font *fn;
+	g = dev_glyph(c, o_f);
+	fn = g ? g->font : dev_font(o_f);
+	if (!g) {
+		outrel(*c == ' ' && fn ? font_swid(fn, o_s) : 1, 0);
+		return;
+	}
+	o_pf = o_loadfont(g);
+	out_fontup();
+	o_queue(g);
+}
+
+void outh(int h)
+{
+	o_h = h;
+}
+
+void outv(int v)
+{
+	o_v = v;
+}
+
+void outrel(int h, int v)
+{
+	o_h += h;
+	o_v += v;
+}
+
+void outfont(int f)
+{
+	if (dev_font(f))
+		o_f = f;
+}
+
+void outsize(int s)
+{
+	if (s > 0)
+		o_s = s;
+}
+
+void outcolor(int c)
+{
+	o_m = c;
+}
+
+void outrotate(int deg)
+{
+}
+
+void outeps(char *eps)
+{
+}
+
+void outlink(char *spec)
+{
+}
+
+void outpage(void)
+{
+	o_v = 0;
+	o_h = 0;
+	p_v = 0;
+	p_h = 0;
+	p_s = 0;
+	p_f = 0;
+	p_m = 0;
+}
+
+void outmnt(int f)
+{
+	if (p_f == f)
+		p_f = -1;
+}
+
+void outgname(int g)
+{
+}
+
+void drawbeg(void)
+{
+}
+
+void drawend(int close, int fill)
+{
+}
+
+void drawmbeg(char *s)
+{
+}
+
+void drawmend(char *s)
+{
+}
+
+void drawl(int h, int v)
+{
+}
+
+void drawc(int c)
+{
+}
+
+void drawe(int h, int v)
+{
+}
+
+void drawa(int h1, int v1, int h2, int v2)
+{
+}
+
+void draws(int h1, int v1, int h2, int v2)
+{
+}
+
+void ps_header(char *title, int pagewidth, int pageheight, int linewidth)
+{
+	pdf_title = title;
+	obj_map();
+	pdf_root = obj_map();
+	pdf_pages = obj_map();
+	pdf_title = title;
+	pdfout("%%PDF-1.6\n");
+	pdf_width = (pagewidth * 72 + 127) / 254;
+	pdf_height = (pageheight * 72 + 127) / 254;
+}
+
+void ps_trailer(int pages)
+{
+	int i;
+	int xref_off;
+	int info_id;
+	/* pdf pages object */
+	obj_beg(pdf_pages);
+	pdfout("<<\n");
+	pdfout("  /Type /Pages\n");
+	pdfout("  /MediaBox [ 0 0 %d %d ]\n", pdf_width, pdf_height);
+	pdfout("  /Count %d\n", page_n);
+	pdfout("  /Kids [");
+	for (i = 0; i < page_n; i++)
+		pdfout(" %d 0 R", page_id[i]);
+	pdfout(" ]\n");
+	pdfout(">>\n");
+	obj_end();
+	/* pdf root object */
+	obj_beg(pdf_root);
+	pdfout("<<\n");
+	pdfout("  /Type /Catalog\n");
+	pdfout("  /Pages %d 0 R\n", pdf_pages);
+	pdfout(">>\n");
+	obj_end();
+	/* info object */
+	info_id = obj_beg(0);
+	pdfout("<<\n");
+	if (pdf_title)
+		pdfout("  /Title (%s)\n", pdf_title);
+	pdfout("  /Creator (Neatroff)\n");
+	pdfout("  /Producer (Neatpost)\n");
+	pdfout(">>\n");
+	obj_end();
+	/* the xref */
+	xref_off = pdf_pos;
+	pdfout("xref\n");
+	pdfout("0 %d\n", obj_n);
+	pdfout("0000000000 65535 f \n");
+	for (i = 1; i < obj_n; i++)
+		pdfout("%010d 00000 n \n", obj_off[i]);
+	/* the trailer */
+	pdfout("trailer\n");
+	pdfout("<<\n");
+	pdfout("  /Size %d\n", obj_n);
+	pdfout("  /Root %d 0 R\n", pdf_root);
+	pdfout("  /Info %d 0 R\n", info_id);
+	pdfout(">>\n");
+	pdfout("startxref\n");
+	pdfout("%d\n", xref_off);
+	pdfout("%%%%EOF\n");
+	free(page_id);
+	free(obj_off);
+	for (i = 0; i < font_n; i++)
+		free(font_ps[i]);
+	free(font_ps);
+	free(font_ct);
+	free(font_id);
+	free(font_ix);
+}
+
+void ps_pagebeg(int n)
+{
+	pg = sbuf_make();
+	sbuf_printf(pg, "BT\n");
+}
+
+void ps_pageend(int n)
+{
+	int cont_id;
+	int i;
+	o_flush();
+	sbuf_printf(pg, "ET\n");
+	/* page contents */
+	cont_id = obj_beg(0);
+	pdfout("<<\n");
+	pdfout("  /Length %d\n", sbuf_len(pg));
+	pdfout(">>\n");
+	pdfout("stream\n");
+	pdfout("%s", sbuf_buf(pg));
+	pdfout("endstream\n");
+	obj_end();
+	/* the page object */
+	if (page_n == page_sz) {
+		page_sz += 1024;
+		page_id = mextend(page_id, page_n, page_sz, sizeof(page_id[0]));
+	}
+	page_id[page_n++] = obj_beg(0);
+	pdfout("<<\n");
+	pdfout("  /Type /Page\n");
+	pdfout("  /Parent %d 0 R\n", pdf_pages);
+	pdfout("  /Resources <<\n");
+	pdfout("    /Font <<");
+	for (i = 0; i < o_fsn; i++)
+		pdfout(" /%s.%d %d 0 R",
+			font_ps[o_fs[i]], font_ix[o_fs[i]], font_id[o_fs[i]]);
+	pdfout(" >>\n");
+	pdfout("  >>\n");
+	pdfout("  /Contents %d 0 R\n", cont_id);
+	pdfout(">>\n");
+	obj_end();
+	sbuf_free(pg);
+	free(o_fs);
+	o_fs = NULL;
+	o_fsn = 0;
+	o_fssz = 0;
+}
--- a/post.c
+++ b/post.c
@@ -1,7 +1,7 @@
 /*
- * NEATPOST: NEATROFF'S POSTSCRIPT POSTPROCESSOR
+ * NEATPOST: NEATROFF'S POSTSCRIPT/PDF POSTPROCESSOR
  *
- * Copyright (C) 2013-2017 Ali Gholami Rudi <ali at rudi dot ir>
+ * Copyright (C) 2013-2018 Ali Gholami Rudi <ali at rudi dot ir>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -433,15 +433,9 @@
 	ps_pageheight -= ps_pageheight % 10;
 }
 
-static void errdie(char *msg)
-{
-	fprintf(stderr, msg);
-	exit(1);
-}
-
 void *mextend(void *old, long oldsz, long newsz, int memsz)
 {
-	void *new = xmalloc(newsz * memsz);
+	void *new = malloc(newsz * memsz);
 	memcpy(new, old, oldsz * memsz);
 	memset(new + oldsz * memsz, 0, (newsz - oldsz) * memsz);
 	free(old);
@@ -448,14 +442,6 @@
 	return new;
 }
 
-void *xmalloc(long len)
-{
-	void *m = malloc(len);
-	if (!m)
-		errdie("neatroff: malloc() failed\n");
-	return m;
-}
-
 static char *usage =
 	"Usage: neatpost [options] <input >output\n"
 	"Options:\n"
@@ -485,7 +471,7 @@
 		}
 	}
 	post();
-	ps_trailer(o_pages, o_fonts);
+	ps_trailer(o_pages);
 	dev_close();
 	return 0;
 }
--- a/post.h
+++ b/post.h
@@ -1,6 +1,6 @@
 /* predefined array limits */
 #define PATHLEN		1024	/* path length */
-#define NFONTS		32	/* number of fonts */
+#define NFONTS		1024	/* number of fonts */
 #define FNLEN		64	/* font name length */
 #define GNLEN		32	/* glyph name length */
 #define ILNLEN		1000	/* line limit of input files */
@@ -41,6 +41,9 @@
 int font_wid(struct font *fn, int sz, int w);
 int font_swid(struct font *fn, int sz);
 char *font_name(struct font *fn);
+char *font_path(struct font *fn);
+int font_glnum(struct font *fn, struct glyph *g);
+struct glyph *font_glget(struct font *fn, int id);
 
 /* output functions */
 void out(char *s, ...);
@@ -57,7 +60,6 @@
 void outpage(void);
 void outmnt(int f);
 void outgname(int g);
-extern char o_fonts[];
 
 void drawbeg(void);
 void drawend(int close, int fill);
@@ -71,7 +73,7 @@
 
 /* postscript functions */
 void ps_header(char *title, int pagewidth, int pageheight, int linewidth);
-void ps_trailer(int pages, char *fonts);
+void ps_trailer(int pages);
 void ps_pagebeg(int n);
 void ps_pageend(int n);
 
@@ -102,5 +104,14 @@
 int dict_prefix(struct dict *d, char *key, int *idx);
 
 /* memory allocation */
-void *xmalloc(long len);
 void *mextend(void *old, long oldsz, long newsz, int memsz);
+
+/* string buffers */
+struct sbuf *sbuf_make(void);
+char *sbuf_buf(struct sbuf *sb);
+char *sbuf_done(struct sbuf *sb);
+void sbuf_free(struct sbuf *sb);
+int sbuf_len(struct sbuf *sbuf);
+void sbuf_str(struct sbuf *sbuf, char *s);
+void sbuf_printf(struct sbuf *sbuf, char *s, ...);
+void sbuf_chr(struct sbuf *sbuf, int c);
--- a/ps.c
+++ b/ps.c
@@ -1,5 +1,358 @@
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include "post.h"
 
+static int o_f, o_s, o_m;	/* font and size */
+static int o_h, o_v;		/* current user position */
+static int p_f, p_s, p_m;	/* output postscript font */
+static int o_qtype;		/* queued character type */
+static int o_qv, o_qh, o_qend;	/* queued character position */
+static int o_rh, o_rv, o_rdeg;	/* previous rotation position and degree */
+static int o_gname;		/* use glyphshow for all glyphs */
+
+static char o_fonts[FNLEN * NFONTS] = " ";
+
+static void outvf(char *s, va_list ap)
+{
+	vfprintf(stdout, s, ap);
+}
+
+static void outf(char *s, ...)
+{
+	va_list ap;
+	va_start(ap, s);
+	outvf(s, ap);
+	va_end(ap);
+}
+
+static void o_flush(void)
+{
+	if (o_qtype == 1)
+		outf(") %d %d w\n", o_qh, o_qv);
+	if (o_qtype == 2)
+		outf("] %d %d g\n", o_qh, o_qv);
+	o_qtype = 0;
+}
+
+void outgname(int g)
+{
+	o_gname = g;
+}
+
+void outpage(void)
+{
+	o_flush();
+	o_v = 0;
+	o_h = 0;
+	p_s = 0;
+	p_f = 0;
+	p_m = 0;
+	o_rdeg = 0;
+}
+
+static void o_queue(struct glyph *g)
+{
+	int type = 1 + (g->pos <= 0 || o_gname);
+	if (o_qtype != type || o_qend != o_h || o_qv != o_v) {
+		o_flush();
+		o_qh = o_h;
+		o_qv = o_v;
+		o_qtype = type;
+		outf(type == 1 ? "(" : "[");
+	}
+	if (o_qtype == 1) {
+		if (g->pos >= ' ' && g->pos <= '~')
+			outf("%s%c", strchr("()\\", g->pos) ? "\\" : "", g->pos);
+		else
+			outf("\\%d%d%d", (g->pos >> 6) & 7,
+					(g->pos >> 3) & 7, g->pos & 7);
+	} else {
+		outf("/%s", g->id);
+	}
+	o_qend = o_h + font_wid(g->font, o_s, g->wid);
+}
+
+/* calls o_flush() if necessary */
+void out(char *s, ...)
+{
+	va_list ap;
+	o_flush();
+	va_start(ap, s);
+	outvf(s, ap);
+	va_end(ap);
+}
+
+static void out_fontup(int fid)
+{
+	char fnname[FNLEN];
+	struct font *fn;
+	if (o_m != p_m) {
+		out("%d %d %d rgb\n", CLR_R(o_m), CLR_G(o_m), CLR_B(o_m));
+		p_m = o_m;
+	}
+	if (fid != p_f || o_s != p_s) {
+		fn = dev_font(fid);
+		out("%d /%s f\n", o_s, font_name(fn));
+		p_f = fid;
+		p_s = o_s;
+		sprintf(fnname, " %s ", font_name(fn));
+		if (!strstr(o_fonts, fnname))
+			sprintf(strchr(o_fonts, '\0'), "%s ", font_name(fn));
+	}
+}
+
+void outc(char *c)
+{
+	struct glyph *g;
+	struct font *fn;
+	g = dev_glyph(c, o_f);
+	fn = g ? g->font : dev_font(o_f);
+	if (!g) {
+		outrel(*c == ' ' && fn ? font_swid(fn, o_s) : 1, 0);
+		return;
+	}
+	out_fontup(dev_fontid(fn));
+	o_queue(g);
+}
+
+void outh(int h)
+{
+	o_h = h;
+}
+
+void outv(int v)
+{
+	o_v = v;
+}
+
+void outrel(int h, int v)
+{
+	o_h += h;
+	o_v += v;
+}
+
+void outfont(int f)
+{
+	if (dev_font(f))
+		o_f = f;
+}
+
+/* a font was mounted at pos f */
+void outmnt(int f)
+{
+	if (p_f == f)
+		p_f = -1;
+}
+
+void outsize(int s)
+{
+	if (s > 0)
+		o_s = s;
+}
+
+void outcolor(int c)
+{
+	o_m = c;
+}
+
+void outrotate(int deg)
+{
+	o_flush();
+	out_fontup(o_f);
+	if (o_rdeg)
+		outf("%d %d %d rot\n", -o_rdeg, o_rh, o_rv);
+	o_rdeg = deg;
+	o_rh = o_h;
+	o_rv = o_v;
+	outf("%d %d %d rot\n", deg, o_h, o_v);
+}
+
+static int draw_path;	/* number of path segments */
+static int draw_point;	/* point was set for postscript newpath */
+
+static void drawmv(void)
+{
+	if (!draw_point)
+		outf("%d %d m ", o_h, o_v);
+	draw_point = 1;
+}
+
+/* start a multi-segment path */
+void drawmbeg(char *s)
+{
+	o_flush();
+	out_fontup(o_f);
+	draw_path = 1;
+	outf("gsave newpath %s\n", s);
+}
+
+/* end a multi-segment path */
+void drawmend(char *s)
+{
+	draw_path = 0;
+	draw_point = 0;
+	outf("%s grestore\n", s);
+}
+
+void drawbeg(void)
+{
+	o_flush();
+	out_fontup(o_f);
+	if (draw_path)
+		return;
+	outf("newpath ");
+}
+
+void drawend(int close, int fill)
+{
+	if (draw_path)
+		return;
+	draw_point = 0;
+	if (close)
+		outf("closepath ");
+	if (fill)
+		outf("fill\n");
+	else
+		outf("stroke\n");
+}
+
+void drawl(int h, int v)
+{
+	drawmv();
+	outrel(h, v);
+	outf("%d %d drawl ", o_h, o_v);
+}
+
+void drawc(int c)
+{
+	drawmv();
+	outrel(c, 0);
+	outf("%d %d drawe ", c, c);
+}
+
+void drawe(int h, int v)
+{
+	drawmv();
+	outrel(h, 0);
+	outf("%d %d drawe ", h, v);
+}
+
+void drawa(int h1, int v1, int h2, int v2)
+{
+	drawmv();
+	outf("%d %d %d %d drawa ", h1, v1, h2, v2);
+	outrel(h1 + h2, v1 + v2);
+}
+
+void draws(int h1, int v1, int h2, int v2)
+{
+	drawmv();
+	outf("%d %d %d %d %d %d draws ", o_h, o_v, o_h + h1, o_v + v1,
+		o_h + h1 + h2, o_v + v1 + v2);
+	outrel(h1, v1);
+}
+
+static char *strcut(char *dst, char *src)
+{
+	while (*src == ' ' || *src == '\n')
+		src++;
+	if (src[0] == '"') {
+		src++;
+		while (*src && (src[0] != '"' || src[1] == '"')) {
+			if (*src == '"')
+				src++;
+			*dst++ = *src++;
+		}
+		if (*src == '"')
+			src++;
+	} else {
+		while (*src && *src != ' ' && *src != '\n')
+			*dst++ = *src++;
+	}
+	*dst = '\0';
+	return src;
+}
+
+void outeps(char *spec)
+{
+	char eps[1 << 12];
+	char buf[1 << 12];
+	int llx, lly, urx, ury;
+	int hwid, vwid;
+	FILE *filp;
+	int nspec, nbb;
+	spec = strcut(eps, spec);
+	if (!eps[0])
+		return;
+	nspec = sscanf(spec, "%d %d", &hwid, &vwid);
+	if (nspec < 1)
+		hwid = 0;
+	if (nspec < 2)
+		vwid = 0;
+	if (!(filp = fopen(eps, "r")))
+		return;
+	if (!fgets(buf, sizeof(buf), filp) ||
+			(strcmp(buf, "%!PS-Adobe-2.0 EPSF-1.2\n") &&
+			strcmp(buf, "%!PS-Adobe-2.0 EPSF-2.0\n") &&
+			strcmp(buf, "%!PS-Adobe-3.0 EPSF-3.0\n"))) {
+		fclose(filp);
+		return;
+	}
+	nbb = 0;
+	while (fgets(buf, sizeof(buf), filp))
+		if (!strncmp(buf, "%%BoundingBox: ", 15))
+			if ((nbb = sscanf(buf + 15, "%d %d %d %d",
+					&llx, &lly, &urx, &ury)) == 4)
+				break;
+	fclose(filp);
+	if (nbb < 4)		/* no BoundingBox comment */
+		return;
+	if (hwid <= 0 && vwid <= 0)
+		hwid = (urx - llx) * dev_res / 72;
+	if (vwid <= 0)
+		vwid = (ury - lly) * hwid / (urx - llx);
+	if (hwid <= 0)
+		hwid = (urx - llx) * vwid / (ury - lly);
+	/* output the EPS file */
+	o_flush();
+	out_fontup(o_f);
+	outf("%d %d %d %d %d %d %d %d EPSFBEG\n",
+		llx, lly, hwid, urx - llx, vwid, ury - lly, o_h, o_v);
+	outf("%%%%BeginDocument: %s\n", eps);
+	filp = fopen(eps, "r");
+	while (fgets(buf, sizeof(buf), filp))
+		out("%s", buf);
+	fclose(filp);
+	outf("%%%%EndDocument\n");
+	outf("EPSFEND\n");
+}
+
+void outlink(char *spec)
+{
+	char lnk[1 << 12];
+	int hwid, vwid;
+	int nspec;
+	spec = strcut(lnk, spec);
+	if (!lnk[0] || (nspec = sscanf(spec, "%d %d", &hwid, &vwid)) != 2)
+		return;
+	o_flush();
+	if (lnk[0] == '#' || isdigit((unsigned char) lnk[0])) {
+		outf("[ /Rect [ %d %d t %d %d t ] %s%s "
+			"/Subtype /Link /LNK pdfmark\n",
+			o_h, o_v, o_h + hwid, o_v + vwid,
+			lnk[0] == '#' ? "/Dest /" : "/Page ",
+			lnk[0] == '#' ? lnk + 1 : lnk);
+	} else {
+		outf("[ /Rect [ %d %d t %d %d t ] "
+			"/Action << /Subtype /URI /URI (%s) >> /Open true "
+			"/Subtype /Link /LNK pdfmark\n",
+			o_h, o_v, o_h + hwid, o_v + vwid, lnk);
+	}
+}
+
 void ps_pagebeg(int n)
 {
 	out("%%%%Page: %d %d\n", n, n);
@@ -15,11 +368,11 @@
 	out("saveobj restore\n");
 }
 
-void ps_trailer(int pages, char *fonts)
+void ps_trailer(int pages)
 {
 	out("%%%%Trailer\n");
 	out("done\n");
-	out("%%%%DocumentFonts: %s\n", fonts);
+	out("%%%%DocumentFonts: %s\n", o_fonts);
 	out("%%%%Pages: %d\n", pages);
 	out("%%%%EOF\n");
 }
--- /dev/null
+++ b/sbuf.c
@@ -1,0 +1,95 @@
+/* variable length string buffer */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "post.h"
+
+#define SBUFSZ		128
+#define ALIGN(n, a)	(((n) + (a) - 1) & ~((a) - 1))
+#define NEXTSZ(o, r)	ALIGN(MAX((o) * 2, (o) + (r)), SBUFSZ)
+
+struct sbuf {
+	char *s;		/* allocated buffer */
+	int s_n;		/* length of the string stored in s[] */
+	int s_sz;		/* size of memory allocated for s[] */
+};
+
+static void sbuf_extend(struct sbuf *sbuf, int newsz)
+{
+	char *s = sbuf->s;
+	sbuf->s_sz = newsz;
+	sbuf->s = malloc(sbuf->s_sz);
+	if (sbuf->s_n)
+		memcpy(sbuf->s, s, sbuf->s_n);
+	free(s);
+}
+
+struct sbuf *sbuf_make(void)
+{
+	struct sbuf *sb = malloc(sizeof(*sb));
+	memset(sb, 0, sizeof(*sb));
+	return sb;
+}
+
+char *sbuf_buf(struct sbuf *sb)
+{
+	if (!sb->s)
+		sbuf_extend(sb, 1);
+	sb->s[sb->s_n] = '\0';
+	return sb->s;
+}
+
+char *sbuf_done(struct sbuf *sb)
+{
+	char *s = sbuf_buf(sb);
+	free(sb);
+	return s;
+}
+
+void sbuf_free(struct sbuf *sb)
+{
+	free(sb->s);
+	free(sb);
+}
+
+void sbuf_chr(struct sbuf *sbuf, int c)
+{
+	if (sbuf->s_n + 2 >= sbuf->s_sz)
+		sbuf_extend(sbuf, NEXTSZ(sbuf->s_sz, 1));
+	sbuf->s[sbuf->s_n++] = c;
+}
+
+void sbuf_mem(struct sbuf *sbuf, char *s, int len)
+{
+	if (sbuf->s_n + len + 1 >= sbuf->s_sz)
+		sbuf_extend(sbuf, NEXTSZ(sbuf->s_sz, len + 1));
+	memcpy(sbuf->s + sbuf->s_n, s, len);
+	sbuf->s_n += len;
+}
+
+void sbuf_str(struct sbuf *sbuf, char *s)
+{
+	sbuf_mem(sbuf, s, strlen(s));
+}
+
+int sbuf_len(struct sbuf *sbuf)
+{
+	return sbuf->s_n;
+}
+
+void sbuf_cut(struct sbuf *sb, int len)
+{
+	if (sb->s_n > len)
+		sb->s_n = len;
+}
+
+void sbuf_printf(struct sbuf *sbuf, char *s, ...)
+{
+	char buf[256];
+	va_list ap;
+	va_start(ap, s);
+	vsnprintf(buf, sizeof(buf), s, ap);
+	va_end(ap);
+	sbuf_str(sbuf, buf);
+}