shithub: neatpost

ref: 60fd4c1038c15de25849494c7fd3df6e12b5bb67
dir: /pdf.c/

View raw version
/* PDF post-processor functions */
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <stdio.h>
#include "post.h"

static char pdf_title[256];	/* document title */
static char pdf_author[256];	/* document author */
static int pdf_width;		/* page width */
static int pdf_height;		/* page height */
static int pdf_linewid;		/* line width in thousands of ems */
static int pdf_linecap = 1;	/* line cap style: 0 (butt), 1 (round), 2 (projecting square) */
static int pdf_linejoin = 1;	/* line join style: 0 (miter), 1 (round), 2 (bevel) */
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 int pdf_outline;		/* pdf outline hierarchiy */
static int pdf_dests;		/* named destinations */

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_i, p_i;		/* output and pdf fonts (indices into pfont[]) */
static int p_f, p_s, p_m;	/* output font */
static int o_queued;		/* queued character type */
static char o_iset[1024];	/* fonts accesssed in this page */
static int xobj[128];		/* page xobject object ids */
static int xobj_n;		/* number of xobjects in this page */
static int ann[128];		/* page annotations */
static int ann_n;		/* number of annotations in this page */

/* loaded PDF fonts */
struct pfont {
	char name[128];		/* font PostScript name */
	char path[1024];	/* font path */
	char desc[1024];	/* font descriptor path */
	int gbeg;		/* the first glyph */
	int gend;		/* the last glyph */
	int sub;		/* subfont number */
	int obj;		/* the font object */
	int des;		/* font descriptor */
	int cid;		/* CID-indexed */
};

static struct pfont *pfonts;
static int pfonts_n, pfonts_sz;

/* print formatted pdf output */
static void pdfout(char *s, ...)
{
	va_list ap;
	va_start(ap, s);
	pdf_pos += vprintf(s, ap);
	va_end(ap);
}

/* print pdf output */
static void pdfmem(char *s, int len)
{
	fwrite(s, len, 1, stdout);
	pdf_pos += len;
}

/* 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");
}

void out(char *s, ...)
{
}

/* the length of the clear-text, encrypted, and fixed-content portions */
static int type1lengths(char *t1, int l, int *l1, int *l2, int *l3)
{
	int i;
	char *cleartext = t1;
	char *encrypted = NULL;
	char *fixedcont = NULL;
	for (i = 0; i < l - 5 && !encrypted; i++)
		if (t1[i] == 'e' && !memcmp("eexec", t1 + i, 5))
			encrypted = t1 + i;
	if (!encrypted)
		return 1;
	for (; i < l - 512 && !fixedcont; i++)
		if (t1[i] == '0' && !memcmp("00000", t1 + i, 5))
			fixedcont = t1 + i;
	*l1 = encrypted - cleartext;
	*l2 = fixedcont ? fixedcont - cleartext : 0;
	*l3 = fixedcont ? t1 + l - fixedcont : 0;
	return 0;
}

/* return font type: 't': TrueType, '1': Type 1, 'o': OpenType */
static int fonttype(char *path)
{
	char *ext = strrchr(path, '.');
	if (ext && !strcmp(".ttf", ext))
		return 't';
	if (ext && !strcmp(".otf", ext))
		return 't';
	if (ext && (!strcmp(".ttc", ext) || !strcmp(".otc", ext)))
		return 't';
	return '1';
}

/* write the object corresponding to the given font */
static void pfont_write(struct pfont *ps)
{
	int i;
	int enc_obj;
	struct font *fn = dev_fontopen(ps->desc);
	/* the encoding object */
	enc_obj = obj_beg(0);
	pdfout("<<\n");
	pdfout("  /Type /Encoding\n");
	pdfout("  /Differences [ %d", ps->gbeg % 256);
	for (i = ps->gbeg; i <= ps->gend; i++)
		pdfout(" /%s", font_glget(fn, i)->id);
	pdfout(" ]\n");
	pdfout(">>\n");
	obj_end();
	/* the font object */
	obj_beg(ps->obj);
	pdfout("<<\n");
	pdfout("  /Type /Font\n");
	if (fonttype(ps->path) == 't')
		pdfout("  /Subtype /TrueType\n");
	else
		pdfout("  /Subtype /Type1\n");
	pdfout("  /BaseFont /%s\n", ps->name);
	pdfout("  /FirstChar %d\n", ps->gbeg % 256);
	pdfout("  /LastChar %d\n", ps->gend % 256);
	pdfout("  /Widths [");
	for (i = ps->gbeg; i <= ps->gend; i++)
		pdfout(" %d", (long) font_glget(fn, i)->wid * 100 * 72 / dev_res);
	pdfout(" ]\n");
	pdfout("  /FontDescriptor %d 0 R\n", ps->des);
	pdfout("  /Encoding %d 0 R\n", enc_obj);
	pdfout(">>\n");
	obj_end();
	font_close(fn);
}

