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;
+}