more files
diff --git a/src/cmd/postscript/tr2post/Bgetfield.c b/src/cmd/postscript/tr2post/Bgetfield.c
new file mode 100644
index 0000000..8922139
--- /dev/null
+++ b/src/cmd/postscript/tr2post/Bgetfield.c
@@ -0,0 +1,156 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "../common/common.h"
+#include "tr2post.h"
+
+#undef isspace
+#define isspace bisspace
+
+int
+isspace(Rune r)
+{
+	return(r==' ' || r=='\t' || r=='\n' || r == '\r' || r=='\f');
+}
+
+int
+Bskipws(Biobuf *bp) {
+	int r;
+	char c[UTFmax];
+	int sindex = 0;
+
+	/* skip over initial white space */
+	do {
+		r = Bgetrune(bp);
+		if (r == '\n') inputlineno++;
+		sindex++;	
+	} while (r>=0 && isspace(r));
+	if (r<0) {
+		return(-1);
+	} else if (!isspace(r)) {
+		Bungetrune(bp);
+		--sindex;
+	}
+	return(sindex);
+}
+
+int
+asc2dig(char c, int base) {
+	if (c >= '0' && c <= '9')
+		if (base == 8 && c > '7') return(-1);
+		else return(c - '0');
+
+	if (base == 16)
+		if (c >= 'a' && c <= 'f') return(10 + c - 'a');
+		else if (c >= 'A' && c <= 'F') return(10 + c - 'A');
+
+	return(-1);
+}
+
+/* get a string of type: "d" for decimal integer, "u" for unsigned,
+ * "s" for string", "c" for char, 
+ * return the number of characters gotten for the field.  If nothing
+ * was gotten and the end of file was reached, a negative value
+ * from the Bgetrune is returned.
+ */
+
+int
+Bgetfield(Biobuf *bp, int type, void *thing, int size) {
+	int r;
+	Rune R;
+	char c[UTFmax];
+	int sindex = 0, i, j, n = 0;
+	int negate = 0;
+	int base = 10;
+	BOOLEAN bailout = FALSE;
+	int dig;
+	unsigned int u = 0;
+
+	/* skip over initial white space */
+	if (Bskipws(bp) < 0)
+		return(-1);
+
+	switch (type) {
+	case 'd':
+		while (!bailout && (r = Bgetrune(bp))>=0) {
+			switch (sindex++) {
+			case 0:
+				switch (r) {
+				case '-':
+					negate = 1;
+					continue;
+				case '+':
+					continue;
+				case '0':
+					base = 8;
+					continue;
+				default:	
+					break;
+				}
+				break;
+			case 1:
+				if ((r == 'x' || r == 'X') && base == 8) {
+					base = 16;
+					continue;
+				}
+			}
+			if ((dig = asc2dig(r, base)) == -1) bailout = TRUE;						
+			else n = dig + (n * base);
+		}
+		if (r < 0) return(-1);
+		*(int *)thing = (negate)?-n:n;
+		Bungetrune(bp);
+		break;
+	case 'u':
+		while (!bailout && (r = Bgetrune(bp))>=0) {
+			switch (sindex++) {
+			case 0:
+				if (*c == '0') {
+					base = 8;
+					continue;
+				}
+				break;
+			case 1:
+				if ((r == 'x' || r == 'X') && base == 8) {
+					base = 16;
+					continue;
+				}
+			}
+			if ((dig = asc2dig(r, base)) == -1) bailout = TRUE;						
+			else u = dig + (n * base);
+		}
+		*(int *)thing = u;
+		if (r < 0) return(-1);
+		Bungetrune(bp);
+		break;
+	case 's':
+		j = 0;
+		while ((size>j+UTFmax) && (r = Bgetrune(bp))>=0 && !isspace(r)) {
+			R = r;
+			i = runetochar(&(((char *)thing)[j]), &R);
+			j += i;
+			sindex++;
+		}
+		((char *)thing)[j++] = '\0';
+		if (r < 0) return(-1);
+		Bungetrune(bp);
+		break;
+	case 'r':
+		if ((r = Bgetrune(bp))>=0) {
+			*(Rune *)thing = r;
+			sindex++;
+			return(sindex);
+		}
+		if (r <= 0) return(-1);
+		Bungetrune(bp);
+		break;
+	default:
+		return(-2);
+	}
+	if (r < 0 && sindex == 0)
+		return(r);
+	else if (bailout && sindex == 1) {
+		return(0);
+	} else
+		return(sindex);
+}
diff --git a/src/cmd/postscript/tr2post/chartab.c b/src/cmd/postscript/tr2post/chartab.c
new file mode 100644
index 0000000..87ab556
--- /dev/null
+++ b/src/cmd/postscript/tr2post/chartab.c
@@ -0,0 +1,458 @@
+/*    Unicode   |     PostScript
+ *  start  end  | offset  font name
+ * 0x0000 0x00ff  0x00   LucidaSansUnicode00
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "common.h"
+#include "tr2post.h"
+#include "comments.h"
+#include "path.h"
+
+/* Postscript font names, e.g., `LucidaSansUnicode00'
+ * names may only be added because reference to the
+ * names is made by indexing into this table.
+ */
+static struct pfnament *pfnafontmtab = 0;
+static int pfnamcnt = 0;
+int curpostfontid = -1;
+int curfontsize = -1;
+int curtrofffontid = -1;
+static int curfontpos = -1;
+static int fontheight = 0;
+static int fontslant = 0;
+
+/* This is troffs mounted font table.  It is an anachronism resulting
+ * from the design of the APS typesetter.  fontmnt is the
+ * number of positions available.  fontmnt is really 11, but
+ * should not be limited.
+ */
+int fontmnt = 0;
+char **fontmtab;
+
+struct troffont *troffontab = 0;
+
+int troffontcnt = 0;
+
+void
+mountfont(int pos, char *fontname) {
+	int i;
+
+	if (debug) Bprint(Bstderr, "mountfont(%d, %s)\n", pos, fontname);
+	if (pos < 0 || pos >= fontmnt)
+		error(FATAL, "cannot mount a font at position %d,\n  can only mount into postions 0-%d\n",
+			pos, fontmnt-1);
+
+	i = strlen(fontname);
+	fontmtab[pos] = galloc(fontmtab[pos], i+1, "mountfont():fontmtab");
+	strcpy(fontmtab[pos], fontname);
+	if (curfontpos == pos)	curfontpos = -1;
+}
+
+void
+settrfont(void) {
+	if (curfontpos == fontpos) return;
+
+	if (fontmtab[fontpos] == 0)
+		error(FATAL, "Font at position %d was not initialized, botch!\n", fontpos);
+
+	curtrofffontid = findtfn(fontmtab[fontpos], 1);
+	if (debug) Bprint(Bstderr, "settrfont()-> curtrofffontid=%d\n", curtrofffontid);
+	curfontpos = fontpos;
+	if (curtrofffontid < 0) {
+		int i;
+
+		error(WARNING, "fontpos=%d\n", fontpos);
+		for (i=0; i<fontmnt; i++)
+			if (fontmtab[i] == 0)
+				error(WARNING, "fontmtab[%d]=0x0\n", i);
+			else
+				error(WARNING, "fontmtab[%d]=%s\n", i, fontmtab[i]);
+		exits("settrfont()");
+	}
+}
+
+void
+setpsfont(int psftid, int fontsize) {
+	if (psftid == curpostfontid && fontsize == curfontsize) return;
+	if (psftid >= pfnamcnt)
+		error(FATAL, "Postscript font index=%d used but not defined, there are only %d fonts\n",
+			psftid, pfnamcnt);
+
+	endstring();
+	if (pageon()) {
+		Bprint(Bstdout, "%d /%s f\n", fontsize, pfnafontmtab[psftid].str);
+		if ( fontheight != 0 || fontslant != 0 )
+			Bprint(Bstdout, "%d %d changefont\n", fontslant, (fontheight != 0) ? fontheight : fontsize);
+		pfnafontmtab[psftid].used = 1;
+		curpostfontid = psftid;
+		curfontsize = fontsize;
+	}
+}
+
+/* find index of PostScript font name in table
+ * returns -1 if name is not in table
+ * If insflg is not zero
+ * and the name is not found in the table, insert it.
+ */
+int
+findpfn(char *fontname, int insflg) {
+	char *tp;
+	int i;
+
+	for (i=0; i<pfnamcnt; i++) {
+		if (strcmp(pfnafontmtab[i].str, fontname) == 0)
+			return(i);
+	}
+	if (insflg) {
+		tp = galloc(pfnafontmtab, sizeof(struct pfnament)*(pfnamcnt+1), "findpfn():pfnafontmtab");
+		if (tp == 0)
+			return(-2);
+		pfnafontmtab = (struct pfnament *)tp;
+		i = strlen(fontname);
+		pfnafontmtab[pfnamcnt].str = galloc(0, i+1, "findpfn():pfnafontmtab[].str");
+		strncpy(pfnafontmtab[pfnamcnt].str, fontname, i);
+		pfnafontmtab[pfnamcnt].str[i] = '\0';
+		pfnafontmtab[pfnamcnt].used = 0;
+		return(pfnamcnt++);
+	}
+	return(-1);
+}
+
+char postroffdirname[] = "#9/sys/lib/postscript/troff";		/* "/sys/lib/postscript/troff/"; */
+char troffmetricdirname[] = "#9/sys/lib/troff/font";	/* "/sys/lib/troff/font/devutf/"; */
+
+int
+readpsfontdesc(char *fontname, int trindex) {
+	static char *filename = 0;
+	Biobuf *bfd;
+	Biobuf *Bfd;
+	int warn = 0, errorflg = 0, line =1, rv;
+	int start, end, offset;
+	int startfont, endfont, startchar, endchar, i, pfid;
+	char psfontnam[128];
+	struct troffont *tp;
+
+	if (debug) Bprint(Bstderr, "readpsfontdesc(%s,%d)\n", fontname, trindex);
+	filename=galloc(filename, strlen(postroffdirname)+1+strlen(fontname)+1, "readpsfontdesc: cannot allocate memory\n");
+	sprint(filename, "%s/%s", postroffdirname, fontname);
+
+	bfd = Bopen(unsharp(filename), OREAD);
+	if (bfd == 0) {
+		error(WARNING, "cannot open file %s\n", filename);
+		return(0);
+	}
+	Bfd = bfd;
+
+	do {
+		offset = 0;
+		if ((rv=Bgetfield(Bfd, 'd', &start, 0)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal start value\n", filename, line);
+		} else if (rv < 0) break;
+		if ((rv=Bgetfield(Bfd, 'd', &end, 0)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal end value\n", filename, line);
+		} else if (rv < 0) break;
+		if ((rv=Bgetfield(Bfd, 'd', &offset, 0)) < 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal offset value\n", filename, line);
+		}
+		if ((rv=Bgetfield(Bfd, 's', psfontnam, 128)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal fontname value\n", filename, line);
+		} else if (rv < 0) break;
+		Brdline(Bfd, '\n');
+		if (!errorflg) {
+			struct psfent *psfentp;
+			startfont = RUNEGETGROUP(start);
+			startchar = RUNEGETCHAR(start);
+			endfont = RUNEGETGROUP(end);
+			endchar = RUNEGETCHAR(end);
+			pfid = findpfn(psfontnam, 1);
+			if (startfont != endfont) {
+				error(WARNING, "font descriptions must not cross 256 glyph block boundary\n");
+				errorflg = 1;
+				break;
+			}
+			tp = &(troffontab[trindex]);
+			tp->psfmap = galloc(tp->psfmap, ++(tp->psfmapsize)*sizeof(struct psfent), "readpsfontdesc():psfmap");
+			psfentp = &(tp->psfmap[tp->psfmapsize-1]);
+			psfentp->start = start;
+			psfentp->end = end;
+			psfentp->offset = offset;
+			psfentp->psftid = pfid;
+			if (debug) {
+				Bprint(Bstderr, "\tpsfmap->start=0x%x\n", start);
+				Bprint(Bstderr, "\tpsfmap->end=0x%x\n", end);
+				Bprint(Bstderr, "\tpsfmap->offset=0x%x\n", offset);
+				Bprint(Bstderr, "\tpsfmap->pfid=0x%x\n", pfid);
+			}
+/*
+			for (i=startchar; i<=endchar; i++) {
+				tp->charent[startfont][i].postfontid = pfid;
+				tp->charent[startfont][i].postcharid = i + offset - startchar;
+			}
+ */
+			if (debug) {
+				Bprint(Bstderr, "%x %x ", start, end);
+				if (offset) Bprint(Bstderr, "%x ", offset);
+				Bprint(Bstderr, "%s\n", psfontnam);
+			}
+			line++;
+		}
+	} while(errorflg != 1);
+	Bterm(Bfd);
+	return(1);
+}
+
+int
+readtroffmetric(char *fontname, int trindex) {
+	static char *filename = 0;
+	Biobuf *bfd;
+	Biobuf *Bfd;
+	int warn = 0, errorflg = 0, line =1, rv;
+	struct troffont *tp;
+	struct charent **cp;
+	char stoken[128], *str;
+	int ntoken;
+	Rune troffchar, quote;
+	int width, flag, charnum, thisfont, thischar;
+	BOOLEAN specharflag;
+
+	if (debug) Bprint(Bstderr, "readtroffmetric(%s,%d)\n", fontname, trindex);
+	filename=galloc(filename, strlen(troffmetricdirname)+4+strlen(devname)+1+strlen(fontname)+1, "readtroffmetric():filename");
+	sprint(filename, "%s/dev%s/%s", troffmetricdirname, devname, fontname);
+
+	bfd = Bopen(unsharp(filename), OREAD);
+	if (bfd == 0) {
+		error(WARNING, "cannot open file %s\n", filename);
+		return(0);
+	}
+	Bfd = bfd;
+	do {
+		/* deal with the few lines at the beginning of the
+		 * troff font metric files.
+		 */
+		if ((rv=Bgetfield(Bfd, 's', stoken, 128)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal token\n", filename, line);
+		} else if (rv < 0) break;
+		if (debug) {
+			Bprint(Bstderr, "%s\n", stoken);
+		}
+
+		if (strcmp(stoken, "name") == 0) {
+			if ((rv=Bgetfield(Bfd, 's', stoken, 128)) == 0) {
+				errorflg = 1;
+				error(WARNING, "file %s:%d illegal token\n", filename, line);
+			} else if (rv < 0) break;
+		} else if (strcmp(stoken, "named") == 0) {
+			Brdline(Bfd, '\n');
+		} else if (strcmp(stoken, "fontname") == 0) {
+			if ((rv=Bgetfield(Bfd, 's', stoken, 128)) == 0) {
+				errorflg = 1;
+				error(WARNING, "file %s:%d illegal token\n", filename, line);
+			} else if (rv < 0) break;
+		} else if (strcmp(stoken, "spacewidth") == 0) {
+			if ((rv=Bgetfield(Bfd, 'd', &ntoken, 0)) == 0) {
+				errorflg = 1;
+				error(WARNING, "file %s:%d illegal token\n", filename, line);
+			} else if (rv < 0) break;
+			troffontab[trindex].spacewidth = ntoken;
+			thisfont = RUNEGETGROUP(' ');
+			thischar = RUNEGETCHAR(' ');
+			for (cp = &(troffontab[trindex].charent[thisfont][thischar]); *cp != 0; cp = &((*cp)->next))
+				if ((*cp)->name)
+					if  (strcmp((*cp)->name, " ") == 0)
+						break;
+
+			if (*cp == 0) *cp = galloc(0, sizeof(struct charent), "readtroffmetric:charent");
+			(*cp)->postfontid = thisfont;
+			(*cp)->postcharid = thischar; 
+			(*cp)->troffcharwidth = ntoken;
+			(*cp)->name = galloc(0, 2, "readtroffmetric: char name");
+			(*cp)->next = 0;
+			strcpy((*cp)->name, " ");
+		} else if (strcmp(stoken, "special") == 0) {
+			troffontab[trindex].special = TRUE;
+		} else if (strcmp(stoken, "charset") == 0) {
+			line++;
+			break;
+		}
+		if (!errorflg) {		
+			line++;
+		}
+	} while(!errorflg && rv>=0);
+	while(!errorflg && rv>=0) {
+		if ((rv=Bgetfield(Bfd, 's', stoken, 128)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal rune token <0x%x> rv=%d\n", filename, line, troffchar, rv);
+		} else if (rv < 0) break;
+		if (utflen(stoken) > 1) specharflag = TRUE;
+		else specharflag = FALSE;
+		/* if this character is a quote we have to use the previous characters info */
+		if ((rv=Bgetfield(Bfd, 'r', &quote, 0)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal width or quote token <0x%x> rv=%d\n", filename, line, quote, rv);
+		} else if (rv < 0) break;
+		if (quote == '"') {
+			/* need some code here */
+
+			goto flush;
+		} else {
+			Bungetrune(Bfd);
+		}
+
+		if ((rv=Bgetfield(Bfd, 'd', &width, 0)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal width token <0x%x> rv=%d\n", filename, line, troffchar, rv);
+		} else if (rv < 0) break;
+		if ((rv=Bgetfield(Bfd, 'd', &flag, 0)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal flag token <0x%x> rv=%d\n", filename, line, troffchar, rv);
+		} else if (rv < 0) break;
+		if ((rv=Bgetfield(Bfd, 'd', &charnum, 0)) == 0) {
+			errorflg = 1;
+			error(WARNING, "file %s:%d illegal character number token <0x%x> rv=%d\n", filename, line, troffchar, rv);
+		} else if (rv < 0) break;
+flush:
+		str = Brdline(Bfd, '\n');
+		/* stash the crap from the end of the line for debugging */
+		if (debug) {
+			if (str == 0) {
+				Bprint(Bstderr, "premature EOF\n");
+				return(0);
+			}
+			str[Blinelen(Bfd)-1] = '\0';
+		}
+		line++;
+		chartorune(&troffchar, stoken);
+		if (specharflag) {
+			if (debug)
+				Bprint(Bstderr, "%s %d  %d 0x%x %s # special\n",stoken, width, flag, charnum, str);
+		}
+		if (strcmp(stoken, "---") == 0) {
+			thisfont = RUNEGETGROUP(charnum);
+			thischar = RUNEGETCHAR(charnum);
+			stoken[0] = '\0';
+		} else {
+			thisfont = RUNEGETGROUP(troffchar);
+			thischar = RUNEGETCHAR(troffchar);
+		}
+		for (cp = &(troffontab[trindex].charent[thisfont][thischar]); *cp != 0; cp = &((*cp)->next))
+			if ((*cp)->name) {
+				if (debug) Bprint(Bstderr, "installing <%s>, found <%s>\n", stoken, (*cp)->name);
+				if  (strcmp((*cp)->name, stoken) == 0)
+					break;
+			}
+		if (*cp == 0) *cp = galloc(0, sizeof(struct charent), "readtroffmetric:charent");
+		(*cp)->postfontid = RUNEGETGROUP(charnum);
+		(*cp)->postcharid = RUNEGETCHAR(charnum); 
+		(*cp)->troffcharwidth = width;
+		(*cp)->name = galloc(0, strlen(stoken)+1, "readtroffmetric: char name");
+		(*cp)->next = 0;
+		strcpy((*cp)->name, stoken);
+		if (debug) {
+			if (specharflag)
+				Bprint(Bstderr, "%s", stoken);
+			else
+				Bputrune(Bstderr, troffchar);
+			Bprint(Bstderr, " %d  %d 0x%x %s # psfontid=0x%x pscharid=0x%x thisfont=0x%x thischar=0x%x\n",
+				width, flag, charnum, str,
+				(*cp)->postfontid,
+				(*cp)->postcharid,
+				thisfont, thischar);
+		}
+	}
+	Bterm(Bfd);
+	Bflush(Bstderr);
+	return(1);
+}
+
+/* find index of troff font name in table
+ * returns -1 if name is not in table
+ * returns -2 if it cannot allocate memory
+ * returns -3 if there is a font mapping problem
+ * If insflg is not zero
+ * and the name is not found in the table, insert it.
+ */
+int
+findtfn(char *fontname, BOOLEAN insflg) {
+	struct troffont *tp;
+	int i, j;
+
+	if (debug) {
+		if (fontname==0) fprint(2, "findtfn(0x%x,%d)\n", fontname, insflg);
+		else fprint(2, "findtfn(%s,%d)\n", fontname, insflg);
+	}
+	for (i=0; i<troffontcnt; i++) {
+		if (troffontab[i].trfontid==0) {
+			error(WARNING, "findtfn:troffontab[%d].trfontid=0x%x, botch!\n",
+				i, troffontab[i].trfontid);
+			continue;
+		}
+		if (strcmp(troffontab[i].trfontid, fontname) == 0)
+			return(i);
+	}
+	if (insflg) {
+		tp = (struct troffont *)galloc(troffontab, sizeof(struct troffont)*(troffontcnt+1), "findtfn: struct troffont:");
+		if (tp == 0)
+			return(-2);
+		troffontab = tp;
+		tp = &(troffontab[troffontcnt]);
+		i = strlen(fontname);
+		tp->trfontid = galloc(0, i+1, "findtfn: trfontid:");
+
+		/* initialize new troff font entry with name and numeric fields to 0 */
+		strncpy(tp->trfontid, fontname, i);
+		tp->trfontid[i] = '\0';
+		tp->special = FALSE;
+		tp->spacewidth = 0;
+		tp->psfmapsize = 0;
+		tp->psfmap = 0;
+		for (i=0; i<NUMOFONTS; i++)
+			for (j=0; j<FONTSIZE; j++)
+				tp->charent[i][j] = 0;
+		troffontcnt++;
+		if (!readtroffmetric(fontname, troffontcnt-1))
+			return(-3);
+		if (!readpsfontdesc(fontname, troffontcnt-1))
+			return(-3);
+		return(troffontcnt-1);
+	}
+	return(-1);
+}
+
+void
+finish(void) {
+	int i;
+
+	Bprint(Bstdout, "%s", TRAILER);
+	Bprint(Bstdout, "done\n");
+	Bprint(Bstdout, "%s", DOCUMENTFONTS);
+
+	for (i=0; i<pfnamcnt; i++)
+		if (pfnafontmtab[i].used)
+			Bprint(Bstdout, " %s", pfnafontmtab[i].str);
+	Bprint(Bstdout, "\n");
+
+	Bprint(Bstdout, "%s %d\n", PAGES, pages_printed);
+
+}
+
+/* Set slant to n degrees. Disable slanting if n is 0. */
+void
+t_slant(int n) {
+	fontslant = n;
+	curpostfontid = -1;
+}
+
+/* Set character height to n points. Disabled if n is 0 or the current size. */
+
+void
+t_charht(int n) {
+	fontheight = (n == fontsize) ? 0 : n;
+	curpostfontid = -1;
+}
diff --git a/src/cmd/postscript/tr2post/conv.c b/src/cmd/postscript/tr2post/conv.c
new file mode 100644
index 0000000..4b7d97f
--- /dev/null
+++ b/src/cmd/postscript/tr2post/conv.c
@@ -0,0 +1,100 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "../common/common.h"
+#include "tr2post.h"
+
+void
+conv(Biobuf *Bp) {
+	long c, n;
+	int r;
+	char special[10];
+	int save;
+
+	inputlineno = 1;
+	if (debug) Bprint(Bstderr, "conv(Biobuf *Bp=0x%x)\n", Bp);
+	while ((r = Bgetrune(Bp)) >= 0) {
+/* Bprint(Bstderr, "r=<%c>,0x%x\n", r, r); */
+/*		Bflush(Bstderr); */
+		switch (r) {
+		case 's':	/* set point size */
+			Bgetfield(Bp, 'd', &fontsize, 0);
+			break;
+		case 'f':	/* set font to postion */
+			Bgetfield(Bp, 'd', &fontpos, 0);
+			save = inputlineno;
+			settrfont();
+			inputlineno = save;	/* ugh */
+			break;
+		case 'c':	/* print rune */
+			r = Bgetrune(Bp);
+			runeout(r);
+			break;
+		case 'C':	/* print special character */
+			Bgetfield(Bp, 's', special, 10);
+			specialout(special);
+			break;
+		case 'N':	/* print character with numeric value from current font */
+			Bgetfield(Bp, 'd', &n, 0);
+			break;
+		case 'H':	/* go to absolute horizontal position */
+			Bgetfield(Bp, 'd', &n, 0);
+			hgoto(n);
+			break;
+		case 'V':	/* go to absolute vertical position */
+			Bgetfield(Bp, 'd', &n, 0);
+			vgoto(n);
+			break;
+		case 'h':	/* go to relative horizontal position */
+			Bgetfield(Bp, 'd', &n, 0);
+			hmot(n);
+			break;
+		case 'v':	/* go to relative vertical position */
+			Bgetfield(Bp, 'd', &n, 0);
+			vmot(n);
+			break;
+		case '0': case '1': case '2': case '3': case '4':
+		case '5': case '6': case '7': case '8': case '9':
+				/* move right nn units, then print character c */
+			n = (r - '0') * 10;
+			r = Bgetrune(Bp);
+			if (r < 0)
+				error(FATAL, "EOF or error reading input\n");
+			else if (r < '0' || r > '9')
+				error(FATAL, "integer expected\n");
+			n += r - '0';
+			r = Bgetrune(Bp);
+			hmot(n);
+			runeout(r);
+			break;
+		case 'p':	/* begin page */
+			Bgetfield(Bp, 'd', &n, 0);
+			endpage();
+			startpage();
+			break;
+		case 'n':	/* end of line (information only 'b a' follows) */
+			Brdline(Bp, '\n');	/* toss rest of line */
+			inputlineno++;
+			break;
+		case 'w':	/* paddable word space (information only) */
+			break;
+		case 'D':	/* graphics function */
+			draw(Bp);
+			break;
+		case 'x':	/* device control functions */
+			devcntl(Bp);
+			break;
+		case '#':	/* comment */
+			Brdline(Bp, '\n');	/* toss rest of line */
+		case '\n':
+			inputlineno++;
+			break;
+		default:
+			error(WARNING, "unknown troff function <%c>\n", r);
+			break;
+		}
+	}
+	endpage();
+	if (debug) Bprint(Bstderr, "r=0x%x\n", r);
+	if (debug) Bprint(Bstderr, "leaving conv\n");
+}
diff --git a/src/cmd/postscript/tr2post/devcntl.c b/src/cmd/postscript/tr2post/devcntl.c
new file mode 100644
index 0000000..8b438f7
--- /dev/null
+++ b/src/cmd/postscript/tr2post/devcntl.c
@@ -0,0 +1,178 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <stdio.h>
+#include "../common/common.h"
+#include "tr2post.h"
+
+char devname[20] = { 'u', 't', 'f', '\0' };
+int resolution;
+int minx, miny;
+
+struct sjt {
+	char *str;
+	void (*func)(void *);
+};
+
+/* I won't need this if getfields can replace sscanf
+
+extern void picture(Biobuf *);
+extern void notavail(char *);
+
+void
+PSInclude(Biobuf *inp) {
+	char buf[256];
+
+	Bgetfield(inp, 's', buf, 256);
+	if(pageon()) {
+		endstring();
+		Bprint(Bstdout, "%s\n", buf);
+	}
+}
+
+struct sjt specialjumptable[] = {
+	{"PI", picture},
+	{"PictureInclusion", picture},
+	{"InlinePicture", NULL},
+	{"BeginPath", NULL},
+	{"DrawPath", NULL},
+	{"BeginObject", NULL},
+	{"EndObject", NULL},
+	{"NewBaseline", NULL},
+	{"DrawText", NULL},
+	{"SetText", NULL},
+	{"SetColor", NULL},
+	{"INFO", NULL},
+	{"PS", PSInclude},
+	{"Postscript", PSInclude},
+	{"ExportPS", notavail("ExportPS")},
+	{NULL, NULL}
+};
+*/
+
+void
+devcntl(Biobuf *inp) {
+
+	char cmd[50], buf[256], str[MAXTOKENSIZE], *line;
+	int c, n, linelen;
+
+/*
+ *
+ * Interpret device control commands, ignoring any we don't recognize. The
+ * "x X ..." commands are a device dependent collection generated by troff's
+ * \X'...' request.
+ *
+ */
+
+	Bgetfield(inp, 's', cmd, 50);
+	if (debug) Bprint(Bstderr, "devcntl(cmd=%s)\n", cmd);
+	switch (cmd[0]) {
+	case 'f':		/* mount font in a position */
+		Bgetfield(inp, 'd', &n, 0);
+		Bgetfield(inp, 's', str, 100);
+		mountfont(n, str);
+		break;
+
+	case 'i':			/* initialize */
+		initialize();
+		break;
+
+	case 'p':			/* pause */
+		break;
+
+	case 'r':			/* resolution assumed when prepared */
+		Bgetfield(inp, 'd', &resolution, 0);
+		Bgetfield(inp, 'd', &minx, 0);
+		Bgetfield(inp, 'd', &miny, 0);
+		break;
+
+	case 's':			/* stop */
+	case 't':			/* trailer */
+		/* flushtext(); */
+		break;
+
+	case 'H':			/* char height */
+		Bgetfield(inp, 'd', &n, 0);
+		t_charht(n);
+		break;
+
+	case 'S':			/* slant */
+		Bgetfield(inp, 'd', &n, 0);
+		t_slant(n);
+		break;
+
+	case 'T':			/* device name */
+		Bgetfield(inp, 's', &devname, 16);
+		if (debug) Bprint(Bstderr, "devname=%s\n", devname);
+		break;
+
+	case 'E':			/* input encoding - not in troff yet */
+		Bgetfield(inp, 's', &str, 100);
+/*		if ( strcmp(str, "UTF") == 0 )
+		    reading = UTFENCODING;
+		else reading = ONEBYTE;
+  */
+		break;
+
+	case 'X':			/* copy through - from troff */
+		if (Bgetfield(inp, 's', str, MAXTOKENSIZE-1) <= 0)
+			error(FATAL, "incomplete devcntl line\n");
+		if ((line = Brdline(inp, '\n')) == 0)
+			error(FATAL, "incomplete devcntl line\n");
+		strncpy(buf, line, Blinelen(inp)-1);
+		buf[Blinelen(inp)-1] = '\0';
+		Bungetc(inp);
+
+		if (strncmp(str, "PI", sizeof("PI")-1) == 0 || strncmp(str, "PictureInclusion", sizeof("PictureInclusion")-1) == 0) {
+			picture(inp, str);
+		} else if (strncmp(str, "InlinePicture", sizeof("InlinePicture")-1) == 0) {
+			error(FATAL, "InlinePicture not implemented yet.\n");
+/*			inlinepic(inp, buf);			*/
+		} else if (strncmp(str, "BeginPath", sizeof("BeginPath")-1) == 0) {
+			beginpath(buf, FALSE);
+		} else if (strncmp(str, "DrawPath", sizeof("DrawPath")-1) == 0) {
+			drawpath(buf, FALSE);
+		} else if (strncmp(str, "BeginObject", sizeof("BeginObject")-1) == 0) {
+			beginpath(buf, TRUE);
+		} else if (strncmp(str, "EndObject", sizeof("EndObject")-1) == 0) {
+			drawpath(buf, TRUE);
+		} else if (strncmp(str, "NewBaseline", sizeof("NewBaseline")-1) == 0) {
+			error(FATAL, "NewBaseline not implemented yet.\n");
+/*			newbaseline(buf);			*/
+		} else if (strncmp(str, "DrawText", sizeof("DrawText")-1) == 0) {
+			error(FATAL, "DrawText not implemented yet.\n");
+/*			drawtext(buf);				*/
+		} else if (strncmp(str, "SetText", sizeof("SetText")-1) == 0) {
+			error(FATAL, "SetText not implemented yet.\n");
+/*			settext(buf);				*/
+		} else if (strncmp(str, "SetColor", sizeof("SetColor")-1) == 0) {
+			error(FATAL, "SetColor not implemented yet.\n");
+/*			newcolor(buf);				*/
+/*			setcolor();					*/
+		} else if (strncmp(str, "INFO", sizeof("INFO")-1) == 0) {
+			error(FATAL, "INFO not implemented yet.\n");
+/*			flushtext();				*/
+/*			Bprint(outp, "%%INFO%s", buf);	*/
+		} else if (strncmp(str, "PS", sizeof("PS")-1) == 0 || strncmp(str, "PostScript", sizeof("PostScript")-1) == 0) {
+			if(pageon()) {
+				endstring();
+				Bprint(Bstdout, "%s\n", buf);
+			}
+		} else if (strncmp(str, "ExportPS", sizeof("ExportPS")-1) == 0) {	/* dangerous!! */
+			error(FATAL, "ExportPS not implemented yet.\n");
+/*			if (Bfildes(outp) == 1) {		*/
+/*				restore();				*/
+/*				Bprint(outp, "%s", buf);	*/
+/*				save();				*/
+/*			}						*/
+		}
+/*		 else
+			error(WARNING, "Unknown string <%s %s> after x X\n", str, buf);
+*/
+
+		break;
+	}
+	while ((c = Bgetc(inp)) != '\n' && c != Beof);
+	inputlineno++;
+}
+
diff --git a/src/cmd/postscript/tr2post/draw.c b/src/cmd/postscript/tr2post/draw.c
new file mode 100644
index 0000000..575ec88
--- /dev/null
+++ b/src/cmd/postscript/tr2post/draw.c
@@ -0,0 +1,342 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "../common/common.h"
+#include "tr2post.h"
+
+BOOLEAN drawflag = FALSE;
+BOOLEAN	inpath = FALSE;			/* TRUE if we're putting pieces together */
+
+void
+cover(double x, double y) {
+}
+
+void
+drawspline(Biobuf *Bp, int flag) {	/* flag!=1 connect end points */
+	int x[100], y[100];
+	int i, N;
+/*
+ *
+ * Spline drawing routine for Postscript printers. The complicated stuff is
+ * handled by procedure Ds, which should be defined in the library file. I've
+ * seen wrong implementations of troff's spline drawing, so fo the record I'll
+ * write down the parametric equations and the necessary conversions to Bezier
+ * cubic splines (as used in Postscript).
+ *
+ *
+ * Parametric equation (x coordinate only):
+ *
+ *
+ *	    (x2 - 2 * x1 + x0)    2                    (x0 + x1)
+ *	x = ------------------ * t   + (x1 - x0) * t + ---------
+ *		    2					   2
+ *
+ *
+ * The coefficients in the Bezier cubic are,
+ *
+ *
+ *	A = 0
+ *	B = (x2 - 2 * x1 + x0) / 2
+ *	C = x1 - x0
+ *
+ *
+ * while the current point is,
+ *
+ *	current-point = (x0 + x1) / 2
+ *
+ * Using the relationships given in the Postscript manual (page 121) it's easy to
+ * see that the control points are given by,
+ *
+ *
+ *	x0' = (x0 + 5 * x1) / 6
+ *	x1' = (x2 + 5 * x1) / 6
+ *	x2' = (x1 + x2) / 2
+ *
+ *
+ * where the primed variables are the ones used by curveto. The calculations
+ * shown above are done in procedure Ds using the coordinates set up in both
+ * the x[] and y[] arrays.
+ *
+ * A simple test of whether your spline drawing is correct would be to use cip
+ * to draw a spline and some tangent lines at appropriate points and then print
+ * the file.
+ *
+ */
+
+	for (N=2; N<sizeof(x)/sizeof(x[0]); N++)
+		if (Bgetfield(Bp, 'd', &x[N], 0)<=0 || Bgetfield(Bp, 'd', &y[N], 0)<=0)
+			break;
+
+	x[0] = x[1] = hpos;
+	y[0] = y[1] = vpos;
+
+	for (i = 1; i < N; i++) {
+		x[i+1] += x[i];
+		y[i+1] += y[i];
+	}
+
+	x[N] = x[N-1];
+	y[N] = y[N-1];
+
+	for (i = ((flag!=1)?0:1); i < ((flag!=1)?N-1:N-2); i++) {
+		endstring();
+		if (pageon())
+			Bprint(Bstdout, "%d %d %d %d %d %d Ds\n", x[i], y[i], x[i+1], y[i+1], x[i+2], y[i+2]);
+/*		if (dobbox == TRUE) {		/* could be better */
+/*	    		cover((double)(x[i] + x[i+1])/2,(double)-(y[i] + y[i+1])/2);
+/*	    		cover((double)x[i+1], (double)-y[i+1]);
+/*	    		cover((double)(x[i+1] + x[i+2])/2, (double)-(y[i+1] + y[i+2])/2);
+/*		}
+ */
+	}
+
+	hpos = x[N];			/* where troff expects to be */
+	vpos = y[N];
+}
+
+void
+draw(Biobuf *Bp) {
+
+	int r, x1, y1, x2, y2, i;
+	int d1, d2;
+
+	drawflag = TRUE;
+	r = Bgetrune(Bp);
+	switch(r) {
+	case 'l':
+		if (Bgetfield(Bp, 'd', &x1, 0)<=0 || Bgetfield(Bp, 'd', &y1, 0)<=0 || Bgetfield(Bp, 'r', &i, 0)<=0)
+			error(FATAL, "draw line function, destination coordinates not found.\n");
+
+		endstring();
+		if (pageon())
+			Bprint(Bstdout, "%d %d %d %d Dl\n", hpos, vpos, hpos+x1, vpos+y1);
+		hpos += x1;
+		vpos += y1;
+		break;
+	case 'c':
+		if (Bgetfield(Bp, 'd', &d1, 0)<=0)
+			error(FATAL, "draw circle function, diameter coordinates not found.\n");
+
+		endstring();
+		if (pageon())
+			Bprint(Bstdout, "%d %d %d %d De\n", hpos, vpos, d1, d1);
+		hpos += d1;
+		break;
+	case 'e':
+		if (Bgetfield(Bp, 'd', &d1, 0)<=0 || Bgetfield(Bp, 'd', &d2, 0)<=0)
+			error(FATAL, "draw ellipse function, diameter coordinates not found.\n");
+
+		endstring();
+		if (pageon())
+			Bprint(Bstdout, "%d %d %d %d De\n", hpos, vpos, d1, d2);
+		hpos += d1;
+		break;
+	case 'a':
+		if (Bgetfield(Bp, 'd', &x1, 0)<=0 || Bgetfield(Bp, 'd', &y1, 0)<=0 || Bgetfield(Bp, 'd', &x2, 0)<=0 || Bgetfield(Bp, 'd', &y2, 0)<=0)
+			error(FATAL, "draw arc function, coordinates not found.\n");
+
+		endstring();
+		if (pageon())
+			Bprint(Bstdout, "%d %d %d %d %d %d Da\n", hpos, vpos, x1, y1, x2, y2);
+		hpos += x1 + x2;
+		vpos += y1 + y2;
+		break;
+	case 'q':
+		drawspline(Bp, 1);
+		break;
+	case '~':
+		drawspline(Bp, 2);
+		break;
+	default:
+		error(FATAL, "unknown draw function <%c>\n", r);
+		break;
+	}
+}
+
+void
+beginpath(char *buf, int copy) {
+
+/*
+ * Called from devcntrl() whenever an "x X BeginPath" command is read. It's used
+ * to mark the start of a sequence of drawing commands that should be grouped
+ * together and treated as a single path. By default the drawing procedures in
+ * *drawfile treat each drawing command as a separate object, and usually start
+ * with a newpath (just as a precaution) and end with a stroke. The newpath and
+ * stroke isolate individual drawing commands and make it impossible to deal with
+ * composite objects. "x X BeginPath" can be used to mark the start of drawing
+ * commands that should be grouped together and treated as a single object, and
+ * part of what's done here ensures that the PostScript drawing commands defined
+ * in *drawfile skip the newpath and stroke, until after the next "x X DrawPath"
+ * command. At that point the path that's been built up can be manipulated in
+ * various ways (eg. filled and/or stroked with a different line width).
+ *
+ * Color selection is one of the options that's available in parsebuf(),
+ * so if we get here we add *colorfile to the output file before doing
+ * anything important.
+ *
+ */
+	if (inpath == FALSE) {
+		endstring();
+	/*	getdraw();	*/
+	/*	getcolor(); */
+		Bprint(Bstdout, "gsave\n");
+		Bprint(Bstdout, "newpath\n");
+		Bprint(Bstdout, "%d %d m\n", hpos, vpos);
+		Bprint(Bstdout, "/inpath true def\n");
+		if ( copy == TRUE )
+			Bprint(Bstdout, "%s\n", buf);
+		inpath = TRUE;
+	}
+}
+
+static void parsebuf(char*);
+
+void
+drawpath(char *buf, int copy) {
+
+/*
+ *
+ * Called from devcntrl() whenever an "x X DrawPath" command is read. It marks the
+ * end of the path started by the last "x X BeginPath" command and uses whatever
+ * has been passed along in *buf to manipulate the path (eg. fill and/or stroke
+ * the path). Once that's been done the drawing procedures are restored to their
+ * default behavior in which each drawing command is treated as an isolated path.
+ * The new version (called after "x X DrawPath") has copy set to FALSE, and calls
+ * parsebuf() to figure out what goes in the output file. It's a feeble attempt
+ * to free users and preprocessors (like pic) from having to know PostScript. The
+ * comments in parsebuf() describe what's handled.
+ *
+ * In the early version a path was started with "x X BeginObject" and ended with
+ * "x X EndObject". In both cases *buf was just copied to the output file, and
+ * was expected to be legitimate PostScript that manipulated the current path.
+ * The old escape sequence will be supported for a while (for Ravi), and always
+ * call this routine with copy set to TRUE.
+ * 
+ *
+ */
+
+	if ( inpath == TRUE ) {
+		if ( copy == TRUE )
+			Bprint(Bstdout, "%s\n", buf);
+		else
+			parsebuf(buf);
+		Bprint(Bstdout, "grestore\n");
+		Bprint(Bstdout, "/inpath false def\n");
+/*		reset();		*/
+		inpath = FALSE;
+	}
+}
+
+
+/*****************************************************************************/
+
+static void
+parsebuf(char *buf)
+{
+	char	*p;			/* usually the next token */
+	char *q;
+	int		gsavelevel = 0;		/* non-zero if we've done a gsave */
+
+/*
+ *
+ * Simple minded attempt at parsing the string that followed an "x X DrawPath"
+ * command. Everything not recognized here is simply ignored - there's absolutely
+ * no error checking and what was originally in buf is clobbered by strtok().
+ * A typical *buf might look like,
+ *
+ *	gray .9 fill stroke
+ *
+ * to fill the current path with a gray level of .9 and follow that by stroking the
+ * outline of the path. Since unrecognized tokens are ignored the last example
+ * could also be written as,
+ *
+ *	with gray .9 fill then stroke
+ *
+ * The "with" and "then" strings aren't recognized tokens and are simply discarded.
+ * The "stroke", "fill", and "wfill" force out appropriate PostScript code and are
+ * followed by a grestore. In otherwords changes to the grahics state (eg. a gray
+ * level or color) are reset to default values immediately after the stroke, fill,
+ * or wfill tokens. For now "fill" gets invokes PostScript's eofill operator and
+ * "wfill" calls fill (ie. the operator that uses the non-zero winding rule).
+ *
+ * The tokens that cause temporary changes to the graphics state are "gray" (for
+ * setting the gray level), "color" (for selecting a known color from the colordict
+ * dictionary defined in *colorfile), and "line" (for setting the line width). All
+ * three tokens can be extended since strncmp() makes the comparison. For example
+ * the strings "line" and "linewidth" accomplish the same thing. Colors are named
+ * (eg. "red"), but must be appropriately defined in *colorfile. For now all three
+ * tokens must be followed immediately by their single argument. The gray level
+ * (ie. the argument that follows "gray") should be a number between 0 and 1, with
+ * 0 for black and 1 for white.
+ *
+ * To pass straight PostScript through enclose the appropriate commands in double
+ * quotes. Straight PostScript is only bracketed by the outermost gsave/grestore
+ * pair (ie. the one from the initial "x X BeginPath") although that's probably
+ * a mistake. Suspect I may have to change the double quote delimiters.
+ *
+ */
+
+	for( ; p != nil ; p = q ) {
+		if( q = strchr(p, ' ') ) {
+			*q++ = '\0';
+		}
+
+		if ( gsavelevel == 0 ) {
+			Bprint(Bstdout, "gsave\n");
+			gsavelevel++;
+		}
+		if ( strcmp(p, "stroke") == 0 ) {
+			Bprint(Bstdout, "closepath stroke\ngrestore\n");
+			gsavelevel--;
+		} else if ( strcmp(p, "openstroke") == 0 ) {
+			Bprint(Bstdout, "stroke\ngrestore\n");
+			gsavelevel--;
+		} else if ( strcmp(p, "fill") == 0 ) {
+			Bprint(Bstdout, "eofill\ngrestore\n");
+			gsavelevel--;
+		} else if ( strcmp(p, "wfill") == 0 ) {
+			Bprint(Bstdout, "fill\ngrestore\n");
+			gsavelevel--;
+		} else if ( strcmp(p, "sfill") == 0 ) {
+			Bprint(Bstdout, "eofill\ngrestore\ngsave\nstroke\ngrestore\n");
+			gsavelevel--;
+		} else if ( strncmp(p, "gray", strlen("gray")) == 0 ) {
+			if( q ) {
+				p = q;
+				if ( q = strchr(p, ' ') )
+					*q++ = '\0';
+				Bprint(Bstdout, "%s setgray\n", p);
+			}
+		} else if ( strncmp(p, "color", strlen("color")) == 0 ) {
+			if( q ) {
+				p = q;
+				if ( q = strchr(p, ' ') )
+					*q++ = '\0';
+				Bprint(Bstdout, "/%s setcolor\n", p);
+			}
+		} else if ( strncmp(p, "line", strlen("line")) == 0 ) {
+			if( q ) {
+				p = q;
+				if ( q = strchr(p, ' ') )
+					*q++ = '\0';
+				Bprint(Bstdout, "%s resolution mul 2 div setlinewidth\n", p);
+			}
+		} else if ( strncmp(p, "reverse", strlen("reverse")) == 0 )
+			Bprint(Bstdout, "reversepath\n");
+		else if ( *p == '"' ) {
+			for ( ; gsavelevel > 0; gsavelevel-- )
+				Bprint(Bstdout, "grestore\n");
+			if ( q != nil )
+				*--q = ' ';
+			if ( (q = strchr(p, '"')) != nil ) {
+				*q++ = '\0';
+				Bprint(Bstdout, "%s\n", p);
+			}
+		}
+	}
+
+	for ( ; gsavelevel > 0; gsavelevel-- )
+		Bprint(Bstdout, "grestore\n");
+
+}
diff --git a/src/cmd/postscript/tr2post/mkfile b/src/cmd/postscript/tr2post/mkfile
new file mode 100644
index 0000000..e1e5aec
--- /dev/null
+++ b/src/cmd/postscript/tr2post/mkfile
@@ -0,0 +1,36 @@
+<$PLAN9/src/mkhdr
+
+<../config
+
+COMMONDIR=../common
+
+SHORTLIB=bio 9
+TARG=tr2post
+
+OFILES=tr2post.$O\
+	chartab.$O\
+	Bgetfield.$O\
+	conv.$O\
+	utils.$O\
+	devcntl.$O\
+	draw.$O\
+	readDESC.$O\
+	ps_include.$O\
+	pictures.$O\
+	common.$O\
+
+HFILES=tr2post.h\
+	ps_include.h\
+	$COMMONDIR/common.h\
+	$COMMONDIR/comments.h\
+	$COMMONDIR/path.h\
+	$COMMONDIR/ext.h\
+
+BIN=$POSTBIN
+
+<$PLAN9/src/mkone
+
+CFLAGS=$CFLAGS -c -D'PROGRAMVERSION="0.1"' -D'DOROUND=1' -I$COMMONDIR
+
+%.$O:	$COMMONDIR/%.c
+	$CC $CFLAGS $COMMONDIR/$stem.c
diff --git a/src/cmd/postscript/tr2post/pictures.c b/src/cmd/postscript/tr2post/pictures.c
new file mode 100644
index 0000000..a20b7ad
--- /dev/null
+++ b/src/cmd/postscript/tr2post/pictures.c
@@ -0,0 +1,295 @@
+/*
+ *
+ * PostScript picture inclusion routines. Support for managing in-line pictures
+ * has been added, and works in combination with the simple picpack pre-processor
+ * that's supplied with this package. An in-line picture begins with a special
+ * device control command that looks like,
+ *
+ *		x X InlinPicture name size
+ *
+ * where name is the pathname of the original picture file and size is the number
+ * of bytes in the picture, which begins immediately on the next line. When dpost
+ * encounters the InlinePicture device control command inlinepic() is called and
+ * that routine appends the string name and the integer size to a temporary file
+ * (fp_pic) and then adds the next size bytes read from the current input file to
+ * file fp_pic. All in-line pictures are saved in fp_pic and located later using
+ * the name string and picture file size that separate pictures saved in fp_pic.
+ *
+ * When a picture request (ie. an "x X PI" command) is encountered picopen() is
+ * called and it first looks for the picture file in fp_pic. If it's found there
+ * the entire picture (ie. size bytes) is copied from fp_pic to a new temp file
+ * and that temp file is used as the picture file. If there's nothing in fp_pic
+ * or if the lookup failed the original route is taken.
+ *
+ * Support for in-line pictures is an attempt to address requirements, expressed
+ * by several organizations, of being able to store a document as a single file
+ * (usually troff input) that can then be sent through dpost and ultimately to
+ * a PostScript printer. The mechanism may help some users, but the are obvious
+ * disadvantages to this approach, and the original mechanism is the recommended
+ * approach! Perhaps the most important problem is that troff output, with in-line
+ * pictures included, doesn't fit the device independent language accepted by
+ * important post-processors (like proff) and that means you won't be able to
+ * reliably preview a packed file on your 5620 (or whatever).
+ *
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <stdio.h>
+#include "ext.h"
+#include "common.h"
+#include "tr2post.h"
+/* PostScript file structuring comments */
+#include "comments.h"
+/* general purpose definitions */
+/* #include "gen.h" */
+/* just for TEMPDIR definition */
+#include "path.h"
+/* external variable declarations */
+/* #include "ext.h" */
+
+Biobuf	*bfp_pic = NULL;
+Biobuf	*Bfp_pic;
+Biobuf	*picopen(char *);
+
+#define MAXGETFIELDS	16
+char *fields[MAXGETFIELDS];
+int nfields;
+
+extern int	devres, hpos, vpos;
+extern int	picflag;
+
+/*****************************************************************************/
+
+void
+picture(Biobuf *inp, char *buf) {
+	int	poffset;		/* page offset */
+	int	indent;		/* indent */
+	int	length;		/* line length  */
+	int	totrap;		/* distance to next trap */
+	char	name[100];	/* picture file and page string */
+	char	hwo[40], *p;	/* height, width and offset strings */
+	char	flags[20];		/* miscellaneous stuff */
+	int	page = 1;		/* page number pulled from name[] */
+	double	frame[4];	/* height, width, y, and x offsets from hwo[] */
+	char	units;		/* scale indicator for frame dimensions */
+	int	whiteout = 0;	/* white out the box? */
+	int	outline = 0;	/* draw a box around the picture? */
+	int	scaleboth = 0;	/* scale both dimensions? */
+	double	adjx = 0.5;	/* left-right adjustment */
+	double	adjy = 0.5;	/* top-bottom adjustment */
+	double	rot = 0;	/* rotation in clockwise degrees */
+	Biobuf	*fp_in;	/* for *name */
+	int	i;			/* loop index */
+
+/*
+ *
+ * Called from devcntrl() after an 'x X PI' command is found. The syntax of that
+ * command is:
+ *
+ *	x X PI:args
+ *
+ * with args separated by colons and given by:
+ *
+ *	poffset
+ *	indent
+ *	length
+ *	totrap
+ *	file[(page)]
+ *	height[,width[,yoffset[,xoffset]]]
+ *	[flags]
+ *
+ * poffset, indent, length, and totrap are given in machine units. height, width,
+ * and offset refer to the picture frame in inches, unless they're followed by
+ * the u scale indicator. flags is a string that provides a little bit of control
+ * over the placement of the picture in the frame. Rotation of the picture, in
+ * clockwise degrees, is set by the a flag. If it's not followed by an angle
+ * the current rotation angle is incremented by 90 degrees, otherwise the angle
+ * is set by the number that immediately follows the a.
+ *
+ */
+
+	if (!picflag)		/* skip it */
+		return;
+	endstring();
+
+	flags[0] = '\0';			/* just to be safe */
+
+	nfields = getfields(buf, fields, MAXGETFIELDS, 0, ":\n");
+	if (nfields < 6) {
+		error(WARNING, "too few arguments to specify picture");
+		return;
+	}
+	poffset = atoi(fields[1]);
+	indent = atoi(fields[2]);
+	length = atoi(fields[3]);
+	totrap = atoi(fields[4]);
+	strncpy(name, fields[5], sizeof(name));
+	strncpy(hwo, fields[6], sizeof(hwo));
+	if (nfields >= 6)
+		strncpy(flags, fields[7], sizeof(flags));
+
+	nfields = getfields(buf, fields, MAXGETFIELDS, 0, "()");
+	if (nfields == 2) {
+		strncpy(name, fields[0], sizeof(name));
+		page = atoi(fields[1]);
+	}
+
+	if ((fp_in = picopen(name)) == NULL) {
+		error(WARNING, "can't open picture file %s\n", name);
+		return;
+	}
+
+	frame[0] = frame[1] = -1;		/* default frame height, width */
+	frame[2] = frame[3] = 0;		/* and y and x offsets */
+
+	for (i = 0, p = hwo-1; i < 4 && p != NULL; i++, p = strchr(p, ','))
+		if (sscanf(++p, "%lf%c", &frame[i], &units) == 2)
+	    		if (units == 'i' || units == ',' || units == '\0')
+				frame[i] *= devres;
+
+	if (frame[0] <= 0)		/* check what we got for height */
+		frame[0] = totrap;
+
+    	if (frame[1] <= 0)		/* and width - check too big?? */
+		frame[1] = length - indent;
+
+	frame[3] += poffset + indent;	/* real x offset */
+
+	for (i = 0; flags[i]; i++)
+		switch (flags[i]) {
+		case 'c': adjx = adjy = 0.5; break;	/* move to the center */
+		case 'l': adjx = 0; break;		/* left */
+		case 'r': adjx = 1; break;		/* right */
+		case 't': adjy = 1; break;		/* top */
+		case 'b': adjy = 0; break;		/* or bottom justify */
+		case 'o': outline = 1; break;	/* outline the picture */
+		case 'w': whiteout = 1; break;	/* white out the box */
+		case 's': scaleboth = 1; break;	/* scale both dimensions */
+		case 'a': if ( sscanf(&flags[i+1], "%lf", &rot) != 1 )
+			  rot += 90;
+	}
+
+	/* restore(); */
+	endstring();
+	Bprint(Bstdout, "cleartomark\n");
+	Bprint(Bstdout, "saveobj restore\n");
+
+	ps_include(fp_in, Bstdout, page, whiteout, outline, scaleboth,
+		frame[3]+frame[1]/2, -vpos-frame[2]-frame[0]/2, frame[1], frame[0], adjx, adjy, -rot);
+	/* save(); */
+	Bprint(Bstdout, "/saveobj save def\n");
+	Bprint(Bstdout, "mark\n");
+	Bterm(fp_in);
+
+}
+
+/*
+ *
+ * Responsible for finding and opening the next picture file. If we've accumulated
+ * any in-line pictures fp_pic won't be NULL and we'll look there first. If *path
+ * is found in *fp_pic we create another temp file, open it for update, unlink it,
+ * copy in the picture, seek back to the start of the new temp file, and return
+ * the file pointer to the caller. If fp_pic is NULL or the lookup fails we just
+ * open file *path and return the resulting file pointer to the caller.
+ *
+ */
+Biobuf *
+picopen(char *path) {
+/*	char	name[100];	/* pathnames */
+/*	long	pos;			/* current position */
+/*	long	total;			/* and sizes - from *fp_pic */
+	Biobuf *bfp;
+	Biobuf	*Bfp;		/* and pointer for the new temp file */
+
+
+	if ((bfp = Bopen(path, OREAD)) == 0)
+		error(FATAL, "can't open %s\n", path);
+	Bfp = bfp;
+	return(Bfp);
+#ifdef UNDEF
+	if (Bfp_pic != NULL) {
+		Bseek(Bfp_pic, 0L, 0);
+		while (Bgetfield(Bfp_pic, 's', name, 99)>0
+			&& Bgetfield(Bfp_pic, 'd', &total, 0)>0) {
+			pos = Bseek(Bfp_pic, 0L, 1);
+			if (strcmp(path, name) == 0) {
+				if (tmpnam(pictmpname) == NULL)
+					error(FATAL, "can't generate temp file name");
+				if ( (bfp = Bopen(pictmpname, ORDWR)) == NULL )
+					error(FATAL, "can't open %s", pictmpname);
+				Bfp = bfp;
+				piccopy(Bfp_pic, Bfp, total);
+				Bseek(Bfp, 0L, 0);
+				return(Bfp);
+	    		}
+			Bseek(Bfp_pic, total+pos, 0);
+		}
+	}
+	if ((bfp = Bopen(path, OREAD)) == 0)
+		Bfp = 0;
+	else
+		Bfp = bfp;
+	return(Bfp);
+#endif
+}
+
+/*
+ *
+ * Adds an in-line picture file to the end of temporary file *Bfp_pic. All pictures
+ * grabbed from the input file are saved in the same temp file. Each is preceeded
+ * by a one line header that includes the original picture file pathname and the
+ * size of the picture in bytes. The in-line picture file is opened for update,
+ * left open, and unlinked so it disappears when we do.
+ *
+ */
+/*	*fp;			/* current input file */
+/*	*buf;			/* whatever followed "x X InlinePicture" */
+
+#ifdef UNDEF
+void
+inlinepic(Biobuf *Bfp, char *buf) {
+	char	name[100];		/* picture file pathname */
+	long	total;			/* and size - both from *buf */
+
+
+	if (Bfp_pic == NULL ) {
+		tmpnam(pictmpname);
+		if ((bfp_pic = Bopen(pictmpname, ORDWR)) == 0)
+	    		error(FATAL, "can't open in-line picture file %s", ipictmpname);
+		unlink(pictmpname);
+	}
+
+	if ( sscanf(buf, "%s %ld", name, &total) != 2 )
+		error(FATAL, "in-line picture error");
+
+	fseek(Bfp_pic, 0L, 2);
+	fprintf(Bfp_pic, "%s %ld\n", name, total);
+	getc(fp);
+	fflush(fp_pic);
+	piccopy(fp, fp_pic, total);
+	ungetc('\n', fp);
+
+}
+#endif
+
+/*
+ *
+ * Copies total bytes from file fp_in to fp_out. Used to append picture files to
+ * *fp_pic and then copy them to yet another temporary file immediately before
+ * they're used (in picture()).
+ *
+ */
+/*	*fp_in;	input */
+/*	*fp_out;	and output file pointers */
+/*	total;		number of bytes to be copied */
+void
+piccopy(Biobuf *Bfp_in, Biobuf *Bfp_out, long total) {
+	long i;
+
+	for (i = 0; i < total; i++)
+		if (Bputc(Bfp_out, Bgetc(Bfp_in)) < 0)
+			error(FATAL, "error copying in-line picture file");
+	Bflush(Bfp_out);
+}
diff --git a/src/cmd/postscript/tr2post/ps_include.c b/src/cmd/postscript/tr2post/ps_include.c
new file mode 100644
index 0000000..af30ff2
--- /dev/null
+++ b/src/cmd/postscript/tr2post/ps_include.c
@@ -0,0 +1,191 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <stdio.h>
+#include "../common/common.h"
+#include "ps_include.h"
+
+extern int curpostfontid;
+extern int curfontsize;
+
+typedef struct {long start, end;} Section;
+static char *buf;
+
+static void
+copy(Biobuf *fin, Biobuf *fout, Section *s) {
+	int cond;
+	if (s->end <= s->start)
+		return;
+	Bseek(fin, s->start, 0);
+	while (Bseek(fin, 0L, 1) < s->end && (buf=Brdline(fin, '\n')) != NULL){
+		/*
+		 * We have to be careful here, because % can legitimately appear
+		 * in Ascii85 encodings, and must not be elided.
+		 * The goal here is to make any DSC comments impotent without
+		 * actually changing the behavior of the Postscript.
+		 * Since stripping ``comments'' breaks Ascii85, we can instead just
+		 * indent comments a space, which turns DSC comments into non-DSC comments
+		 * and has no effect on binary encodings, which are whitespace-blind.
+		 */
+		if(buf[0] == '%')
+			Bputc(fout, ' ');
+		Bwrite(fout, buf, Blinelen(fin));
+	}
+}
+
+/*
+ *
+ * Reads a PostScript file (*fin), and uses structuring comments to locate the
+ * prologue, trailer, global definitions, and the requested page. After the whole
+ * file is scanned, the  special ps_include PostScript definitions are copied to
+ * *fout, followed by the prologue, global definitions, the requested page, and
+ * the trailer. Before returning the initial environment (saved in PS_head) is
+ * restored.
+ *
+ * By default we assume the picture is 8.5 by 11 inches, but the BoundingBox
+ * comment, if found, takes precedence.
+ *
+ */
+/*	*fin, *fout;		/* input and output files */
+/*	page_no;		/* physical page number from *fin */
+/*	whiteout;		/* erase picture area */
+/*	outline;		/* draw a box around it and */
+/*	scaleboth;		/* scale both dimensions - if not zero */
+/*	cx, cy;			/* center of the picture and */
+/*	sx, sy;			/* its size - in current coordinates */
+/*	ax, ay;			/* left-right, up-down adjustment */
+/*	rot;			/* rotation - in clockwise degrees */
+
+void
+ps_include(Biobuf *fin, Biobuf *fout, int page_no, int whiteout,
+	int outline, int scaleboth, double cx, double cy, double sx, double sy,
+	double ax, double ay, double rot) {
+	char		**strp;
+	int		foundpage = 0;		/* found the page when non zero */
+	int		foundpbox = 0;		/* found the page bounding box */
+	int		nglobal = 0;		/* number of global defs so far */
+	int		maxglobal = 0;		/* and the number we've got room for */
+	Section	prolog, page, trailer;	/* prologue, page, and trailer offsets */
+	Section	*global;		/* offsets for all global definitions */
+	double	llx, lly;		/* lower left and */
+	double	urx, ury;		/* upper right corners - default coords */
+	double	w = whiteout != 0;	/* mostly for the var() macro */
+	double	o = outline != 0;
+	double	s = scaleboth != 0;
+	int		i;		/* loop index */
+
+#define has(word)	(strncmp(buf, word, strlen(word)) == 0)
+#define grab(n)		((Section *)(nglobal \
+			? realloc((char *)global, n*sizeof(Section)) \
+			: calloc(n, sizeof(Section))))
+
+	llx = lly = 0;		/* default BoundingBox - 8.5x11 inches */
+	urx = 72 * 8.5;
+	ury = 72 * 11.0;
+
+	/* section boundaries and bounding box */
+
+	prolog.start = prolog.end = 0;
+	page.start = page.end = 0;
+	trailer.start = 0;
+	Bseek(fin, 0L, 0);
+
+	while ((buf=Brdline(fin, '\n')) != NULL) {
+		buf[Blinelen(fin)-1] = '\0';
+		if (!has("%%"))
+			continue;
+		else if (has("%%Page: ")) {
+			if (!foundpage)
+				page.start = Bseek(fin, 0L, 1);
+			sscanf(buf, "%*s %*s %d", &i);
+			if (i == page_no)
+				foundpage = 1;
+			else if (foundpage && page.end <= page.start)
+				page.end = Bseek(fin, 0L, 1);
+		} else if (has("%%EndPage: ")) {
+			sscanf(buf, "%*s %*s %d", &i);
+			if (i == page_no) {
+				foundpage = 1;
+				page.end = Bseek(fin, 0L, 1);
+			}
+			if (!foundpage)
+				page.start = Bseek(fin, 0L, 1);
+		} else if (has("%%PageBoundingBox: ")) {
+			if (i == page_no) {
+				foundpbox = 1;
+				sscanf(buf, "%*s %lf %lf %lf %lf",
+						&llx, &lly, &urx, &ury);
+			}
+		} else if (has("%%BoundingBox: ")) {
+			if (!foundpbox)
+				sscanf(buf,"%*s %lf %lf %lf %lf",
+						&llx, &lly, &urx, &ury);
+		} else if (has("%%EndProlog") || has("%%EndSetup") || has("%%EndDocumentSetup"))
+			prolog.end = page.start = Bseek(fin, 0L, 1);
+		else if (has("%%Trailer"))
+			trailer.start = Bseek(fin, 0L, 1);
+		else if (has("%%BeginGlobal")) {
+			if (page.end <= page.start) {
+				if (nglobal >= maxglobal) {
+					maxglobal += 20;
+					global = grab(maxglobal);
+				}
+				global[nglobal].start = Bseek(fin, 0L, 1);
+			}
+		} else if (has("%%EndGlobal"))
+			if (page.end <= page.start)
+				global[nglobal++].end = Bseek(fin, 0L, 1);
+	}
+	Bseek(fin, 0L, 2);
+	if (trailer.start == 0)
+		trailer.start = Bseek(fin, 0L, 1);
+	trailer.end = Bseek(fin, 0L, 1);
+
+	if (page.end <= page.start)
+		page.end = trailer.start;
+
+/*
+fprint(2, "prolog=(%d,%d)\n", prolog.start, prolog.end);
+fprint(2, "page=(%d,%d)\n", page.start, page.end);
+for(i = 0; i < nglobal; i++)
+	fprint(2, "global[%d]=(%d,%d)\n", i, global[i].start, global[i].end);
+fprint(2, "trailer=(%d,%d)\n", trailer.start, trailer.end);
+*/
+
+	/* all output here */
+	for (strp = PS_head; *strp != NULL; strp++)
+		Bwrite(fout, *strp, strlen(*strp));
+
+	Bprint(fout, "/llx %g def\n", llx);
+	Bprint(fout, "/lly %g def\n", lly);
+	Bprint(fout, "/urx %g def\n", urx);
+	Bprint(fout, "/ury %g def\n", ury);
+	Bprint(fout, "/w %g def\n", w);
+	Bprint(fout, "/o %g def\n", o);
+	Bprint(fout, "/s %g def\n", s);
+	Bprint(fout, "/cx %g def\n", cx);
+	Bprint(fout, "/cy %g def\n", cy);
+	Bprint(fout, "/sx %g def\n", sx);
+	Bprint(fout, "/sy %g def\n", sy);
+	Bprint(fout, "/ax %g def\n", ax);
+	Bprint(fout, "/ay %g def\n", ay);
+	Bprint(fout, "/rot %g def\n", rot);
+
+	for (strp = PS_setup; *strp != NULL; strp++)
+		Bwrite(fout, *strp, strlen(*strp));
+
+	copy(fin, fout, &prolog);
+	for(i = 0; i < nglobal; i++)
+		copy(fin, fout, &global[i]);
+	copy(fin, fout, &page);
+	copy(fin, fout, &trailer);
+	for (strp = PS_tail; *strp != NULL; strp++)
+		Bwrite(fout, *strp, strlen(*strp));
+
+	if(nglobal)
+		free(global);
+
+	/* force the program to reestablish its state */
+	curpostfontid = -1;
+	curfontsize = -1;
+}
diff --git a/src/cmd/postscript/tr2post/ps_include.h b/src/cmd/postscript/tr2post/ps_include.h
new file mode 100644
index 0000000..cef4905
--- /dev/null
+++ b/src/cmd/postscript/tr2post/ps_include.h
@@ -0,0 +1,66 @@
+static char *PS_head[] = {
+	"%ps_include: begin\n",
+	"save\n",
+	"/ed {exch def} def\n",
+	"{} /showpage ed\n",
+	"{} /copypage ed\n",
+	"{} /erasepage ed\n",
+	"{} /letter ed\n",
+	"currentdict /findfont known systemdict /findfont known and {\n",
+	"	/findfont systemdict /findfont get def\n",
+	"} if\n",
+	"36 dict dup /PS-include-dict-dw ed begin\n",
+	"/context ed\n",
+	"count array astore /o-stack ed\n",
+	"%ps_include: variables begin\n",
+	0
+};
+
+static char *PS_setup[] = {
+	"%ps_include: variables end\n",
+	"{llx lly urx ury} /bbox ed\n",
+	"{newpath 2 index exch 2 index exch dup 6 index exch\n",
+	" moveto 3 {lineto} repeat closepath} /boxpath ed\n",
+	"{dup mul exch dup mul add sqrt} /len ed\n",
+	"{2 copy gt {exch} if pop} /min ed\n",
+	"{2 copy lt {exch} if pop} /max ed\n",
+	"{transform round exch round exch A itransform} /nice ed\n",
+	"{6 array} /n ed\n",
+	"n defaultmatrix n currentmatrix n invertmatrix n concatmatrix /A ed\n",
+	"urx llx sub 0 A dtransform len /Sx ed\n",
+	"0 ury lly sub A dtransform len /Sy ed\n",
+	"llx urx add 2 div lly ury add 2 div A transform /Cy ed /Cx ed\n",
+	"rot dup sin abs /S ed cos abs /C ed\n",
+	"Sx S mul Sy C mul add /H ed\n",
+	"Sx C mul Sy S mul add /W ed\n",
+	"sy H div /Scaley ed\n",
+	"sx W div /Scalex ed\n",
+	"s 0 eq {Scalex Scaley min dup /Scalex ed /Scaley ed} if\n",
+	"sx Scalex W mul sub 0 max ax 0.5 sub mul cx add /cx ed\n",
+	"sy Scaley H mul sub 0 max ay 0.5 sub mul cy add /cy ed\n",
+	"urx llx sub 0 A dtransform exch atan rot exch sub /rot ed\n",
+	"n currentmatrix initgraphics setmatrix\n",
+	"cx cy translate\n",
+	"Scalex Scaley scale\n",
+	"rot rotate\n",
+	"Cx neg Cy neg translate\n",
+	"A concat\n",
+	"bbox boxpath clip newpath\n",
+	"w 0 ne {gsave bbox boxpath 1 setgray fill grestore} if\n",
+	"end\n",
+	"gsave\n",
+	"%ps_include: inclusion begin\n",
+	0
+};
+
+static char *PS_tail[] = {
+	"%ps_include: inclusion end\n",
+	"grestore\n",
+	"PS-include-dict-dw begin\n",
+	"o 0 ne {gsave A defaultmatrix /A ed llx lly nice urx ury nice\n",
+	"	initgraphics 0.1 setlinewidth boxpath stroke grestore} if\n",
+	"clear o-stack aload pop\n",
+	"context end restore\n",
+	"%ps_include: end\n",
+	0
+};
diff --git a/src/cmd/postscript/tr2post/readDESC.c b/src/cmd/postscript/tr2post/readDESC.c
new file mode 100644
index 0000000..03b8b64
--- /dev/null
+++ b/src/cmd/postscript/tr2post/readDESC.c
@@ -0,0 +1,139 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "common.h"
+#include "tr2post.h"
+#include "comments.h"
+#include "path.h"
+
+char *printdesclang = 0;
+char *encoding = 0;
+int devres;
+int unitwidth;
+int nspechars = 0;
+struct charent spechars[MAXSPECHARS];
+
+#define NDESCTOKS 9
+static char *desctoks[NDESCTOKS] = {
+	"PDL",
+	"Encoding",
+	"fonts",
+	"sizes",
+	"res",
+	"hor",
+	"vert",
+	"unitwidth",
+	"charset"
+};
+
+char *spechar[MAXSPECHARS];
+
+int
+hash(char *s, int l) {
+    unsigned i;
+
+    for (i=0; *s; s++)
+	i = i*10 + *s;
+    return(i % l);
+}
+
+BOOLEAN
+readDESC(void) {
+	char token[MAXTOKENSIZE];
+	char *descnameformat = "%s/dev%s/DESC";
+	char *descfilename = 0;
+	Biobuf *bfd;
+	Biobuf *Bfd;
+	int i, state = -1;
+	int fontindex = 0;
+
+	if (debug) Bprint(Bstderr, "readDESC()\n");
+	descfilename = galloc(descfilename, strlen(descnameformat)+strlen(FONTDIR)
+		+strlen(devname)+1, "readdesc");
+	sprint(descfilename, descnameformat, FONTDIR, devname);
+	if ((bfd = Bopen(unsharp(descfilename), OREAD)) == 0) {
+		error(WARNING, "cannot open file %s\n", descfilename);
+		return(0);
+	}
+	Bfd = bfd;
+
+	while (Bgetfield(Bfd, 's', token, MAXTOKENSIZE) > 0) {
+		for (i=0; i<NDESCTOKS; i++) {
+			if (strcmp(desctoks[i], token) == 0) {
+				state = i;
+				break;
+			}
+		}
+		if (i<NDESCTOKS) continue;
+		switch (state) {
+		case 0:
+			printdesclang=galloc(printdesclang, strlen(token)+1, "readdesc:");
+			strcpy(printdesclang, token);
+			if (debug) Bprint(Bstderr, "PDL %s\n", token);
+			break;	
+		case 1:
+			encoding=galloc(encoding, strlen(token)+1, "readdesc:");
+			strcpy(encoding, token);
+			if (debug) Bprint(Bstderr, "encoding %s\n", token);
+			break;
+		case 2:
+			if (fontmnt <=0) {
+				if (!isdigit(*token)) {
+					error(WARNING, "readdesc: expecting number of fonts in mount table.\n");
+					return(FALSE);
+				}
+				fontmnt = atoi(token) + 1;
+				fontmtab = galloc(fontmtab, fontmnt*sizeof(char *), "readdesc:");
+				
+				for (i=0; i<fontmnt; i++)
+					fontmtab[i] = 0;
+				fontindex = 0;
+			} else {
+				mountfont(++fontindex, token);
+				findtfn(token, TRUE);
+			}
+			break;
+		case 3:
+			/* I don't really care about sizes */
+			break;
+		case 4:
+			/* device resolution in dots per inch */
+			if (!isdigit(*token)) {
+				error(WARNING, "readdesc: expecting device resolution.\n");
+				return(FALSE);
+			}
+			devres = atoi(token);
+			if (debug) Bprint(Bstderr, "res %d\n", devres);
+			break;
+		case 5:
+			/* I don't really care about horizontal motion resolution */
+			if (debug) Bprint(Bstderr, "ignoring horizontal resolution\n");
+			break;
+		case 6:
+			/* I don't really care about vertical motion resolution */
+			if (debug) Bprint(Bstderr, "ignoring vertical resolution\n");
+			break;
+		case 7:
+			/* unitwidth is the font size at which the character widths are 1:1 */
+			if (!isdigit(*token)) {
+				error(WARNING, "readdesc: expecting unitwidth.\n");
+				return(FALSE);
+			}
+			unitwidth = atoi(token);
+			if (debug) Bprint(Bstderr, "unitwidth %d\n", unitwidth);
+			break;
+		case 8:
+			/* I don't really care about this list of special characters */
+			if (debug) Bprint(Bstderr, "ignoring special character <%s>\n", token);
+			break;
+		default:
+			if (*token == '#')
+				Brdline(Bfd, '\n');
+			else
+				error(WARNING, "unknown token %s in DESC file.\n", token);
+			break;
+		}
+	}
+	Bterm(Bfd);
+}
diff --git a/src/cmd/postscript/tr2post/shell.lib b/src/cmd/postscript/tr2post/shell.lib
new file mode 100644
index 0000000..d96e656
--- /dev/null
+++ b/src/cmd/postscript/tr2post/shell.lib
@@ -0,0 +1,1238 @@
+#
+# Shell library - for building devutf tables.
+#
+
+RESOLUTION=720
+UNITWIDTH=10
+
+OCTALESCAPES=${OCTALESCAPES:-160}	# <= code means add \0ddd names
+DOWNLOADVECTOR=FALSE			# TRUE can mean incomplete tables
+
+#
+# BuiltinTables returns command lines that generate PostScript programs
+# for building a typesetter description file and font width tables for
+# a relatively standard collection of fonts. Use awk to select a command
+# line or modify an existing command to build a width table for a new
+# font.
+#
+
+BuiltinTables() {
+	cat <<-'//End of BuiltinTables'
+		Proportional	R	Times-Roman
+		Proportional	I	Times-Italic
+		Proportional	B	Times-Bold
+		Proportional	BI	Times-BoldItalic
+		Proportional	AB	AvantGarde-Demi
+		Proportional	AI	AvantGarde-BookOblique
+		Proportional	AR	AvantGarde-Book
+		Proportional	AX	AvantGarde-DemiOblique
+		Proportional	H	Helvetica
+		Proportional	HB	Helvetica-Bold
+		Proportional	HI	Helvetica-Oblique
+		Proportional	HX	Helvetica-BoldOblique
+		Proportional	Hb	Helvetica-Narrow-Bold
+		Proportional	Hi	Helvetica-Narrow-Oblique
+		Proportional	Hr	Helvetica-Narrow
+		Proportional	Hx	Helvetica-Narrow-BoldOblique
+		Proportional	KB	Bookman-Demi
+		Proportional	KI	Bookman-LightItalic
+		Proportional	KR	Bookman-Light
+		Proportional	KX	Bookman-DemiItalic
+		Proportional	NB	NewCenturySchlbk-Bold
+		Proportional	NI	NewCenturySchlbk-Italic
+		Proportional	NR	NewCenturySchlbk-Roman
+		Proportional	NX	NewCenturySchlbk-BoldItalic
+		Proportional	PA	Palatino-Roman
+		Proportional	PB	Palatino-Bold
+		Proportional	PI	Palatino-Italic
+		Proportional	PX	Palatino-BoldItalic
+		Proportional	ZI	ZapfChancery-MediumItalic
+		FixedWidth	C	Courier
+		FixedWidth	CB	Courier-Bold
+		FixedWidth	CI	Courier-Oblique
+		FixedWidth	CO	Courier
+		FixedWidth	CW	Courier
+		FixedWidth	CX	Courier-BoldOblique
+		Dingbats	ZD	ZapfDingbats
+		Greek		GR	Symbol
+		Symbol		S	Symbol
+		Special		S1	Times-Roman
+		Description	DESC	---
+	//End of BuiltinTables
+}
+
+#
+# AllTables prints the complete list of builtin font names.
+#
+
+AllTables() {
+	BuiltinTables | awk '{print $2}'
+}
+
+#
+# Charset functions generate keyword/value pairs (as PostScript objects)
+# that describe the character set available in a font. The keyword is a
+# PostScript string that represents troff's name for the character. The
+# value is usually the literal name (i.e. begins with a /) assigned to
+# the character in the PostScript font. The value can also be an integer
+# or a PostScript string. An integer value is used as an index in the
+# current font's Encoding array. A string value is returned to the host
+# unchanged when the entry for the character is constructed. Entries that
+# have (") as their value are synonyms for the preceeding character.
+#
+# The 18 characters missing from ROM resident fonts on older printers are
+# flagged with the PostScript comment "% missing".
+#
+
+StandardCharset() {
+	cat <<-'//End of StandardCharset'
+		(!)	/exclam
+		(")	/quotedbl
+		(dq)	(")			% synonym
+		(#)	/numbersign
+		($)	/dollar
+		(%)	/percent
+		(&)	/ampersand
+		(')	/quoteright
+		(\()	/parenleft
+		(\))	/parenright
+		(*)	/asterisk
+		(+)	/plus
+		(,)	/comma
+		(-)	/hyphen			% changed from minus by request
+		(.)	/period
+		(/)	/slash
+		(0)	/zero
+		(1)	/one
+		(2)	/two
+		(3)	/three
+		(4)	/four
+		(5)	/five
+		(6)	/six
+		(7)	/seven
+		(8)	/eight
+		(9)	/nine
+		(:)	/colon
+		(;)	/semicolon
+		(<)	/less
+		(=)	/equal
+		(>)	/greater
+		(?)	/question
+		(@)	/at
+		(A)	/A
+		(B)	/B
+		(C)	/C
+		(D)	/D
+		(E)	/E
+		(F)	/F
+		(G)	/G
+		(H)	/H
+		(I)	/I
+		(J)	/J
+		(K)	/K
+		(L)	/L
+		(M)	/M
+		(N)	/N
+		(O)	/O
+		(P)	/P
+		(Q)	/Q
+		(R)	/R
+		(S)	/S
+		(T)	/T
+		(U)	/U
+		(V)	/V
+		(W)	/W
+		(X)	/X
+		(Y)	/Y
+		(Z)	/Z
+		([)	/bracketleft
+		(\\)	/backslash
+		(bs)	(")			% synonym
+		(])	/bracketright
+		(^)	/asciicircum
+		(_)	/underscore
+		(`)	/quoteleft
+		(a)	/a
+		(b)	/b
+		(c)	/c
+		(d)	/d
+		(e)	/e
+		(f)	/f
+		(g)	/g
+		(h)	/h
+		(i)	/i
+		(j)	/j
+		(k)	/k
+		(l)	/l
+		(m)	/m
+		(n)	/n
+		(o)	/o
+		(p)	/p
+		(q)	/q
+		(r)	/r
+		(s)	/s
+		(t)	/t
+		(u)	/u
+		(v)	/v
+		(w)	/w
+		(x)	/x
+		(y)	/y
+		(z)	/z
+		({)	/braceleft
+		(|)	/bar
+		(})	/braceright
+		(~)	/asciitilde
+		(\\`)	/grave			% devpost character
+		(ga)	(")			% synonym
+		(!!)	/exclamdown
+		(c|)	/cent
+		(ct)	(")			% devpost synonym
+		(L-)	/sterling
+		(ps)	(")			% devpost synonym
+		(xo)	/currency
+		(cr)	(")			% devpost synonym
+		(Y-)	/yen
+		(yn)	(")			% devpost synonym
+		(||)	/brokenbar		% missing
+		(so)	/section
+		(sc)	(")			% devpost synonym
+		("")	/dieresis
+		(:a)	(")			% devpost synonym
+		(co)	/copyright
+		(a_)	/ordfeminine
+		(<<)	/guillemotleft
+		(-,)	/logicalnot
+		(hy)	/hyphen
+		(--)	/minus
+		(ro)	/registered
+		(rg)	(")			% devpost synonym
+		(-^)	/macron
+		(-a)	(")			% devpost synonym
+		(0^)	/degree			% missing
+		(+-)	/plusminus		% missing
+		(2^)	/twosuperior		% missing
+		(3^)	/threesuperior		% missing
+		(\\')	/acute
+		(aa)	(")			% devpost synonym
+		(/u)	/mu			% missing
+		(P!)	/paragraph
+		(pg)	(")			% devpost synonym
+		(.^)	/periodcentered
+		(,,)	/cedilla
+		(,a)	(")			% devpost synonym
+		(1^)	/onesuperior		% missing
+		(o_)	/ordmasculine
+		(>>)	/guillemotright
+		(14)	/onequarter		% missing
+		(12)	/onehalf		% missing
+		(34)	/threequarters		% missing
+		(??)	/questiondown
+		(A`)	/Agrave
+		(A')	/Aacute
+		(A^)	/Acircumflex
+		(A~)	/Atilde
+		(A")	/Adieresis
+		(A*)	/Aring
+		(AE)	/AE
+		(C,)	/Ccedilla
+		(E`)	/Egrave
+		(E')	/Eacute
+		(E^)	/Ecircumflex
+		(E")	/Edieresis
+		(I`)	/Igrave
+		(I')	/Iacute
+		(I^)	/Icircumflex
+		(I")	/Idieresis
+		(D-)	/Eth			% missing
+		(N~)	/Ntilde
+		(O`)	/Ograve
+		(O')	/Oacute
+		(O^)	/Ocircumflex
+		(O~)	/Otilde
+		(O")	/Odieresis
+		(xx)	/multiply		% missing
+		(O/)	/Oslash
+		(U`)	/Ugrave
+		(U')	/Uacute
+		(U^)	/Ucircumflex
+		(U")	/Udieresis
+		(Y')	/Yacute			% missing
+		(TH)	/Thorn			% missing
+		(ss)	/germandbls
+		(a`)	/agrave
+		(a')	/aacute
+		(a^)	/acircumflex
+		(a~)	/atilde
+		(a")	/adieresis
+		(a*)	/aring
+		(ae)	/ae
+		(c,)	/ccedilla
+		(e`)	/egrave
+		(e')	/eacute
+		(e^)	/ecircumflex
+		(e")	/edieresis
+		(i`)	/igrave
+		(i')	/iacute
+		(i^)	/icircumflex
+		(i")	/idieresis
+		(d-)	/eth			% missing
+		(n~)	/ntilde
+		(o`)	/ograve
+		(o')	/oacute
+		(o^)	/ocircumflex
+		(o~)	/otilde
+		(o")	/odieresis
+		(-:)	/divide			% missing
+		(o/)	/oslash
+		(u`)	/ugrave
+		(u')	/uacute
+		(u^)	/ucircumflex
+		(u")	/udieresis
+		(y')	/yacute			% missing
+		(th)	/thorn			% missing
+		(y")	/ydieresis
+		(^a)	/circumflex		% devpost accent
+		(~a)	/tilde			% devpost accent
+		(Ua)	/breve			% devpost accent
+		(.a)	/dotaccent		% devpost accent
+		(oa)	/ring			% devpost accent
+		("a)	/hungarumlaut		% devpost accent
+		(Ca)	/ogonek			% devpost accent
+		(va)	/caron			% devpost accent
+	//End of StandardCharset
+}
+
+#
+# DingbatsCharset guarantees changes in StandardCharset don't show up in ZD.
+#
+
+DingbatsCharset() {
+	cat <<-'//End of DingbatsCharset'
+		(!)	/exclam
+		(")	/quotedbl
+		(#)	/numbersign
+		($)	/dollar
+		(%)	/percent
+		(&)	/ampersand
+		(')	/quoteright
+		(\()	/parenleft
+		(\))	/parenright
+		(*)	/asterisk
+		(+)	/plus
+		(,)	/comma
+		(-)	/minus		% also hyphen in devpost
+		(.)	/period
+		(/)	/slash
+		(0)	/zero
+		(1)	/one
+		(2)	/two
+		(3)	/three
+		(4)	/four
+		(5)	/five
+		(6)	/six
+		(7)	/seven
+		(8)	/eight
+		(9)	/nine
+		(:)	/colon
+		(;)	/semicolon
+		(<)	/less
+		(=)	/equal
+		(>)	/greater
+		(?)	/question
+		(@)	/at
+		(A)	/A
+		(B)	/B
+		(C)	/C
+		(D)	/D
+		(E)	/E
+		(F)	/F
+		(G)	/G
+		(H)	/H
+		(I)	/I
+		(J)	/J
+		(K)	/K
+		(L)	/L
+		(M)	/M
+		(N)	/N
+		(O)	/O
+		(P)	/P
+		(Q)	/Q
+		(R)	/R
+		(S)	/S
+		(T)	/T
+		(U)	/U
+		(V)	/V
+		(W)	/W
+		(X)	/X
+		(Y)	/Y
+		(Z)	/Z
+		([)	/bracketleft
+		(\\)	/backslash
+		(])	/bracketright
+		(^)	/asciicircum
+		(_)	/underscore
+		(`)	/quoteleft
+		(a)	/a
+		(b)	/b
+		(c)	/c
+		(d)	/d
+		(e)	/e
+		(f)	/f
+		(g)	/g
+		(h)	/h
+		(i)	/i
+		(j)	/j
+		(k)	/k
+		(l)	/l
+		(m)	/m
+		(n)	/n
+		(o)	/o
+		(p)	/p
+		(q)	/q
+		(r)	/r
+		(s)	/s
+		(t)	/t
+		(u)	/u
+		(v)	/v
+		(w)	/w
+		(x)	/x
+		(y)	/y
+		(z)	/z
+		({)	/braceleft
+		(|)	/bar
+		(})	/braceright
+		(~)	/asciitilde
+		(\\`)	/grave			% devpost character
+		(!!)	/exclamdown
+		(c|)	/cent
+		(L-)	/sterling
+		(xo)	/currency
+		(Y-)	/yen
+		(||)	/brokenbar		% missing
+		(so)	/section
+		("")	/dieresis
+		(co)	/copyright
+		(a_)	/ordfeminine
+		(<<)	/guillemotleft
+		(-,)	/logicalnot
+		(hy)	/hyphen
+		(ro)	/registered
+		(-^)	/macron
+		(0^)	/degree			% missing
+		(+-)	/plusminus		% missing
+		(2^)	/twosuperior		% missing
+		(3^)	/threesuperior		% missing
+		(\\')	/acute
+		(/u)	/mu			% missing
+		(P!)	/paragraph
+		(.^)	/periodcentered
+		(,,)	/cedilla
+		(1^)	/onesuperior		% missing
+		(o_)	/ordmasculine
+		(>>)	/guillemotright
+		(14)	/onequarter		% missing
+		(12)	/onehalf		% missing
+		(34)	/threequarters		% missing
+		(??)	/questiondown
+		(A`)	/Agrave
+		(A')	/Aacute
+		(A^)	/Acircumflex
+		(A~)	/Atilde
+		(A")	/Adieresis
+		(A*)	/Aring
+		(AE)	/AE
+		(C,)	/Ccedilla
+		(E`)	/Egrave
+		(E')	/Eacute
+		(E^)	/Ecircumflex
+		(E")	/Edieresis
+		(I`)	/Igrave
+		(I')	/Iacute
+		(I^)	/Icircumflex
+		(I")	/Idieresis
+		(D-)	/Eth			% missing
+		(N~)	/Ntilde
+		(O`)	/Ograve
+		(O')	/Oacute
+		(O^)	/Ocircumflex
+		(O~)	/Otilde
+		(O")	/Odieresis
+		(xx)	/multiply		% missing
+		(O/)	/Oslash
+		(U`)	/Ugrave
+		(U')	/Uacute
+		(U^)	/Ucircumflex
+		(U")	/Udieresis
+		(Y')	/Yacute			% missing
+		(TH)	/Thorn			% missing
+		(ss)	/germandbls
+		(a`)	/agrave
+		(a')	/aacute
+		(a^)	/acircumflex
+		(a~)	/atilde
+		(a")	/adieresis
+		(a*)	/aring
+		(ae)	/ae
+		(c,)	/ccedilla
+		(e`)	/egrave
+		(e')	/eacute
+		(e^)	/ecircumflex
+		(e")	/edieresis
+		(i`)	/igrave
+		(i')	/iacute
+		(i^)	/icircumflex
+		(i")	/idieresis
+		(d-)	/eth			% missing
+		(n~)	/ntilde
+		(o`)	/ograve
+		(o')	/oacute
+		(o^)	/ocircumflex
+		(o~)	/otilde
+		(o")	/odieresis
+		(-:)	/divide			% missing
+		(o/)	/oslash
+		(u`)	/ugrave
+		(u')	/uacute
+		(u^)	/ucircumflex
+		(u")	/udieresis
+		(y')	/yacute			% missing
+		(th)	/thorn			% missing
+		(y")	/ydieresis
+	//End of DingbatsCharset
+}
+
+SymbolCharset() {
+	cat <<-'//End of SymbolCharset'
+		(---)		/exclam
+		(fa)		/universal
+		(---)		/numbersign
+		(te)		/existential
+		(---)		/percent
+		(---)		/ampersand
+		(st)		/suchthat
+		(---)		/parenleft
+		(---)		/parenright
+		(**)		/asteriskmath
+		(pl)		/plus
+		(---)		/comma
+		(mi)		/minus
+		(---)		/period
+		(sl)		/slash
+		(---)		/zero
+		(---)		/one
+		(---)		/two
+		(---)		/three
+		(---)		/four
+		(---)		/five
+		(---)		/six
+		(---)		/seven
+		(---)		/eight
+		(---)		/nine
+		(---)		/colon
+		(---)		/semicolon
+		(<)		/less
+		(eq)		/equal
+		(>)		/greater
+		(---)		/question
+		(cg)		/congruent
+		(*A)		/Alpha
+		(\244x)		(")
+		(*B)		/Beta
+		(\244y)		(")
+		(*X)		/Chi
+		(\244\257)	(")
+		(*D)		/Delta
+		(\244{)		(")
+		(*E)		/Epsilon
+		(\244|)		(")
+		(*F)		/Phi
+		(\244\256)	(")
+		(*G)		/Gamma
+		(\244z)		(")
+		(*Y)		/Eta
+		(\244~)		(")
+		(*I)		/Iota
+		(\244\241)	(")
+		(---)		/theta1
+		(\244\331)	(")
+		(*K)		/Kappa
+		(\244\242)	(")
+		(*L)		/Lambda
+		(\244\243)	(")
+		(*M)		/Mu
+		(\244\244)	(")
+		(*N)		/Nu
+		(\244\245)	(")
+		(*O)		/Omicron
+		(\244\247)	(")
+		(*P)		/Pi
+		(\244\250)	(")
+		(*H)		/Theta
+		(\244\240)	(")
+		(*R)		/Rho
+		(\244\251)	(")
+		(*S)		/Sigma
+		(\244\253)	(")
+		(*T)		/Tau
+		(\244\254)	(")
+		(*U)		/Upsilon
+		(\244\255)	(")
+		(ts)		/sigma1
+		(\244\312)	(")
+		(*W)		/Omega
+		(\244\261)	(")
+		(*C)		/Xi
+		(\244\246)	(")
+		(*Q)		/Psi
+		(\244\260)	(")
+		(*Z)		/Zeta
+		(\244})		(")
+		(---)		/bracketleft
+		(tf)		/therefore
+		(---)		/bracketright
+		(pp)		/perpendicular
+		(ul)		/underscore
+		(_)		(")			% synonym
+		(rn)		/radicalex
+		(*a)		/alpha
+		(\244\271)	(")
+		(*b)		/beta
+		(\244\272)	(")
+		(*x)		/chi
+		(\244\317)	(")
+		(*d)		/delta
+		(\244\274)	(")
+		(*e)		/epsilon
+		(\244\275)	(")
+		(*f)		/phi
+		(\244\316)	(")
+		(*g)		/gamma
+		(\244\273)	(")
+		(*y)		/eta
+		(\244\277)	(")
+		(*i)		/iota
+		(\244\301)	(")
+		(---)		/phi1
+		(\244\335)	(")
+		(*k)		/kappa
+		(\244\302)	(")
+		(*l)		/lambda
+		(\244\303)	(")
+		(*m)		/mu
+		(\244\304)	(")
+		(*n)		/nu
+		(\244\305)	(")
+		(*o)		/omicron
+		(\244\307)	(")
+		(*p)		/pi
+		(\244\310)	(")
+		(*h)		/theta
+		(\244\300)	(")
+		(*r)		/rho
+		(\244\311)	(")
+		(*s)		/sigma
+		(\244\313)	(")
+		(*t)		/tau
+		(\244\314)	(")
+		(*u)		/upsilon
+		(\244\315)	(")
+		(---)		/omega1
+		(\244\336)	(")
+		(*w)		/omega
+		(\244\321)	(")
+		(*c)		/xi
+		(\244\306)	(")
+		(*q)		/psi
+		(\244\320)	(")
+		(*z)		/zeta
+		(\244\276)	(")
+		(---)		/braceleft
+		(or)		/bar
+		(---)		/braceright
+		(ap)		/similar
+		(---)		/Upsilon1
+		(fm)		/minute
+		(<=)		/lessequal
+		(fr)		/fraction		% devpost character
+		(if)		/infinity
+		(fn)		/florin			% devpost character
+		(---)		/club
+		(---)		/diamond
+		(---)		/heart
+		(---)		/spade
+		(ab)		/arrowboth
+		(<-)		/arrowleft
+		(ua)		/arrowup
+		(->)		/arrowright
+		(da)		/arrowdown
+		(de)		/degree
+		(+-)		/plusminus
+		(---)		/second
+		(>=)		/greaterequal
+		(mu)		/multiply
+		(pt)		/proportional
+		(pd)		/partialdiff
+		(bu)		/bullet
+		(di)		/divide
+		(!=)		/notequal
+		(==)		/equivalence
+		(~~)		/approxequal
+		(el)		/ellipsis
+		(av)		/arrowvertex
+		(ah)		/arrowhorizex
+		(CR)		/carriagereturn
+		(af)		/aleph
+		(If)		/Ifraktur
+		(Rf)		/Rfraktur
+		(ws)		/weierstrass
+		(Ox)		/circlemultiply
+		(O+)		/circleplus
+		(es)		/emptyset
+		(ca)		/intersection
+		(cu)		/union
+		(sp)		/propersuperset
+		(ip)		/reflexsuperset
+		(!b)		/notsubset
+		(sb)		/propersubset
+		(ib)		/reflexsubset
+		(mo)		/element
+		(!m)		/notelement
+		(an)		/angle
+		(gr)		/gradient
+		(rg)		/registerserif
+		(co)		/copyrightserif
+		(tm)		/trademarkserif
+		(---)		/product
+		(sr)		/radical
+		(c.)		/dotmath
+		(no)		/logicalnot
+		(l&)		/logicaland
+		(l|)		/logicalor
+		(---)		/arrowdblboth
+		(---)		/arrowdblleft
+		(---)		/arrowdblup
+		(---)		/arrowdblright
+		(---)		/arrowdbldown
+		(lz)		/lozenge
+		(b<)		/angleleft
+		(RG)		/registersans
+		(CO)		/copyrightsans
+		(TM)		/trademarksans
+		(---)		/summation
+		(LT)		/parenlefttp
+		(br)		/parenleftex
+		(LX)		(")			% synonym
+		(LB)		/parenleftbt
+		(lc)		/bracketlefttp
+		(lx)		/bracketleftex
+		(lf)		/bracketleftbt
+		(lt)		/bracelefttp
+		(lk)		/braceleftmid
+		(lb)		/braceleftbt
+		(bv)		/braceex
+		(|)		(")			% synonym
+		(b>)		/angleright
+		(is)		/integral
+		(---)		/integraltp
+		(---)		/integralex
+		(---)		/integralbt
+		(RT)		/parenrighttp
+		(RX)		/parenrightex
+		(RB)		/parenrightbt
+		(rc)		/bracketrighttp
+		(rx)		/bracketrightex
+		(rf)		/bracketrightbt
+		(rt)		/bracerighttp
+		(rk)		/bracerightmid
+		(rb)		/bracerightbt
+		(~=)		(55	0	1)	% charlib
+	//End of SymbolCharset
+}
+
+SpecialCharset() {
+	cat <<-'//End of SpecialCharset'
+		(ru)	/underscore
+		('')	/quotedblright		% devpost character
+		(``)	/quotedblleft		% devpost character
+		(dg)	/dagger			% devpost character
+		(dd)	/daggerdbl		% devpost character
+		(en)	/endash			% devpost character
+		(\\-)	(")			% synonym
+		(em)	/emdash
+%		(ff)	(60	2	1)	% charlib
+%		(Fi)	(84	2	1)	% charlib
+%		(Fl)	(84	2	1)	% charlib
+		(14)	(75	2	1)	% charlib
+		(12)	(75	2	1)	% charlib
+		(34)	(75	2	1)	% charlib
+		(bx)	(50	2	1)	% charlib
+		(ob)	(38	2	1)	% charlib
+		(ci)	(75	0	1)	% charlib
+		(sq)	(50	2	1)	% charlib
+		(Sl)	(50	2	1)	% charlib
+		(L1)	(110	1	1)	% charlib
+		(LA)	(110	1	1)	% charlib
+		(LV)	(110	3	1)	% charlib
+		(LH)	(210	1	1)	% charlib
+		(lh)	(100	0	1)	% charlib
+		(rh)	(100	0	1)	% charlib
+		(lH)	(100	0	1)	% charlib
+		(rH)	(100	0	1)	% charlib
+		(PC)	(220	2	1)	% charlib
+		(DG)	(185	2	1)	% charlib
+	//End of SpecialCharset
+}
+
+#
+# Latin1 ensures a font uses the ISOLatin1Encoding vector, although only
+# text fonts should be re-encoded. Downloading the Encoding vector doesn't
+# often make sense. No ISOLatin1Encoding array likely means ROM based fonts
+# on your printer are incomplete. Type 1 fonts with a full Latin1 character
+# set appeared sometime after Version 50.0.
+#
+
+Latin1() {
+	if [ "$DOWNLOADVECTOR" = TRUE ]; then
+		cat <<-'//End of ISOLatin1Encoding'
+			/ISOLatin1Encoding [
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/space
+				/exclam
+				/quotedbl
+				/numbersign
+				/dollar
+				/percent
+				/ampersand
+				/quoteright
+				/parenleft
+				/parenright
+				/asterisk
+				/plus
+				/comma
+				/minus
+				/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
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/.notdef
+				/dotlessi
+				/grave
+				/acute
+				/circumflex
+				/tilde
+				/macron
+				/breve
+				/dotaccent
+				/dieresis
+				/.notdef
+				/ring
+				/cedilla
+				/.notdef
+				/hungarumlaut
+				/ogonek
+				/caron
+				/space
+				/exclamdown
+				/cent
+				/sterling
+				/currency
+				/yen
+				/brokenbar
+				/section
+				/dieresis
+				/copyright
+				/ordfeminine
+				/guillemotleft
+				/logicalnot
+				/hyphen
+				/registered
+				/macron
+				/degree
+				/plusminus
+				/twosuperior
+				/threesuperior
+				/acute
+				/mu
+				/paragraph
+				/periodcentered
+				/cedilla
+				/onesuperior
+				/ordmasculine
+				/guillemotright
+				/onequarter
+				/onehalf
+				/threequarters
+				/questiondown
+				/Agrave
+				/Aacute
+				/Acircumflex
+				/Atilde
+				/Adieresis
+				/Aring
+				/AE
+				/Ccedilla
+				/Egrave
+				/Eacute
+				/Ecircumflex
+				/Edieresis
+				/Igrave
+				/Iacute
+				/Icircumflex
+				/Idieresis
+				/Eth
+				/Ntilde
+				/Ograve
+				/Oacute
+				/Ocircumflex
+				/Otilde
+				/Odieresis
+				/multiply
+				/Oslash
+				/Ugrave
+				/Uacute
+				/Ucircumflex
+				/Udieresis
+				/Yacute
+				/Thorn
+				/germandbls
+				/agrave
+				/aacute
+				/acircumflex
+				/atilde
+				/adieresis
+				/aring
+				/ae
+				/ccedilla
+				/egrave
+				/eacute
+				/ecircumflex
+				/edieresis
+				/igrave
+				/iacute
+				/icircumflex
+				/idieresis
+				/eth
+				/ntilde
+				/ograve
+				/oacute
+				/ocircumflex
+				/otilde
+				/odieresis
+				/divide
+				/oslash
+				/ugrave
+				/uacute
+				/ucircumflex
+				/udieresis
+				/yacute
+				/thorn
+				/ydieresis
+			] def
+		//End of ISOLatin1Encoding
+	fi
+
+	echo "ISOLatin1Encoding /$1 ReEncode"
+}
+
+#
+# Generating functions output PostScript programs that build font width
+# tables or a typesetter description file. Send the program to a printer
+# and the complete table will come back on the serial port. All write on
+# stdout and assume the prologue and other required PostScript files are
+# all available.
+#
+
+Proportional() {
+	echo "/unitwidth $UNITWIDTH def"
+	echo "/resolution $RESOLUTION def"
+	echo "/octalescapes $OCTALESCAPES def"
+	echo "/charset ["
+		# Get <>_ and | from S. Use accents for ascii ^ and ~.
+		StandardCharset | awk '
+			$1 == "(<)" && $2 == "/less" {$1 = "(---)"}
+			$1 == "(>)" && $2 == "/greater" {$1 = "(---)"}
+			$1 == "(_)" && $2 == "/underscore" {$1 = "(---)"}
+			$1 == "(|)" && $2 == "/bar" {$1 = "(---)"}
+			$1 == "(^)" && $2 == "/asciicircum" {
+				printf "(^)\t/circumflex\n"
+				$1 = "(---)"
+			}
+			$1 == "(~)" && $2 == "/asciitilde" {
+				printf "(~)\t/tilde\n"
+				$1 = "(---)"
+			}
+			{printf "%s\t%s\n", $1, $2}
+		'
+	echo "] def"
+
+	Latin1 $2
+	echo "/$2 SelectFont"
+	echo "(opO) SetAscender"
+
+	echo "(name $1\\\\n) Print"
+	echo "(fontname $2\\\\n) Print"
+	echo "/$1 NamedInPrologue"
+	echo "(spacewidth ) Print 32 GetWidth Print (\n) Print"
+	echo "(charset\\\\n) Print"
+	echo "BuildFontCharset"
+}
+
+FixedWidth() {
+	echo "/unitwidth $UNITWIDTH def"
+	echo "/resolution $RESOLUTION def"
+	echo "/octalescapes $OCTALESCAPES def"
+	echo "/charset ["
+		StandardCharset
+	echo "] def"
+
+	Latin1 $2
+	echo "/$2 SelectFont"
+	echo "(opO) SetAscender"
+
+	echo "(name $1\\\\n) Print"
+	echo "(fontname $2\\\\n) Print"
+	echo "/$1 NamedInPrologue"
+	echo "(spacewidth ) Print 32 GetWidth Print (\n) Print"
+	echo "(charset\\\\n) Print"
+	echo "BuildFontCharset"
+}
+
+Dingbats() {
+	echo "/unitwidth $UNITWIDTH def"
+	echo "/resolution $RESOLUTION def"
+	echo "/octalescapes $OCTALESCAPES def"
+	echo "/charset ["
+		DingbatsCharset | awk '$1 != "(---)" && $2 ~ /^\/[a-zA-Z]/ {
+			printf "%s\tISOLatin1Encoding %s GetCode\n", $1, $2
+		}'
+	echo "] def"
+
+	echo "/$2 SelectFont"
+	echo "(   ) SetAscender"
+
+	echo "(name $1\\\\n) Print"
+	echo "(fontname $2\\\\n) Print"
+	echo "/$1 NamedInPrologue"
+	echo "(charset\\\\n) Print"
+	echo "BuildFontCharset"
+}
+
+Greek() {
+	echo "/unitwidth $UNITWIDTH def"
+	echo "/resolution $RESOLUTION def"
+	echo "/charset ["
+		SymbolCharset | awk '
+			BEGIN {hit = -1}
+			$1 ~ /\(\*[a-zA-Z]\)/ {print; hit = NR}
+			$2 == "(\")" && hit == NR-1 {print; hit = NR}
+		'
+	echo "] def"
+
+	echo "/$2 SelectFont"
+	echo "(orO) SetAscender"
+
+	echo "(name $1\\\\n) Print"
+	echo "(fontname $2\\\\n) Print"
+	echo "/$1 NamedInPrologue"
+	echo "(spacewidth ) Print 32 GetWidth Print (\n) Print"
+	echo "(charset\\\\n) Print"
+	echo "BuildFontCharset"
+}
+
+Symbol() {
+	echo "/unitwidth $UNITWIDTH def"
+	echo "/resolution $RESOLUTION def"
+	echo "/charset ["
+		SymbolCharset
+	echo "] def"
+
+	echo "ChangeMetrics"
+	echo "/S SelectFont"
+	echo "(orO) SetAscender"
+
+	echo "(name $1\\\\n) Print"
+	echo "(fontname $2\\\\n) Print"
+	echo "/$1 NamedInPrologue"
+	echo "(special\\\\n) Print"
+	echo "(charset\\\\n) Print"
+	echo "BuildFontCharset"
+}
+
+Special() {
+	echo "/unitwidth $UNITWIDTH def"
+	echo "/resolution $RESOLUTION def"
+	echo "/charset ["
+		SpecialCharset
+	echo "] def"
+
+	echo "ChangeMetrics"
+	echo "/S1 SelectFont"
+
+	echo "(# Times-Roman special font\\\\n) Print"
+	echo "(name $1\\\\n) Print"
+	echo "(fontname $2\\\\n) Print"
+	echo "/$1 NamedInPrologue"
+	echo "(special\\\\n) Print"
+	echo "(charset\\\\n) Print"
+	echo "BuildFontCharset"
+}
+
+#
+# The DESC file doesn't have to be built on a printer. It's only here for
+# consistency.
+#
+
+Description() {
+	echo "/charset ["	# awk - so the stack doesn't overflow
+		StandardCharset | awk '$1 !~ /\(\\[0-9]/ {print $1}'
+		SymbolCharset | awk '$1 !~ /\(\\[0-9]/ {print $1}'
+		SpecialCharset | awk '$1 !~ /\(\\[0-9]/ {print $1}'
+	echo "] def"
+
+	cat <<-//DESC
+		(#Device Description - utf character set
+
+		PDL PostScript
+		Encoding Latin1
+
+		fonts 10 R I B BI CW H HI HB S1 S
+		sizes 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
+		23 24 25 26 27 28 29 30 31 32 33 34 35 36 38 40 42 44 46
+		48 50 52 54 56 58 60 64 68 72 78 84 90 96 100 105 110 115
+		120 125 130 135 140 145 150 155 160 0
+		res $RESOLUTION
+		hor 1
+		vert 1
+		unitwidth $UNITWIDTH
+
+		) Print
+	//DESC
+	echo "(charset\\\\n) Print"
+	echo "BuildDescCharset"
+	echo "(\\\\n) Print"
+}
+
diff --git a/src/cmd/postscript/tr2post/tr2post.c b/src/cmd/postscript/tr2post/tr2post.c
new file mode 100644
index 0000000..d60e0c7
--- /dev/null
+++ b/src/cmd/postscript/tr2post/tr2post.c
@@ -0,0 +1,218 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <stdio.h>
+#include "common.h"
+#include "tr2post.h"
+#include "comments.h"
+#include "path.h"
+
+int formsperpage = 1;
+int picflag = 1;
+double aspectratio = 1.0;
+int copies = 1;
+int landscape = 0;
+double magnification = 1.0;
+int linesperpage = 66;
+int pointsize = 10;
+double xoffset = .25;
+double yoffset = .25;
+char *passthrough = 0;
+
+Biobuf binp, *bstdout, bstderr;
+Biobuf *Bstdin, *Bstdout, *Bstderr;
+int debug = 0;
+
+char tmpfilename[MAXTOKENSIZE];
+char copybuf[BUFSIZ];
+
+
+struct charent **build_char_list = 0;
+int build_char_cnt = 0;
+
+void
+prologues(void) {
+	int i;
+	char charlibname[MAXTOKENSIZE];
+
+	Bprint(Bstdout, "%s", CONFORMING);
+	Bprint(Bstdout, "%s %s\n", VERSION, PROGRAMVERSION);
+	Bprint(Bstdout, "%s %s\n", DOCUMENTFONTS, ATEND);
+	Bprint(Bstdout, "%s %s\n", PAGES, ATEND);
+	Bprint(Bstdout, "%s", ENDCOMMENTS);
+
+	if (cat(unsharp(DPOST))) {
+		Bprint(Bstderr, "can't read %s\n", DPOST);
+		exits("dpost prologue");
+	}
+
+	if (drawflag) {
+		if (cat(unsharp(DRAW))) {
+			Bprint(Bstderr, "can't read %s\n", DRAW);
+			exits("draw prologue");
+		}
+	}
+
+	if (DOROUND)
+		cat(unsharp(ROUNDPAGE));
+
+	Bprint(Bstdout, "%s", ENDPROLOG);
+	Bprint(Bstdout, "%s", BEGINSETUP);
+	Bprint(Bstdout, "mark\n");
+	if (formsperpage > 1) {
+		Bprint(Bstdout, "%s %d\n", FORMSPERPAGE, formsperpage);
+		Bprint(Bstdout, "/formsperpage %d def\n", formsperpage);
+	}
+	if (aspectratio != 1) Bprint(Bstdout, "/aspectratio %g def\n", aspectratio);
+	if (copies != 1) Bprint(Bstdout, "/#copies %d store\n", copies);
+	if (landscape) Bprint(Bstdout, "/landscape true def\n");
+	if (magnification != 1) Bprint(Bstdout, "/magnification %g def\n", magnification);
+	if (pointsize != 10) Bprint(Bstdout, "/pointsize %d def\n", pointsize);
+	if (xoffset != .25) Bprint(Bstdout, "/xoffset %g def\n", xoffset);
+	if (yoffset != .25) Bprint(Bstdout, "/yoffset %g def\n", yoffset);
+	cat(unsharp(ENCODINGDIR"/Latin1.enc"));
+	if (passthrough != 0) Bprint(Bstdout, "%s\n", passthrough);
+
+	Bprint(Bstdout, "setup\n");
+	if (formsperpage > 1) {
+		cat(unsharp(FORMFILE));
+		Bprint(Bstdout, "%d setupforms \n", formsperpage);
+	}
+/* output Build character info from charlib if necessary. */
+
+	for (i=0; i<build_char_cnt; i++) {
+		sprint(charlibname, "%s/%s", CHARLIB, build_char_list[i]->name);
+		if (cat(unsharp(charlibname)))
+		Bprint(Bstderr, "cannot open %s\n", charlibname);
+	}
+
+	Bprint(Bstdout, "%s", ENDSETUP);
+}
+
+void
+cleanup(void) {
+	remove(tmpfilename);
+}
+
+main(int argc, char *argv[]) {
+	Biobuf *binp;
+	Biobuf *Binp;
+	int i, tot, ifd;
+	char *t;
+
+	programname = argv[0];
+	if (Binit(&bstderr, 2, OWRITE) == Beof) {
+		exits("Binit");
+	}
+	Bstderr = &bstderr;
+
+	tmpnam(tmpfilename);
+	if ((bstdout=Bopen(tmpfilename, OWRITE)) == 0) {
+		Bprint(Bstderr, "cannot open temporary file %s\n", tmpfilename);
+		exits("Bopen");
+	}
+	atexit(cleanup);
+	Bstdout = bstdout;
+	
+	ARGBEGIN{
+		case 'a':			/* aspect ratio */
+			aspectratio = atof(ARGF());
+			break;
+		case 'c':			/* copies */
+			copies = atoi(ARGF());
+			break;
+		case 'd':
+			debug = 1;
+			break;
+		case 'm':			/* magnification */
+			magnification = atof(ARGF());
+			break;
+		case 'n':			/* forms per page */
+			formsperpage = atoi(ARGF());
+			break;
+		case 'o':			/* output page list */
+			pagelist(ARGF());
+			break;
+		case 'p':			/* landscape or portrait mode */
+			if ( ARGF()[0] == 'l' )
+				landscape = 1;
+			else
+				landscape = 0;
+			break;
+		case 'x':			/* shift things horizontally */
+			xoffset = atof(ARGF());
+			break;
+		case 'y':			/* and vertically on the page */
+			yoffset = atof(ARGF());
+			break;
+		case 'P':			/* PostScript pass through */
+			t = ARGF();
+			i = strlen(t) + 1;
+			passthrough = malloc(i);
+			if (passthrough == 0) {
+				Bprint(Bstderr, "cannot allocate memory for argument string\n");
+				exits("malloc");
+			}
+			strncpy(passthrough, t, i);
+			break;
+		default:			/* don't know what to do for ch */
+			Bprint(Bstderr, "unknown option %C\n", ARGC());
+			break;
+	}ARGEND;
+	readDESC();
+	if (argc == 0) {
+		if ((binp = (Biobuf *)malloc(sizeof(Biobuf))) < (Biobuf *)0) {
+			Bprint(Bstderr, "malloc failed.\n");
+			exits("malloc");
+		}
+		if (Binit(binp, 0, OREAD) == Beof) {
+			Bprint(Bstderr, "Binit of <stdin> failed.\n");
+			exits("Binit");
+		}
+		Binp = binp;
+		if (debug) Bprint(Bstderr, "using standard input\n");
+		conv(Binp);
+		Bterm(Binp);
+	}
+	for (i=0; i<argc; i++) {
+		if ((binp=Bopen(argv[i], OREAD)) == 0) {
+			Bprint(Bstderr, "cannot open file %s\n", argv[i]);
+			continue;
+		}
+		Binp = binp;
+		inputfilename = argv[i];
+		conv(Binp);
+		Bterm(Binp);
+	}
+	Bterm(Bstdout);
+
+	if ((ifd=open(tmpfilename, OREAD)) < 0) {
+		Bprint(Bstderr, "open of %s failed.\n", tmpfilename);
+		exits("open");
+	}
+
+	bstdout = galloc(0, sizeof(Biobuf), "bstdout");
+	if (Binit(bstdout, 1, OWRITE) == Beof) {
+		Bprint(Bstderr, "Binit of <stdout> failed.\n");
+		exits("Binit");
+	}
+	Bstdout = bstdout;
+	prologues();
+	Bflush(Bstdout);
+	tot = 0; i = 0;
+	while ((i=read(ifd, copybuf, BUFSIZ)) > 0) {
+		if (write(1, copybuf, i) != i) {
+			Bprint(Bstderr, "write error on copying from temp file.\n");
+			exits("write");
+		}
+		tot += i;
+	}
+	if (debug) Bprint(Bstderr, "copied %d bytes to final output i=%d\n", tot, i);
+	if (i < 0) {
+		Bprint(Bstderr, "read error on copying from temp file.\n");
+		exits("read");
+	}
+	finish();
+		
+	exits("");
+}
diff --git a/src/cmd/postscript/tr2post/tr2post.h b/src/cmd/postscript/tr2post/tr2post.h
new file mode 100644
index 0000000..b07e0b1
--- /dev/null
+++ b/src/cmd/postscript/tr2post/tr2post.h
@@ -0,0 +1,103 @@
+#define MAXSPECHARS 	512
+#define MAXTOKENSIZE	128
+#define CHARLIB	"#9/sys/lib/troff/font/devutf/charlib"
+
+extern int debug;
+extern int fontsize;
+extern int fontpos;
+extern int resolution;	/* device resolution, goobies per inch */
+extern int minx;		/* minimum x motion */
+extern int miny;		/* minimum y motion */
+extern char devname[];
+extern int devres;
+extern int unitwidth;
+extern char *printdesclang;
+extern char *encoding;
+extern int fontmnt;
+extern char **fontmtab;
+
+extern int curtrofffontid;	/* index into trofftab of current troff font */
+extern int troffontcnt;
+
+extern BOOLEAN drawflag;
+
+struct specname {
+	char *str;
+	struct specname *next;
+};
+
+/* character entries for special characters (those pointed
+ * to by multiple character names, e.g. \(mu for multiply.
+ */
+struct charent {
+	char postfontid;	/* index into pfnamtab */
+	char postcharid;	/* e.g., 0x00 */
+	short troffcharwidth;
+	char *name;
+	struct charent *next;
+};
+
+extern struct charent **build_char_list;
+extern int build_char_cnt;
+
+struct pfnament {
+	char *str;
+	int used;
+};
+
+/* these entries map troff character code ranges to
+ * postscript font and character ranges.
+ */
+struct psfent {
+	int start;
+	int end;
+	int offset;
+	int psftid;
+};
+
+struct troffont {
+	char *trfontid;		/* the common troff font name e.g., `R' */
+	BOOLEAN special;	/* flag says this is a special font. */
+	int spacewidth;
+	int psfmapsize;
+	struct psfent *psfmap;
+	struct charent *charent[NUMOFONTS][FONTSIZE];
+};
+
+extern struct troffont *troffontab;
+extern struct charent spechars[];
+
+/** prototypes **/
+void initialize(void);
+void mountfont(int, char*);
+int findtfn(char *, int);
+void runeout(Rune);
+void specialout(char *);
+long nametorune(char *);
+void conv(Biobuf *);
+void hgoto(int);
+void vgoto(int);
+void hmot(int);
+void vmot(int);
+void draw(Biobuf *);
+void devcntl(Biobuf *);
+void notavail(char *);
+void error(int, char *, ...);
+void loadfont(int, char *);
+void flushtext(void);
+void t_charht(int);
+void t_slant(int);
+void startstring(void);
+void endstring(void);
+BOOLEAN pageon(void);
+void setpsfont(int, int);
+void settrfont(void);
+int hash(char *, int);
+BOOLEAN readDESC(void);
+void finish(void);
+void ps_include(Biobuf *, Biobuf *, int, int,
+	int, int, double, double, double, double,
+	double, double, double);
+void picture(Biobuf *, char *);
+void beginpath(char*, int);
+void drawpath(char*, int);
diff --git a/src/cmd/postscript/tr2post/utfmap b/src/cmd/postscript/tr2post/utfmap
new file mode 100644
index 0000000..2f9d6dc
--- /dev/null
+++ b/src/cmd/postscript/tr2post/utfmap
@@ -0,0 +1,47 @@
+¡	!!	¢	c$	£	l$	¤	g$
+¥	y$	¦	||	§	SS	¨	""
+©	cO	ª	sa	«	<<	¬	no
+­	--	®	rO	¯	__	°	de
+±	+-	²	s2	³	s3	´	''
+µ	mi	¶	pg	·	..	¸	,,
+¹	s1	º	s0	»	>>	¼	14
+½	12	¾	34	¿	??	À	`A
+Á	'A	Â	^A	Ã	~A	Ä	"A
+Å	oA	Æ	AE	Ç	,C	È	`E
+É	'E	Ê	^E	Ë	"E	Ì	`I
+Í	'I	Î	^I	Ï	"I	Ð	D-
+Ñ	~N	Ò	`O	Ó	'O	Ô	^O
+Õ	~O	Ö	"O	×	mu	Ø	/O
+Ù	`U	Ú	'U	Û	^U	Ü	"U
+Ý	'Y	Þ	|P	ß	ss	à	`a
+á	'a	â	^a	ã	~a	ä	"a
+å	oa	æ	ae	ç	,c	è	`e
+é	'e	ê	^e	ë	"e	ì	`i
+í	'i	î	^i	ï	"i	ð	d-
+ñ	~n	ò	`o	ó	'o	ô	^o
+õ	~o	ö	"o	÷	-:	ø	/o
+ù	`u	ú	'u	û	^u	ü	"u
+ý	'y	þ	|p	ÿ	"y	α	*a
+β	*b	γ	*g	δ	*d	ε	*e
+ζ	*z	η	*y	θ	*h	ι	*i
+κ	*k	λ	*l	*m	μ	ν	*n
+ξ	*c	ο	*o	π	*p	ρ	*r
+ς	ts	σ	*s	τ	*t	υ	*u
+φ	*f	χ	*x	ψ	*q	ω	*w
+Α	*A	Β	*B	Γ	*G	Δ	*D
+Ε	*E	Ζ	*Z	Η	*Y	Θ	*H
+Ι	*I	Κ	*K	Λ	*L	Μ	*M
+Ν	*N	Ξ	*C	Ο	*O	Π	*P
+Ρ	*R	Σ	*S	Τ	*T	Υ	*U
+Φ	*F	Χ	*X	Ψ	*Q	Ω	*W
+←	<-	↑	ua	→	->	↓	da
+↔	ab	∀	fa	∃	te	∂	pd
+∅	es	∆	*D	∇	gr	∉	!m
+∍	st	∗	**	∙	bu	√	sr
+∝	pt	∞	if	∠	an	∧	l&
+∨	l|	∩	ca	∪	cu	∫	is
+∴	tf	≃	~=	≅	cg	≈	~~
+≠	!=	≡	==	≦	<=	≧	>=
+⊂	sb	⊃	sp	⊄	!b	⊆	ib
+⊇	ip	⊕	O+	⊖	O-	⊗	Ox
+⊢	tu	⊨	Tu	⋄	lz	⋯	el
diff --git a/src/cmd/postscript/tr2post/utils.c b/src/cmd/postscript/tr2post/utils.c
new file mode 100644
index 0000000..8f58ea4
--- /dev/null
+++ b/src/cmd/postscript/tr2post/utils.c
@@ -0,0 +1,264 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "../common/common.h"
+#include "tr2post.h"
+
+int hpos = 0, vpos = 0;
+int fontsize, fontpos;
+
+#define MAXSTR	128
+int trindex;			/* index into trofftab of current troff font */
+static int expecthmot = 0;
+
+void
+initialize(void) {
+}
+
+void
+hgoto(int x) {
+	hpos = x;
+	if (pageon()) {
+		endstring();
+/*		Bprint(Bstdout, "%d %d m\n", hpos, vpos); */
+	}
+}
+
+void
+vgoto(int y) {
+	vpos = y;
+	if (pageon()) {
+		endstring();
+/*		Bprint(Bstdout, "%d %d m\n", hpos, vpos); */
+	}
+}
+
+void
+hmot(int x) {
+	int delta;
+
+	if ((x<expecthmot-1) || (x>expecthmot+1)) {
+		delta = x - expecthmot;
+		if (curtrofffontid <0 || curtrofffontid >= troffontcnt) {
+			Bprint(Bstderr, "troffontcnt=%d curtrofffontid=%d\n", troffontcnt, curtrofffontid);
+			Bflush(Bstderr);
+			exits("");
+		}
+		if (delta == troffontab[curtrofffontid].spacewidth*fontsize/10 && isinstring()) {
+			if (pageon()) runeout(' ');
+		} else {
+			if (pageon()) {	
+				endstring();
+				/* Bprint(Bstdout, " %d 0 rmoveto ", delta); */
+/*				Bprint(Bstdout, " %d %d m ", hpos+x, vpos); */
+				if (debug) Bprint(Bstderr, "x=%d expecthmot=%d\n", x, expecthmot);
+			}
+		}
+	}
+	hpos += x;
+	expecthmot = 0;
+}
+
+void
+vmot(int y) {
+	endstring();
+/*	Bprint(Bstdout, " 0 %d rmoveto ", -y); */
+	vpos += y;
+}
+
+struct charent **
+findglyph(int trfid, Rune rune, char *stoken) {
+	struct charent **cp;
+
+	for (cp = &(troffontab[trfid].charent[RUNEGETGROUP(rune)][RUNEGETCHAR(rune)]); *cp != 0; cp = &((*cp)->next)) {
+		if ((*cp)->name) {
+			if (debug) Bprint(Bstderr, "looking for <%s>, have <%s> in font %s\n", stoken, (*cp)->name, troffontab[trfid].trfontid);
+			if (strcmp((*cp)->name, stoken) == 0)
+				break;
+		}
+	}
+	return(cp);
+}
+
+/* output glyph.  Use first rune to look up character (hash)
+ * then use stoken UTF string to find correct glyph in linked
+ * list of glyphs in bucket.
+ */
+void
+glyphout(Rune rune, char *stoken, BOOLEAN specialflag) {
+	struct charent **cp;
+	struct troffont *tfp;
+	struct psfent *psfp;
+	int i, t;
+	int fontid;	/* this is the troff font table index, not the mounted font table index */
+	int mi, fi, wid;
+	Rune r;
+
+	settrfont();
+
+	/* check current font for the character, special or not */
+	fontid = curtrofffontid;
+if (debug) fprint(2, "	looking through current font: trying %s\n", troffontab[fontid].trfontid);
+	cp = findglyph(fontid, rune, stoken);
+	if (*cp != 0) goto foundit;
+
+	if (specialflag) {
+		if (expecthmot) hmot(0);
+
+		/* check special fonts for the special character */
+		/* cycle through the (troff) mounted fonts starting at the next font */
+		for (mi=0; mi<fontmnt; mi++) {
+			if (troffontab[fontid].trfontid==0) error(WARNING, "glyphout:troffontab[%d].trfontid=0x%x, botch!\n",
+				fontid, troffontab[fontid].trfontid);
+			if (fontmtab[mi]==0) {
+				if (debug) fprint(2, "fontmtab[%d]=0x%x, fontmnt=%d\n", mi, fontmtab[mi], fontmnt);
+				continue;
+			}
+			if (strcmp(troffontab[fontid].trfontid, fontmtab[mi])==0) break;
+		}
+		if (mi==fontmnt) error(FATAL, "current troff font is not mounted, botch!\n");
+		for (i=(mi+1)%fontmnt; i!=mi; i=(i+1)%fontmnt) {
+			if (fontmtab[i]==0) {
+				if (debug) fprint(2, "fontmtab[%d]=0x%x, fontmnt=%d\n", i, fontmtab[i], fontmnt);
+				continue;
+			}
+			fontid = findtfn(fontmtab[i], TRUE);
+if (debug) fprint(2, "	looking through special fonts: trying %s\n", troffontab[fontid].trfontid);
+			if (troffontab[fontid].special) {
+				cp = findglyph(fontid, rune, stoken);
+				if (*cp != 0) goto foundit;
+			}
+		}
+
+		/* check font 1 (if current font is not font 1) for the special character */
+		if (mi != 1) {
+				fontid = findtfn(fontmtab[1], TRUE);;
+if (debug) fprint(2, "	looking through font at position 1: trying %s\n", troffontab[fontid].trfontid);
+				cp = findglyph(fontid, rune, stoken);
+				if (*cp != 0) goto foundit;
+		}
+	}
+
+	if (*cp == 0) {
+		error(WARNING, "cannot find glyph, rune=0x%x stoken=<%s> troff font %s\n", rune, stoken,
+			troffontab[curtrofffontid].trfontid);
+		expecthmot = 0;
+	}
+
+	/* use the peter face in lieu of the character that we couldn't find */
+	rune = 'p';	stoken = "pw";
+	for (i=(mi+1)%fontmnt; i!=mi; i=(i+1)%fontmnt) {
+		if (fontmtab[i]==0) {
+			if (debug) fprint(2, "fontmtab[%d]=0x%x\n", i, fontmtab[i]);
+			continue;
+		}
+		fontid = findtfn(fontmtab[i], TRUE);
+if (debug) fprint(2, "	looking through special fonts: trying %s\n", troffontab[fontid].trfontid);
+		if (troffontab[fontid].special) {
+			cp = findglyph(fontid, rune, stoken);
+			if (*cp != 0) goto foundit;
+		}
+	}
+	
+	if (*cp == 0) {
+		error(WARNING, "cannot find glyph, rune=0x%x stoken=<%s> troff font %s\n", rune, stoken,
+			troffontab[curtrofffontid].trfontid);
+		expecthmot = 0;
+		return;
+	}
+
+foundit:
+	t = (((*cp)->postfontid&0xff)<<8) | ((*cp)->postcharid&0xff);
+	if (debug) {
+		Bprint(Bstderr, "runeout(0x%x)<%C> postfontid=0x%x postcharid=0x%x troffcharwidth=%d\n",
+			rune, rune, (*cp)->postfontid, (*cp)->postcharid, (*cp)->troffcharwidth);
+	}
+		
+	tfp = &(troffontab[fontid]);
+	for (i=0; i<tfp->psfmapsize; i++) {
+		psfp = &(tfp->psfmap[i]);
+		if(t>=psfp->start && t<=psfp->end) break;
+	}
+	if (i >= tfp->psfmapsize)
+		error(FATAL, "character <0x%x> does not have a Postscript font defined.\n", rune);
+
+	setpsfont(psfp->psftid, fontsize);
+
+	if (t == 0x0001) {	/* character is in charlib */
+		endstring();
+		if (pageon()) {
+			struct charent *tcp;
+
+			Bprint(Bstdout, "%d %d m ", hpos, vpos);
+			/* if char is unicode character rather than name, clean up for postscript */
+			wid = chartorune(&r, (*cp)->name);
+			if(' '<r && r<0x7F)
+				Bprint(Bstdout, "%d build_%s\n", (*cp)->troffcharwidth, (*cp)->name);
+			else{
+				if((*cp)->name[wid] != 0)
+					error(FATAL, "character <%s> badly named\n", (*cp)->name);
+				Bprint(Bstdout, "%d build_X%.4x\n", (*cp)->troffcharwidth, r);
+			}
+
+			/* stash charent pointer in a list so that we can print these character definitions
+			 * in the prologue.
+			 */
+			for (i=0; i<build_char_cnt; i++)
+				if (*cp == build_char_list[i]) break;
+			if (i == build_char_cnt) {
+				build_char_list = galloc(build_char_list, sizeof(struct charent *) * ++build_char_cnt,
+				"build_char_list");
+				build_char_list[build_char_cnt-1] = *cp;
+			}
+		}
+		expecthmot = (*cp)->troffcharwidth * fontsize / unitwidth;
+	} else if (isinstring() || rune != ' ') {
+		startstring();
+		if (pageon()) {
+			if (rune == ' ')
+				Bprint(Bstdout, " ");
+			else
+				Bprint(Bstdout, "%s", charcode[RUNEGETCHAR(t)].str);
+		}
+		expecthmot = (*cp)->troffcharwidth * fontsize / unitwidth;
+	}
+}
+
+/* runeout puts a symbol into a string (queue) to be output.
+ * It also has to keep track of the current and last symbol
+ * output to check that the spacing is correct by default
+ * or needs to be adjusted with a spacing operation.
+ */
+
+void
+runeout(Rune rune) {
+	char stoken[UTFmax+1];
+	int i;
+
+	i = runetochar(stoken, &rune);
+	stoken[i] = '\0';
+	glyphout(rune, stoken, TRUE);
+}
+
+void
+specialout(char *stoken) {
+	Rune rune;
+	int i;
+
+	i = chartorune(&rune, stoken);
+	glyphout(rune, stoken, TRUE);
+}
+
+void
+graphfunc(Biobuf *bp) {
+}
+
+long
+nametorune(char *name) {
+	return(0);
+}
+
+void
+notavail(char *msg) {
+	Bprint(Bstderr, "%s is not available at this time.\n", msg);
+}