static void encodehex(struct sbuf *d, char *s, int n)
{
	static char hex[] = "0123456789ABCDEF";
	int i;
	for (i = 0; i < n; i++) {
		sbuf_chr(d, hex[((unsigned char) s[i]) >> 4]);
		sbuf_chr(d, hex[((unsigned char) s[i]) & 0x0f]);
		if (i % 40 == 39 && i + 1 < n)
			sbuf_chr(d, '\n');
	}
	sbuf_str(d, ">\n");
}

/* write the object corresponding to this CID font */
static void pfont_writecid(struct pfont *ps)
{
	int cid_obj;
	struct font *fn = dev_fontopen(ps->desc);
	int gcnt = 0;
	int i;
	/* CIDFont */
	cid_obj = obj_beg(0);
	pdfout("<<\n");
	pdfout("  /Type /Font\n");
	pdfout("  /Subtype /CIDFontType2\n");
	pdfout("  /BaseFont /%s\n", ps->name);
	pdfout("  /CIDSystemInfo <</Ordering(Identity)/Registry(Adobe)/Supplement 0>>\n");
	pdfout("  /FontDescriptor %d 0 R\n", ps->des);
	pdfout("  /DW 1000\n");
	while (font_glget(fn, gcnt))
		gcnt++;
	pdfout("  /W [ %d [", ps->gbeg);
	for (i = ps->gbeg; i <= ps->gend; i++)
		pdfout(" %d", (long) font_glget(fn, i)->wid * 100 * 72 / dev_res);
	pdfout(" ] ]\n");
	pdfout(">>\n");
	obj_end();
	/* the font object */
	obj_beg(ps->obj);
	pdfout("<<\n");
	pdfout("  /Type /Font\n");
	pdfout("  /Subtype /Type0\n");
	pdfout("  /BaseFont /%s\n", ps->name);
	pdfout("  /Encoding /Identity-H\n");
	pdfout("  /DescendantFonts [%d 0 R]\n", cid_obj);
	pdfout(">>\n");
	obj_end();
	font_close(fn);
}

/* write font descriptor; returns its object ID */
static int writedesc(struct font *fn)
{
	int str_obj = -1;
	int des_obj;
	char buf[1 << 10];
	int fntype = fonttype(font_path(fn));
	if (fntype == '1' || fntype == 't') {
		int fd = open(font_path(fn), OREAD);
		struct sbuf *ffsb = sbuf_make();
		struct sbuf *sb = sbuf_make();
		int l1 = 0, l2 = 0, l3 = 0;
		int nr;
		/* reading the font file */
		while ((nr = read(fd, buf, sizeof(buf))) > 0)
			sbuf_mem(ffsb, buf, nr);
		close(fd);
		l1 = sbuf_len(ffsb);
		/* initialize Type 1 lengths */
		if (fntype == '1') {
			if (type1lengths(sbuf_buf(ffsb), sbuf_len(ffsb),
					&l1, &l2, &l3))
				l1 = 0;
			/* remove the fixed-content portion of the font */
			if (l3)
				sbuf_cut(ffsb, l1 + l2);
			l1 -= l3;
			l3 = 0;
		}
		/* encoding file contents */
		encodehex(sb, sbuf_buf(ffsb), sbuf_len(ffsb));
		/* write font data if it has nonzero length */
		if (l1) {
			str_obj = obj_beg(0);
			pdfout("<<\n");
			pdfout("  /Filter /ASCIIHexDecode\n");
			pdfout("  /Length %d\n", sbuf_len(sb));
			pdfout("  /Length1 %d\n", l1);
			if (fntype == '1')
				pdfout("  /Length2 %d\n", l2);
			if (fntype == '1')
				pdfout("  /Length3 %d\n", l3);
			pdfout(">>\n");
			pdfout("stream\n");
			pdfmem(sbuf_buf(sb), sbuf_len(sb));
			pdfout("endstream\n");
			obj_end();
		}
		sbuf_free(ffsb);
		sbuf_free(sb);
	}
	/* the font descriptor */
	des_obj = obj_beg(0);
	pdfout("<<\n");
	pdfout("  /Type /FontDescriptor\n");
	pdfout("  /FontName /%s\n", font_name(fn));
	pdfout("  /Flags 32\n");
	pdfout("  /FontBBox [-1000 -1000 1000 1000]\n");
	pdfout("  /MissingWidth 1000\n");
	pdfout("  /StemV 100\n");
	pdfout("  /ItalicAngle 0\n");
	pdfout("  /CapHeight 100\n");
	pdfout("  /Ascent 100\n");
	pdfout("  /Descent 100\n");
	if (str_obj >= 0)
		pdfout("  /FontFile%s %d 0 R\n",
			fntype == 't' ? "2" : "", str_obj);
	pdfout(">>\n");
	obj_end();
	return des_obj;
}

