placeholder; does not yet build
diff --git a/src/cmd/page/ps.c b/src/cmd/page/ps.c
new file mode 100644
index 0000000..46ad5cd
--- /dev/null
+++ b/src/cmd/page/ps.c
@@ -0,0 +1,450 @@
+/*
+ * ps.c
+ * 
+ * provide postscript file reading support for page
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <bio.h>
+#include <ctype.h>
+#include "page.h"
+
+typedef struct PSInfo	PSInfo;
+typedef struct Page	Page;
+	
+struct Page {
+	char *name;
+	int offset;			/* offset of page beginning within file */
+};
+
+struct PSInfo {
+	GSInfo gs;
+	Rectangle bbox;	/* default bounding box */
+	Page *page;
+	int npage;
+	int clueless;	/* don't know where page boundaries are */
+	long psoff;	/* location of %! in file */
+	char ctm[256];
+};
+
+static int	pswritepage(Document *d, int fd, int page);
+static Image*	psdrawpage(Document *d, int page);
+static char*	pspagename(Document*, int);
+
+#define R(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
+Rectangle
+rdbbox(char *p)
+{
+	Rectangle r;
+	int a;
+	char *f[4];
+	while(*p == ':' || *p == ' ' || *p == '\t')
+		p++;
+	if(tokenize(p, f, 4) != 4)
+		return Rect(0,0,0,0);
+	r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
+	r = canonrect(r);
+	if(Dx(r) <= 0 || Dy(r) <= 0)
+		return Rect(0,0,0,0);
+
+	if(truetoboundingbox)
+		return r;
+
+	/* initdraw not called yet, can't use %R */
+	if(chatty) fprint(2, "[%d %d %d %d] -> ", R(r));
+	/*
+	 * attempt to sniff out A4, 8½×11, others
+	 * A4 is 596×842
+	 * 8½×11 is 612×792
+	 */
+
+	a = Dx(r)*Dy(r);
+	if(a < 300*300){	/* really small, probably supposed to be */
+		/* empty */
+	} else if(Dx(r) <= 596 && r.max.x <= 596 && Dy(r) > 792 && Dy(r) <= 842 && r.max.y <= 842)	/* A4 */
+		r = Rect(0, 0, 596, 842);
+	else {	/* cast up to 8½×11 */
+		if(Dx(r) <= 612 && r.max.x <= 612){
+			r.min.x = 0;
+			r.max.x = 612;
+		}
+		if(Dy(r) <= 792 && r.max.y <= 792){
+			r.min.y = 0;
+			r.max.y = 792;
+		}
+	}
+	if(chatty) fprint(2, "[%d %d %d %d]\n", R(r));
+	return r;
+}
+
+#define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y
+
+int
+prefix(char *x, char *y)
+{
+	return strncmp(x, y, strlen(y)) == 0;
+}
+
+/*
+ * document ps is really being printed as n-up pages.
+ * we need to treat every n pages as 1.
+ */
+void
+repaginate(PSInfo *ps, int n)
+{
+	int i, np, onp;
+	Page *page;
+
+	page = ps->page;
+	onp = ps->npage;
+	np = (ps->npage+n-1)/n;
+
+	if(chatty) {
+		for(i=0; i<=onp+1; i++)
+			print("page %d: %d\n", i, page[i].offset);
+	}
+
+	for(i=0; i<np; i++)
+		page[i] = page[n*i];
+
+	/* trailer */
+	page[np] = page[onp];
+
+	/* EOF */
+	page[np+1] = page[onp+1];
+
+	ps->npage = np;
+
+	if(chatty) {
+		for(i=0; i<=np+1; i++)
+			print("page %d: %d\n", i, page[i].offset);
+	}
+
+}
+
+Document*
+initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
+{
+	Document *d;
+	PSInfo *ps;
+	char *p;
+	char *q, *r;
+	char eol;
+	char *nargv[1];
+	char fdbuf[20];
+	char tmp[32];
+	int fd;
+	int i;
+	int incomments;
+	int cantranslate;
+	int trailer=0;
+	int nesting=0;
+	int dumb=0;
+	int landscape=0;
+	long psoff;
+	long npage, mpage;
+	Page *page;
+	Rectangle bbox = Rect(0,0,0,0);
+
+	if(argc > 1) {
+		fprint(2, "can only view one ps file at a time\n");
+		return nil;
+	}
+
+	fprint(2, "reading through postscript...\n");
+	if(b == nil){	/* standard input; spool to disk (ouch) */
+		fd = spooltodisk(buf, nbuf, nil);
+		sprint(fdbuf, "/fd/%d", fd);
+		b = Bopen(fdbuf, OREAD);
+		if(b == nil){
+			fprint(2, "cannot open disk spool file\n");
+			wexits("Bopen temp");
+		}
+		nargv[0] = fdbuf;
+		argv = nargv;
+	}
+
+	/* find %!, perhaps after PCL nonsense */
+	Bseek(b, 0, 0);
+	psoff = 0;
+	eol = 0;
+	for(i=0; i<16; i++){
+		psoff = Boffset(b);
+		if(!(p = Brdline(b, eol='\n')) && !(p = Brdline(b, eol='\r'))) {
+			fprint(2, "cannot find end of first line\n");
+			wexits("initps");
+		}
+		if(p[0]=='\x1B')
+			p++, psoff++;
+		if(p[0] == '%' && p[1] == '!')
+			break;
+	}
+	if(i == 16){
+		werrstr("not ps");
+		return nil;
+	}
+
+	/* page counting */
+	npage = 0;
+	mpage = 16;
+	page = emalloc(mpage*sizeof(*page));
+	memset(page, 0, mpage*sizeof(*page));
+
+	cantranslate = goodps;
+	incomments = 1;
+Keepreading:
+	while(p = Brdline(b, eol)) {
+		if(p[0] == '%')
+			if(chatty) fprint(2, "ps %.*s\n", utfnlen(p, Blinelen(b)-1), p);
+		if(npage == mpage) {
+			mpage *= 2;
+			page = erealloc(page, mpage*sizeof(*page));
+			memset(&page[npage], 0, npage*sizeof(*page));
+		}
+
+		if(p[0] != '%' || p[1] != '%')
+			continue;
+
+		if(prefix(p, "%%BeginDocument")) {
+			nesting++;
+			continue;
+		}
+		if(nesting > 0 && prefix(p, "%%EndDocument")) {
+			nesting--;
+			continue;
+		}
+		if(nesting)
+			continue;
+
+		if(prefix(p, "%%EndComment")) {
+			incomments = 0;
+			continue;
+		}
+		if(reverse == -1 && prefix(p, "%%PageOrder")) {
+			/* glean whether we should reverse the viewing order */
+			p[Blinelen(b)-1] = 0;
+			if(strstr(p, "Ascend"))
+				reverse = 0;
+			else if(strstr(p, "Descend"))
+				reverse = 1;
+			else if(strstr(p, "Special"))
+				dumb = 1;
+			p[Blinelen(b)-1] = '\n';
+			continue;
+		} else if(prefix(p, "%%Trailer")) {
+			incomments = 1;
+			page[npage].offset = Boffset(b)-Blinelen(b);
+			trailer = 1;
+			continue;
+		} else if(incomments && prefix(p, "%%Orientation")) {
+			if(strstr(p, "Landscape"))
+				landscape = 1;
+		} else if(incomments && Dx(bbox)==0 && prefix(p, q="%%BoundingBox")) {
+			bbox = rdbbox(p+strlen(q)+1);
+			if(chatty)
+				/* can't use %R because haven't initdraw() */
+				fprint(2, "document bbox [%d %d %d %d]\n",
+					RECT(bbox));
+			continue;
+		}
+
+		/*
+		 * If they use the initgraphics command, we can't play our translation tricks.
+		 */
+		p[Blinelen(b)-1] = 0;
+		if((q=strstr(p, "initgraphics")) && ((r=strchr(p, '%'))==nil || r > q))
+			cantranslate = 0;
+		p[Blinelen(b)-1] = eol;
+
+		if(!prefix(p, "%%Page:"))
+			continue;
+
+		/* 
+		 * figure out of the %%Page: line contains a page number
+		 * or some other page description to use in the menu bar.
+		 * 
+		 * lines look like %%Page: x y or %%Page: x
+		 * we prefer just x, and will generate our
+		 * own if necessary.
+		 */
+		p[Blinelen(b)-1] = 0;
+		if(chatty) fprint(2, "page %s\n", p);
+		r = p+7;
+		while(*r == ' ' || *r == '\t')
+			r++;
+		q = r;
+		while(*q && *q != ' ' && *q != '\t')
+			q++;
+		free(page[npage].name);
+		if(*r) {
+			if(*r == '"' && *q == '"')
+				r++, q--;
+			if(*q)
+				*q = 0;
+			page[npage].name = estrdup(r);
+			*q = 'x';
+		} else {
+			snprint(tmp, sizeof tmp, "p %ld", npage+1);
+			page[npage].name = estrdup(tmp);
+		}
+
+		/*
+		 * store the offset info for later viewing
+		 */
+		trailer = 0;
+		p[Blinelen(b)-1] = eol;
+		page[npage++].offset = Boffset(b)-Blinelen(b);
+	}
+	if(Blinelen(b) > 0){
+		fprint(2, "page: linelen %d\n", Blinelen(b));
+		Bseek(b, Blinelen(b), 1);
+		goto Keepreading;
+	}
+
+	if(Dx(bbox) == 0 || Dy(bbox) == 0)
+		bbox = Rect(0,0,612,792);	/* 8½×11 */
+	/*
+	 * if we didn't find any pages, assume the document
+	 * is one big page
+	 */
+	if(npage == 0) {
+		dumb = 1;
+		if(chatty) fprint(2, "don't know where pages are\n");
+		reverse = 0;
+		goodps = 0;
+		trailer = 0;
+		page[npage].name = "p 1";
+		page[npage++].offset = 0;
+	}
+
+	if(npage+2 > mpage) {
+		mpage += 2;
+		page = erealloc(page, mpage*sizeof(*page));
+		memset(&page[mpage-2], 0, 2*sizeof(*page));
+	}
+
+	if(!trailer)
+		page[npage].offset = Boffset(b);
+
+	Bseek(b, 0, 2); /* EOF */
+	page[npage+1].offset = Boffset(b);
+
+	d = emalloc(sizeof(*d));
+	ps = emalloc(sizeof(*ps));
+	ps->page = page;
+	ps->npage = npage;
+	ps->bbox = bbox;
+	ps->psoff = psoff;
+
+	d->extra = ps;
+	d->npage = ps->npage;
+	d->b = b;
+	d->drawpage = psdrawpage;
+	d->pagename = pspagename;
+
+	d->fwdonly = ps->clueless = dumb;
+	d->docname = argv[0];
+
+	if(spawngs(&ps->gs) < 0)
+		return nil;
+
+	if(!cantranslate)
+		bbox.min = ZP;
+	setdim(&ps->gs, bbox, ppi, landscape);
+
+	if(goodps){
+		/*
+		 * We want to only send the page (i.e. not header and trailer) information
+	 	 * for each page, so initialize the device by sending the header now.
+		 */
+		pswritepage(d, ps->gs.gsfd, -1);
+		waitgs(&ps->gs);
+	}
+
+	if(dumb) {
+		fprint(ps->gs.gsfd, "(%s) run\n", argv[0]);
+		fprint(ps->gs.gsfd, "(/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n");
+	}
+
+	ps->bbox = bbox;
+
+	return d;
+}
+
+static int
+pswritepage(Document *d, int fd, int page)
+{
+	Biobuf *b = d->b;
+	PSInfo *ps = d->extra;
+	int t, n, i;
+	long begin, end;
+	char buf[8192];
+
+	if(page == -1)
+		begin = ps->psoff;
+	else
+		begin = ps->page[page].offset;
+
+	end = ps->page[page+1].offset;
+
+	if(chatty) {
+		fprint(2, "writepage(%d)... from #%ld to #%ld...\n",
+			page, begin, end);
+	}
+	Bseek(b, begin, 0);
+
+	t = end-begin;
+	n = sizeof(buf);
+	if(n > t) n = t;
+	while(t > 0 && (i=Bread(b, buf, n)) > 0) {
+		if(write(fd, buf, i) != i)
+			return -1;
+		t -= i;
+		if(n > t)
+			n = t;
+	}
+	return end-begin;
+}
+
+static Image*
+psdrawpage(Document *d, int page)
+{
+	PSInfo *ps = d->extra;
+	Image *im;
+
+	if(ps->clueless)
+		return readimage(display, ps->gs.gsdfd, 0);
+
+	waitgs(&ps->gs);
+
+	if(goodps)
+		pswritepage(d, ps->gs.gsfd, page);
+	else {
+		pswritepage(d, ps->gs.gsfd, -1);
+		pswritepage(d, ps->gs.gsfd, page);
+		pswritepage(d, ps->gs.gsfd, d->npage);
+	}
+	/*
+	 * If last line terminator is \r, gs will read ahead to check for \n
+	 * so send one to avoid deadlock.
+	 */
+	write(ps->gs.gsfd, "\n", 1);
+	im = readimage(display, ps->gs.gsdfd, 0);
+	if(im == nil) {
+		fprint(2, "fatal: readimage error %r\n");
+		wexits("readimage");
+	}
+	waitgs(&ps->gs);
+
+	return im;
+}
+
+static char*
+pspagename(Document *d, int page)
+{
+	PSInfo *ps = (PSInfo *) d->extra;
+	return ps->page[page].name;
+}