static int pfont_find(struct glyph *g)
{
	struct font *fn = g->font;
	char *name = font_name(fn);
	struct pfont *ps = NULL;
	int fntype = fonttype(font_path(fn));
	int sub = fntype == '1' ? font_glnum(fn, g) / 256 : 0;
	int i;
	for (i = 0; i < pfonts_n; i++)
		if (!strcmp(name, pfonts[i].name) && pfonts[i].sub == sub)
			return i;
	if (pfonts_n == pfonts_sz) {
		pfonts_sz += 16;
		pfonts = mextend(pfonts, pfonts_n,
				pfonts_sz, sizeof(pfonts[0]));
	}
	ps = &pfonts[pfonts_n];
	snprintf(ps->name, sizeof(ps->name), "%s", name);
	snprintf(ps->path, sizeof(ps->path), "%s", font_path(fn));
	snprintf(ps->desc, sizeof(ps->desc), "%s", font_desc(fn));
	ps->cid = fntype == 't';
	ps->obj = obj_map();
	ps->sub = sub;
	ps->gbeg = 1 << 20;
	for (i = 0; i < pfonts_n; i++)
		if (!strcmp(pfonts[i].name, ps->name))
			break;
	if (i < pfonts_n)
		ps->des = pfonts[i].des;
	else
		ps->des = writedesc(fn);
	return pfonts_n++;
}

static void pfont_done(void)
{
	int i;
	for (i = 0; i < pfonts_n; i++) {
		if (pfonts[i].cid)
			pfont_writecid(&pfonts[i]);
		else
			pfont_write(&pfonts[i]);
	}
	free(pfonts);
}

static void o_flush(void)
{
	if (o_queued == 1)
		sbuf_printf(pg, ">] TJ\n");
	o_queued = 0;
}

static int o_loadfont(struct glyph *g)
{
	int fn = pfont_find(g);
	o_iset[fn] = 1;
	return fn;
}

/* like pdfpos() but assume that uh and uv are multiplied by 100 */
static char *pdfpos00(int uh, int uv)
{
	static char buf[64];
	int h = (long) uh * 72 / dev_res;
	int v = (long) pdf_height * 100 - (long) uv * 72 / dev_res;
	sprintf(buf, "%s%d.%02d %s%d.%02d",
		h < 0 ? "-" : "", abs(h) / 100, abs(h) % 100,
		v < 0 ? "-" : "", abs(v) / 100, abs(v) % 100);
	return buf;
}

/* convert troff position to pdf position; returns a static buffer */
static char *pdfpos(int uh, int uv)
{
	return pdfpos00(uh * 100, uv * 100);
}

/* troff length to thousands of a unit of text space; returns a static buffer */
static char *pdfunit(int uh, int sz)
{
	static char buf[64];
	int h = (long) uh * 1000 * 72 / sz / dev_res;
	sprintf(buf, "%s%d", h < 0 ? "-" : "", abs(h));
	return buf;
}

/* convert troff color to pdf color; returns a static buffer */
static char *pdfcolor(int m)
{
	static char buf[64];
	int r = CLR_R(m) * 1000 / 255;
	int g = CLR_G(m) * 1000 / 255;
	int b = CLR_B(m) * 1000 / 255;
	sbuf_printf(pg, "%d.%03d %d.%03d %d.%03d",
		r / 1000, r % 1000, g / 1000, g % 1000, b / 1000, b % 1000);
	return buf;
}

static void o_queue(struct glyph *g)
{
	int gid;
	if (o_v != p_v) {
		o_flush();
		sbuf_printf(pg, "1 0 0 1 %s Tm\n", pdfpos(o_h, o_v));
		p_h = o_h;
		p_v = o_v;
	}
	if (!o_queued)
		sbuf_printf(pg, "[<");
	o_queued = 1;
	if (o_h != p_h)
		sbuf_printf(pg, "> %s <", pdfunit(p_h - o_h, o_s));
	/* printing glyph identifier */
	gid = font_glnum(g->font, g);
	if (pfonts[o_i].cid)
		sbuf_printf(pg, "%04x", gid);
	else
		sbuf_printf(pg, "%02x", gid % 256);
	/* updating gbeg and gend */
	if (gid < pfonts[o_i].gbeg)
		pfonts[o_i].gbeg = gid;
	if (gid > pfonts[o_i].gend)
		pfonts[o_i].gend = gid;
	/* advancing */
	p_h = o_h + font_wid(g->font, o_s, g->wid);
}

static void out_fontup(void)
{
	if (o_m != p_m) {
		o_flush();
		sbuf_printf(pg, "%s rg\n", pdfcolor(o_m));
		p_m = o_m;
	}
	if (o_i >= 0 && (o_i != p_i || o_s != p_s)) {
		struct pfont *ps = &pfonts[o_i];
		o_flush();
		if (ps->cid)
			sbuf_printf(pg, "/%s %d Tf\n", ps->name, o_s);
		else
			sbuf_printf(pg, "/%s.%d %d Tf\n", ps->name, ps->sub, o_s);
		p_i = o_i;
		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_i = 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, int hwid, int vwid)
{
}

/* return a copy of a PDF object; returns a static buffer */
static char *pdf_copy(char *pdf, int len, int pos)
{
	static char buf[1 << 12];
	int datlen;
	pos += pdf_ws(pdf, len, pos);
	datlen = pdf_len(pdf, len, pos);
	if (datlen > sizeof(buf) - 1)
		datlen = sizeof(buf) - 1;
	memcpy(buf, pdf + pos, datlen);
	buf[datlen] = '\0';
	return buf;
}

static void pdf_dictcopy(char *pdf, int len, int pos, struct sbuf *sb);

/* write stream to sb */
static int pdf_strcopy(char *pdf, int len, int pos, struct sbuf *sb)
{
	int slen, val;
	int beg;
	if ((val = pdf_dval_val(pdf, len, pos, "/Length")) < 0)
		return -1;
	slen = atoi(pdf + val);
	pos = pos + pdf_len(pdf, len, pos);
	pos += pdf_ws(pdf, len, pos);
	if (pos + slen + 15 > len)
		return -1;
	beg = pos;
	pos += strlen("stream");
	if (pdf[pos] == '\r')
		pos++;
	pos += 1 + slen;
	if (pdf[pos] == '\r' || pdf[pos] == ' ')
		pos++;
	if (pdf[pos] == '\n')
		pos++;
	pos += strlen("endstream") + 1;
	sbuf_mem(sb, pdf + beg, pos - beg);
	return 0;
}

/* copy a PDF object and return its new identifier */
static int pdf_objcopy(char *pdf, int len, int pos)
{
	int id;
	if ((pos = pdf_ref(pdf, len, pos)) < 0)
		return -1;
	if (pdf_type(pdf, len, pos) == 'd') {
		struct sbuf *sb = sbuf_make();
		pdf_dictcopy(pdf, len, pos, sb);
		sbuf_chr(sb, '\n');
		if (pdf_dval(pdf, len, pos, "/Length") >= 0)
			pdf_strcopy(pdf, len, pos, sb);
		id = obj_beg(0);
		pdfmem(sbuf_buf(sb), sbuf_len(sb));
		obj_end();
		sbuf_free(sb);
	} else {
		id = obj_beg(0);
		pdfmem(pdf + pos, pdf_len(pdf, len, pos));
		pdfout("\n");
		obj_end();
	}
	return id;
}

/* copy a PDF dictionary recursively */
static void pdf_dictcopy(char *pdf, int len, int pos, struct sbuf *sb)
{
	int i;
	int key, val, id;
	sbuf_printf(sb, "<<");
	for (i = 0; ; i++) {
		if ((key = pdf_dkey(pdf, len, pos, i)) < 0)
			break;
		sbuf_printf(sb, " %s", pdf_copy(pdf, len, key));
		val = pdf_dval(pdf, len, pos, pdf_copy(pdf, len, key));
		if (pdf_type(pdf, len, val) == 'r') {
			if ((id = pdf_objcopy(pdf, len, val)) >= 0)
				sbuf_printf(sb, " %d 0 R", id);
		} else {
			sbuf_printf(sb, " %s", pdf_copy(pdf, len, val));
		}
	}
	sbuf_printf(sb, " >>");
}

/* copy resources dictionary */
static void pdf_rescopy(char *pdf, int len, int pos, struct sbuf *sb)
{
	char *res_fields[] = {"/ProcSet", "/ExtGState", "/ColorSpace",
		"/Pattern", "/Shading", "/Properties", "/Font", "/XObject"};
	int res, i;
	sbuf_printf(sb, "  /Resources <<\n");
	for (i = 0; i < LEN(res_fields); i++) {
		if ((res = pdf_dval_val(pdf, len, pos, res_fields[i])) >= 0) {
			if (pdf_type(pdf, len, res) == 'd') {
				sbuf_printf(sb, "    %s ", res_fields[i]);
				pdf_dictcopy(pdf, len, res, sb);
				sbuf_printf(sb, "\n");
			} else {
				sbuf_printf(sb, "    %s %s\n", res_fields[i],
					pdf_copy(pdf, len, res));
			}
		}
	}
	sbuf_printf(sb, "  >>\n");
}

static int pdfbbox100(char *pdf, int len, int pos, int dim[4])
{
	int val;
	int i;
	for (i = 0; i < 4; i++) {
		int n = 0, f1 = 0, f2 = 0;
		if ((val = pdf_lval(pdf, len, pos, i)) < 0)
			return -1;
		for (; isdigit((unsigned char) pdf[val]); val++)
			n = n * 10 + pdf[val] - '0';
		if (pdf[val] == '.') {
			if (isdigit((unsigned char) pdf[val + 1])) {
				f1 = pdf[val + 1] - '0';
				if (isdigit((unsigned char) pdf[val + 2]))
					f2 = pdf[val + 2] - '0';
			}
		}
		dim[i] = n * 100 + f1 * 10 + f2;
	}
	return 0;
}

static int pdfext(char *pdf, int len, int hwid, int vwid)
{
	char *cont_fields[] = {"/Filter", "/DecodeParms"};
	int trailer, root, cont, pages, page1, res;
	int kids_val, page1_val, val, bbox;
	int xobj_id, length;
	int dim[4];
	int hzoom = 100, vzoom = 100;
	struct sbuf *sb;
	int i;
	if (xobj_n == LEN(xobj))
		return -1;
	if ((trailer = pdf_trailer(pdf, len)) < 0)
		return -1;
	if ((root = pdf_dval_obj(pdf, len, trailer, "/Root")) < 0)
		return -1;
	if ((pages = pdf_dval_obj(pdf, len, root, "/Pages")) < 0)
		return -1;
	if ((kids_val = pdf_dval_val(pdf, len, pages, "/Kids")) < 0)
		return -1;
	if ((page1_val = pdf_lval(pdf, len, kids_val, 0)) < 0)
		return -1;
	if ((page1 = pdf_ref(pdf, len, page1_val)) < 0)
		return -1;
	if ((cont = pdf_dval_obj(pdf, len, page1, "/Contents")) < 0)
		return -1;
	if ((val = pdf_dval_val(pdf, len, cont, "/Length")) < 0)
		return -1;
	res = pdf_dval_val(pdf, len, page1, "/Resources");
	length = atoi(pdf + val);
	bbox = pdf_dval_val(pdf, len, page1, "/MediaBox");
	if (bbox < 0)
		bbox = pdf_dval_val(pdf, len, pages, "/MediaBox");
	if (bbox >= 0 && !pdfbbox100(pdf, len, bbox, dim)) {
		if (hwid > 0)
			hzoom = (long) hwid * (100 * 7200 / dev_res) / (dim[2] - dim[0]);
		if (vwid > 0)
			vzoom = (long) vwid * (100 * 7200 / dev_res) / (dim[3] - dim[1]);
		if (vwid <= 0)
			vzoom = hzoom;
		if (hwid <= 0)
			hzoom = vzoom;
	}
	sb = sbuf_make();
	sbuf_printf(sb, "<<\n");
	sbuf_printf(sb, "  /Type /XObject\n");
	sbuf_printf(sb, "  /Subtype /Form\n");
	sbuf_printf(sb, "  /FormType 1\n");
	if (bbox >= 0)
		sbuf_printf(sb, "  /BBox %s\n", pdf_copy(pdf, len, bbox));
	sbuf_printf(sb, "  /Matrix [%d.%02d 0 0 %d.%02d %s]\n",
		hzoom / 100, hzoom % 100, vzoom / 100, vzoom % 100,
		pdfpos(o_h, o_v));
	if (res >= 0)
		pdf_rescopy(pdf, len, res, sb);
	sbuf_printf(sb, "  /Length %d\n", length);
	for (i = 0; i < LEN(cont_fields); i++)
		if ((val = pdf_dval_val(pdf, len, cont, cont_fields[i])) >= 0)
			sbuf_printf(sb, "  %s %s\n", cont_fields[i],
				pdf_copy(pdf, len, val));
	sbuf_printf(sb, ">>\n");
	pdf_strcopy(pdf, len, cont, sb);
	xobj_id = obj_beg(0);
	pdfmem(sbuf_buf(sb), sbuf_len(sb));
	obj_end();
	sbuf_free(sb);
	xobj[xobj_n++] = xobj_id;
	return xobj_n - 1;
}

void outpdf(char *pdf, int hwid, int vwid)
{
	char buf[1 << 12];
	struct sbuf *sb;
	int xobj_id;
	int fd, nr;
	/* reading the pdf file */
	sb = sbuf_make();
	fd = open(pdf, OREAD);
	while ((nr = read(fd, buf, sizeof(buf))) > 0)
		sbuf_mem(sb, buf, nr);
	close(fd);
	/* the XObject */
	xobj_id = pdfext(sbuf_buf(sb), sbuf_len(sb), hwid, vwid);
	sbuf_free(sb);
	o_flush();
	out_fontup();
	if (xobj_id >= 0)
		sbuf_printf(pg, "ET /FO%d Do BT\n", xobj_id);
	p_h = -1;
	p_v = -1;
}

void outlink(char *lnk, int hwid, int vwid)
{
	if (ann_n == LEN(ann))
		return;
	o_flush();
	ann[ann_n++] = obj_beg(0);
	pdfout("<<\n");
	pdfout("  /Type /Annot\n");
	pdfout("  /Subtype /Link\n");
	pdfout("  /Rect [%s", pdfpos(o_h, o_v));
	pdfout(" %s]\n", pdfpos(o_h + hwid, o_v + vwid));
	if (lnk[0] == '#') {	/* internal links */
		pdfout("  /A << /S /GoTo /D (%s) >>\n", lnk + 1);
	} else {		/* external links */
		pdfout("  /A << /S /URI /URI %s >>\n", pdftext_static(lnk));
	}
	pdfout(">>\n");
	obj_end();
}

void outname(int n, char (*desc)[64], int *page, int *off)
{
	int i;
	o_flush();
	pdf_dests = obj_beg(0);
	pdfout("<<\n");
	for (i = 0; i < n; i++) {
		if (page[i] > 0 && page[i] - 1 < page_n)
			pdfout("  /%s [ %d 0 R /XYZ 0 %d 0 ]\n",
				desc[i], page_id[page[i] - 1],
				pdf_height - (off[i] * 72 / dev_res));
	}
	pdfout(">>\n");
	obj_end();
}

void outmark(int n, char (*desc)[256], int *page, int *off, int *level)
{
	int *objs = malloc(n * sizeof(objs[0]));
	int i, j;
	int cnt = 0;
	/* allocating objects */
	pdf_outline = obj_map();
	for (i = 0; i < n; i++)
		objs[i] = obj_map();
	o_flush();
	/* root object */
	obj_beg(pdf_outline);
	pdfout("<<\n");
	for (i = 0; i < n; i++)
		if (level[i] == level[0])
			cnt++;
	pdfout("  /Count %d\n", cnt);
	pdfout("  /First %d 0 R\n", objs[0]);
	for (i = n - 1; i > 0 && level[i] > level[0]; i--)
		;
	pdfout("  /Last %d 0 R\n", objs[i]);
	pdfout(">>\n");
	obj_end();
	/* other objects */
	for (i = 0; i < n; i++) {
		int cnt = 0;
		for (j = i + 1; j < n && level[j] > level[i]; j++)
			if (level[j] == level[i] + 1)
				cnt++;
		obj_beg(objs[i]);
		pdfout("<<\n");
		pdfout("  /Title %s\n", pdftext_static(desc[i]));
		/* the parent field */
		for (j = i - 1; j >= 0 && level[j] >= level[i]; j--)
			;
		pdfout("  /Parent %d 0 R\n", j >= 0 ? objs[j] : pdf_outline);
		/* the next field */
		for (j = i + 1; j < n && level[j] > level[i]; j++)
			;
		if (j < n && level[j] == level[i])
			pdfout("  /Next %d 0 R\n", objs[j]);
		/* the prev field */
		for (j = i - 1; j >= 0 && level[j] > level[i]; j--)
			;
		if (j >= 0 && level[j] == level[i])
			pdfout("  /Prev %d 0 R\n", objs[j]);
		/* node children */
		if (cnt) {
			int last = 0;
			pdfout("  /Count %d\n", cnt);
			pdfout("  /First %d 0 R\n", objs[i + 1]);
			for (j = i + 1; j < n && level[j] > level[i]; j++)
				if (level[j] == level[i] + 1)
					last = j;
			pdfout("  /Last %d 0 R\n", objs[last]);
		}
		if (page[i] > 0 && page[i] - 1 < page_n)
			pdfout("  /Dest [ %d 0 R /XYZ 0 %d 0 ]\n",
				page_id[page[i] - 1],
				pdf_height - (off[i] * 72 / dev_res));
		pdfout(">>\n");
		obj_end();
	}
	free(objs);
}

void outinfo(char *kwd, char *val)
{
	if (!strcmp("Author", kwd))
		snprintf(pdf_author, sizeof(pdf_author), "%s", val);
	if (!strcmp("Title", kwd))
		snprintf(pdf_title, sizeof(pdf_title), "%s", val);
}

void outset(char *var, char *val)
{
	if (!strcmp("linewidth", var))
		pdf_linewid = atoi(val);
	if (!strcmp("linecap", var))
		pdf_linecap = atoi(val);
	if (!strcmp("linejoin", var))
		pdf_linejoin = atoi(val);
}

void outpage(void)
{
	o_v = 0;
	o_h = 0;
	p_i = 0;
	p_v = 0;
	p_h = 0;
	p_s = 0;
	p_f = 0;
	p_m = 0;
	o_i = -1;
}

void outmnt(int f)
{
	if (p_f == f)
		p_f = -1;
}

void outgname(int g)
{
}

void drawbeg(void)
{
	o_flush();
	out_fontup();
	sbuf_printf(pg, "%s m\n", pdfpos(o_h, o_v));
}

static int l_page, l_size, l_wid, l_cap, l_join;	/* drawing line properties */

void drawend(int close, int fill)
{
	fill = !fill ? 2 : fill;
	if (l_page != page_n || l_size != o_s || l_wid != pdf_linewid ||
			l_cap != pdf_linecap || l_join != pdf_linejoin) {
		int lwid = pdf_linewid * o_s;
		sbuf_printf(pg, "%d.%03d w\n", lwid / 1000, lwid % 1000);
		sbuf_printf(pg, "%d J %d j\n", pdf_linecap, pdf_linejoin);
		l_page = page_n;
		l_size = o_s;
		l_wid = pdf_linewid;
		l_cap = pdf_linecap;
		l_join = pdf_linejoin;
	}
	if (fill & 2)				/* stroking color */
		sbuf_printf(pg, "%s RG\n", pdfcolor(o_m));
	if (fill & 1)
		sbuf_printf(pg, (fill & 2) ? "b\n" : "f\n");
	else
		sbuf_printf(pg, close ? "s\n" : "S\n");
	p_v = 0;
	p_h = 0;
}

void drawmbeg(char *s)
{
}

void drawmend(char *s)
{
}

void drawl(int h, int v)
{
	outrel(h, v);
	sbuf_printf(pg, "%s l\n", pdfpos(o_h, o_v));
}

/* draw circle/ellipse quadrant */
static void drawquad(int ch, int cv)
{
	long b = 551915;
	long x0 = o_h * 1000;
	long y0 = o_v * 1000;
	long x3 = x0 + ch * 1000 / 2;
	long y3 = y0 + cv * 1000 / 2;
	long x1 = x0;
	long y1 = y0 + cv * b / 1000 / 2;
	long x2 = x0 + ch * b / 1000 / 2;
	long y2 = y3;
	if (ch * cv < 0) {
		x1 = x3 - ch * b / 1000 / 2;
		y1 = y0;
		x2 = x3;
		y2 = y3 - cv * b / 1000 / 2;
	}
	sbuf_printf(pg, "%s ", pdfpos00(x1 / 10, y1 / 10));
	sbuf_printf(pg, "%s ", pdfpos00(x2 / 10, y2 / 10));
	sbuf_printf(pg, "%s c\n", pdfpos00(x3 / 10, y3 / 10));
	outrel(ch / 2, cv / 2);
}

/* draw a circle */
void drawc(int c)
{
	drawquad(+c, +c);
	drawquad(+c, -c);
	drawquad(-c, -c);
	drawquad(-c, +c);
	outrel(c, 0);
}

/* draw an ellipse */
void drawe(int h, int v)
{
	drawquad(+h, +v);
	drawquad(+h, -v);
	drawquad(-h, -v);
	drawquad(-h, +v);
	outrel(h, 0);
}

/* draw an arc */
void drawa(int h1, int v1, int h2, int v2)
{
	drawl(h1 + h2, v1 + v2);
}

/* draw an spline */
void draws(int h1, int v1, int h2, int v2)
{
	outrel(h1, v1);
	sbuf_printf(pg, "%s l\n", pdfpos(o_h, o_v));
}

void docheader(char *title, int pagewidth, int pageheight, int linewidth)
{
	if (title)
		outinfo("Title", title);
	obj_map();
	pdf_root = obj_map();
	pdf_pages = obj_map();
	pdfout("%%PDF-1.6\n\n");
	pdf_width = (pagewidth * 72 + 127) / 254;
	pdf_height = (pageheight * 72 + 127) / 254;
	pdf_linewid = linewidth;
}

void doctrailer(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);
	if (pdf_dests > 0)
		pdfout("  /Dests %d 0 R\n", pdf_dests);
	if (pdf_outline > 0)
		pdfout("  /Outlines %d 0 R\n", pdf_outline);
	pdfout(">>\n");
	obj_end();
	/* fonts */
	pfont_done();
	/* info object */
	info_id = obj_beg(0);
	pdfout("<<\n");
	if (pdf_title[0])
		pdfout("  /Title %s\n", pdftext_static(pdf_title));
	if (pdf_author[0])
		pdfout("  /Author %s\n", pdftext_static(pdf_author));
	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);
}

void docpagebeg(int n)
{
	pg = sbuf_make();
	sbuf_printf(pg, "BT\n");
}

void docpageend(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) - 1);
	pdfout(">>\n");
	pdfout("stream\n");
	pdfmem(sbuf_buf(pg), sbuf_len(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 < pfonts_n; i++) {
		if (o_iset[i]) {
			struct pfont *ps = &pfonts[i];
			if (ps->cid)
				pdfout(" /%s %d 0 R", ps->name, ps->obj);
			else
				pdfout(" /%s.%d %d 0 R", ps->name, ps->sub, ps->obj);
		}
	}
	pdfout(" >>\n");
	if (xobj_n) {				/* XObjects */
		pdfout("    /XObject <<");
		for (i = 0; i < xobj_n; i++)
			pdfout(" /FO%d %d 0 R", i, xobj[i]);
		pdfout(" >>\n");
	}
	pdfout("  >>\n");
	pdfout("  /Contents %d 0 R\n", cont_id);
	if (ann_n) {
		pdfout("  /Annots [");
		for (i = 0; i < ann_n; i++)
			pdfout(" %d 0 R", ann[i]);
		pdfout(" ]\n");
	}
	pdfout(">>\n");
	obj_end();
	sbuf_free(pg);
	memset(o_iset, 0, pfonts_n * sizeof(o_iset[0]));
	xobj_n = 0;
	ann_n = 0;
}