tplaceholder; does not yet build - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 24c02865d8fcc97d1fb5cb9281810d8074aa5eb1
 (DIR) parent d1e9002f81f14fbfef1ebc4261edccd9eb97b72c
 (HTM) Author: rsc <devnull@localhost>
       Date:   Tue,  4 Jan 2005 21:23:50 +0000
       
       placeholder; does not yet build
       
       Diffstat:
         A src/cmd/page/filter.c               |     107 +++++++++++++++++++++++++++++++
         A src/cmd/page/gfx.c                  |     331 +++++++++++++++++++++++++++++++
         A src/cmd/page/gs.c                   |     342 +++++++++++++++++++++++++++++++
         A src/cmd/page/mkfile                 |      23 +++++++++++++++++++++++
         A src/cmd/page/nrotate.c              |     277 +++++++++++++++++++++++++++++++
         A src/cmd/page/page.c                 |     236 +++++++++++++++++++++++++++++++
         A src/cmd/page/page.h                 |      84 +++++++++++++++++++++++++++++++
         A src/cmd/page/pdf.c                  |     155 +++++++++++++++++++++++++++++++
         A src/cmd/page/pdfprolog.c            |      29 +++++++++++++++++++++++++++++
         A src/cmd/page/ps.c                   |     450 +++++++++++++++++++++++++++++++
         A src/cmd/page/rotate.c               |     474 +++++++++++++++++++++++++++++++
         A src/cmd/page/util.c                 |     131 +++++++++++++++++++++++++++++++
         A src/cmd/page/view.c                 |    1022 +++++++++++++++++++++++++++++++
       
       13 files changed, 3661 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/page/filter.c b/src/cmd/page/filter.c
       t@@ -0,0 +1,107 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <event.h>
       +#include <bio.h>
       +#include "page.h"
       +
       +Document*
       +initfilt(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf, char *type, char *cmd, int docopy)
       +{
       +        int ofd;
       +        int p[2];
       +        char xbuf[8192];
       +        int n;
       +
       +        if(argc > 1) {
       +                fprint(2, "can only view one %s file at a time\n", type);
       +                return nil;
       +        }
       +
       +        fprint(2, "converting from %s to postscript...\n", type);
       +
       +        if(docopy){
       +                if(pipe(p) < 0){
       +                        fprint(2, "pipe fails: %r\n");
       +                        exits("Epipe");
       +                }
       +        }else{
       +                p[0] = open("/dev/null", ORDWR);
       +                p[1] = open("/dev/null", ORDWR);
       +        }
       +
       +        ofd = opentemp("/tmp/pagecvtXXXXXXXXX");
       +        switch(fork()){
       +        case -1:
       +                fprint(2, "fork fails: %r\n");
       +                exits("Efork");
       +        default:
       +                close(p[1]);
       +                if(docopy){
       +                        write(p[0], buf, nbuf);
       +                        if(b)
       +                                while((n = Bread(b, xbuf, sizeof xbuf)) > 0)
       +                                        write(p[0], xbuf, n);
       +                        else
       +                                while((n = read(stdinfd, xbuf, sizeof xbuf)) > 0)
       +                                        write(p[0], xbuf, n);
       +                }
       +                close(p[0]);
       +                waitpid();
       +                break;
       +        case 0:
       +                close(p[0]);
       +                dup(p[1], 0);
       +                dup(ofd, 1);
       +                /* stderr shines through */
       +                execl("/bin/rc", "rc", "-c", cmd, nil);
       +                break;
       +        }
       +
       +        if(b)
       +                Bterm(b);
       +        seek(ofd, 0, 0);
       +        b = emalloc(sizeof(Biobuf));
       +        Binit(b, ofd, OREAD);
       +
       +        return initps(b, argc, argv, nil, 0);
       +}
       +
       +Document*
       +initdvi(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
       +{
       +        int fd;
       +        char *name;
       +        char cmd[256];
       +        char fdbuf[20];
       +
       +        /*
       +         * Stupid DVIPS won't take standard input.
       +         */
       +        if(b == nil){        /* standard input; spool to disk (ouch) */
       +                fd = spooltodisk(buf, nbuf, &name);
       +                sprint(fdbuf, "/fd/%d", fd);
       +                b = Bopen(fdbuf, OREAD);
       +                if(b == nil){
       +                        fprint(2, "cannot open disk spool file\n");
       +                        wexits("Bopen temp");
       +                }
       +                argv = &name;
       +                argc = 1;
       +        }
       +
       +        snprint(cmd, sizeof cmd, "dvips -Pps -r0 -q1 -f1 '%s'", argv[0]);
       +        return initfilt(b, argc, argv, buf, nbuf, "dvi", cmd, 0);
       +}
       +
       +Document*
       +inittroff(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
       +{
       +        return initfilt(b, argc, argv, buf, nbuf, "troff", "lp -dstdout", 1);
       +}
       +
       +Document*
       +initmsdoc(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
       +{
       +        return initfilt(b, argc, argv, buf, nbuf, "microsoft office", "doc2ps", 1);
       +}
 (DIR) diff --git a/src/cmd/page/gfx.c b/src/cmd/page/gfx.c
       t@@ -0,0 +1,331 @@
       +/*
       + * graphics file reading for page
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <event.h>
       +#include <bio.h>
       +#include "page.h"
       +
       +typedef struct Convert        Convert;
       +typedef struct GfxInfo        GfxInfo;
       +typedef struct Graphic        Graphic;
       +
       +struct Convert {
       +        char *name;
       +        char *cmd;
       +        char *truecmd;        /* cmd for true color */
       +};
       +
       +struct GfxInfo {
       +        Graphic *g;
       +};
       +
       +struct Graphic {
       +        int type;
       +        char *name;
       +        uchar *buf;        /* if stdin */
       +        int nbuf;
       +};
       +
       +enum {
       +        Ipic,
       +        Itiff,
       +        Ijpeg,
       +        Igif,
       +        Iinferno,
       +        Ifax,
       +        Icvt2pic,
       +        Iplan9bm,
       +        Iccittg4,
       +        Ippm,
       +        Ipng,
       +        Iyuv,
       +        Ibmp,
       +};
       +
       +/*
       + * N.B. These commands need to read stdin if %a is replaced
       + * with an empty string.
       + */
       +Convert cvt[] = {
       +[Ipic]                { "plan9",        "fb/3to1 rgbv %a |fb/pcp -tplan9" },
       +[Itiff]                { "tiff",        "fb/tiff2pic %a | fb/3to1 rgbv | fb/pcp -tplan9" },
       +[Iplan9bm]        { "plan9bm",        nil },
       +[Ijpeg]                { "jpeg",        "jpg -9 %a", "jpg -t9 %a" },
       +[Igif]                { "gif",        "gif -9 %a", "gif -t9 %a" },
       +[Iinferno]        { "inferno",        nil },
       +[Ifax]                { "fax",        "aux/g3p9bit -g %a" },
       +[Icvt2pic]        { "unknown",        "fb/cvt2pic %a |fb/3to1 rgbv" },
       +[Ippm]                { "ppm",        "ppm -9 %a", "ppm -t9 %a" },
       +/* ``temporary'' hack for hobby */
       +[Iccittg4]        { "ccitt-g4",        "cat %a|rx nslocum /usr/lib/ocr/bin/bcp -M|fb/pcp -tcompressed -l0" },
       +[Ipng]                { "png",        "png -9 %a", "png -t9 %a" },
       +[Iyuv]                { "yuv",        "yuv -9 %a", "yuv -t9 %a"  },
       +[Ibmp]                { "bmp",        "bmp -9 %a", "bmp -t9 %a"  },
       +};
       +
       +static Image*        convert(Graphic*);
       +static Image*        gfxdrawpage(Document *d, int page);
       +static char*        gfxpagename(Document*, int);
       +static int        spawnrc(char*, uchar*, int);
       +static int        addpage(Document*, char*);
       +static int        rmpage(Document*, int);
       +static int        genaddpage(Document*, char*, uchar*, int);
       +
       +static char*
       +gfxpagename(Document *doc, int page)
       +{
       +        GfxInfo *gfx = doc->extra;
       +        return gfx->g[page].name;
       +}
       +
       +static Image*
       +gfxdrawpage(Document *doc, int page)
       +{
       +        GfxInfo *gfx = doc->extra;
       +        return convert(gfx->g+page);
       +}
       +
       +Document*
       +initgfx(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
       +{
       +        GfxInfo *gfx;
       +        Document *doc;
       +        int i;
       +
       +        USED(b);
       +        doc = emalloc(sizeof(*doc));
       +        gfx = emalloc(sizeof(*gfx));
       +        gfx->g = nil;
       +        
       +        doc->npage = 0;
       +        doc->drawpage = gfxdrawpage;
       +        doc->pagename = gfxpagename;
       +        doc->addpage = addpage;
       +        doc->rmpage = rmpage;
       +        doc->extra = gfx;
       +        doc->fwdonly = 0;
       +
       +        fprint(2, "reading through graphics...\n");
       +        if(argc==0 && buf)
       +                genaddpage(doc, nil, buf, nbuf);
       +        else{
       +                for(i=0; i<argc; i++)
       +                        if(addpage(doc, argv[i]) < 0)
       +                                fprint(2, "warning: not including %s: %r\n", argv[i]);
       +        }
       +
       +        return doc;
       +}
       +
       +static int
       +genaddpage(Document *doc, char *name, uchar *buf, int nbuf)
       +{
       +        Graphic *g;
       +        GfxInfo *gfx;
       +        Biobuf *b;
       +        uchar xbuf[32];
       +        int i, l;
       +
       +        l = 0;
       +        gfx = doc->extra;
       +
       +        assert((name == nil) ^ (buf == nil));
       +        assert(name != nil || doc->npage == 0);
       +
       +        for(i=0; i<doc->npage; i++)
       +                if(strcmp(gfx->g[i].name, name) == 0)
       +                        return i;
       +
       +        if(name){
       +                l = strlen(name);
       +                if((b = Bopen(name, OREAD)) == nil) {
       +                        werrstr("Bopen: %r");
       +                        return -1;
       +                }
       +
       +                if(Bread(b, xbuf, sizeof xbuf) != sizeof xbuf) {
       +                        werrstr("short read: %r");
       +                        return -1;
       +                }
       +                Bterm(b);
       +                buf = xbuf;
       +                nbuf = sizeof xbuf;
       +        }
       +
       +
       +        gfx->g = erealloc(gfx->g, (doc->npage+1)*(sizeof(*gfx->g)));
       +        g = &gfx->g[doc->npage];
       +
       +        memset(g, 0, sizeof *g);
       +        if(memcmp(buf, "GIF", 3) == 0)
       +                g->type = Igif;
       +        else if(memcmp(buf, "\111\111\052\000", 4) == 0) 
       +                g->type = Itiff;
       +        else if(memcmp(buf, "\115\115\000\052", 4) == 0)
       +                g->type = Itiff;
       +        else if(memcmp(buf, "\377\330\377", 3) == 0)
       +                g->type = Ijpeg;
       +        else if(memcmp(buf, "\211PNG\r\n\032\n", 3) == 0)
       +                g->type = Ipng;
       +        else if(memcmp(buf, "compressed\n", 11) == 0)
       +                g->type = Iinferno;
       +        else if(memcmp(buf, "\0PC Research, Inc", 17) == 0)
       +                g->type = Ifax;
       +        else if(memcmp(buf, "TYPE=ccitt-g31", 14) == 0)
       +                g->type = Ifax;
       +        else if(memcmp(buf, "II*", 3) == 0)
       +                g->type = Ifax;
       +        else if(memcmp(buf, "TYPE=ccitt-g4", 13) == 0)
       +                g->type = Iccittg4;
       +        else if(memcmp(buf, "TYPE=", 5) == 0)
       +                g->type = Ipic;
       +        else if(buf[0] == 'P' && '0' <= buf[1] && buf[1] <= '9')
       +                g->type = Ippm;
       +        else if(memcmp(buf, "BM", 2) == 0)
       +                g->type = Ibmp;
       +        else if(memcmp(buf, "          ", 10) == 0 &&
       +                '0' <= buf[10] && buf[10] <= '9' &&
       +                buf[11] == ' ')
       +                g->type = Iplan9bm;
       +        else if(strtochan((char*)buf) != 0)
       +                g->type = Iplan9bm;
       +        else if (l > 4 && strcmp(name + l -4, ".yuv") == 0)
       +                g->type = Iyuv;
       +        else
       +                g->type = Icvt2pic;
       +
       +        if(name)
       +                g->name = estrdup(name);
       +        else{
       +                g->name = estrdup("stdin");        /* so it can be freed */
       +                g->buf = buf;
       +                g->nbuf = nbuf;
       +        }
       +
       +        if(chatty) fprint(2, "classified \"%s\" as \"%s\"\n", g->name, cvt[g->type].name);
       +        return doc->npage++;
       +}
       +
       +static int 
       +addpage(Document *doc, char *name)
       +{
       +        return genaddpage(doc, name, nil, 0);
       +}
       +
       +static int
       +rmpage(Document *doc, int n)
       +{
       +        int i;
       +        GfxInfo *gfx;
       +
       +        if(n < 0 || n >= doc->npage)
       +                return -1;
       +
       +        gfx = doc->extra;
       +        doc->npage--;
       +        free(gfx->g[n].name);
       +
       +        for(i=n; i<doc->npage; i++)
       +                gfx->g[i] = gfx->g[i+1];
       +
       +        if(n < doc->npage)
       +                return n;
       +        if(n == 0)
       +                return 0;
       +        return n-1;
       +}
       +
       +
       +static Image*
       +convert(Graphic *g)
       +{
       +        int fd;
       +        Convert c;
       +        char *cmd;
       +        char *name, buf[1000];
       +        Image *im;
       +        int rcspawned = 0;
       +        Waitmsg *w;
       +
       +        c = cvt[g->type];
       +        if(c.cmd == nil) {
       +                if(chatty) fprint(2, "no conversion for bitmap \"%s\"...\n", g->name);
       +                if(g->buf == nil){        /* not stdin */
       +                        fd = open(g->name, OREAD);
       +                        if(fd < 0) {
       +                                fprint(2, "cannot open file: %r\n");
       +                                wexits("open");
       +                        }
       +                }else
       +                        fd = stdinpipe(g->buf, g->nbuf);        
       +        } else {
       +                cmd = c.cmd;
       +                if(truecolor && c.truecmd)
       +                        cmd = c.truecmd;
       +
       +                if(g->buf != nil)        /* is stdin */
       +                        name = "";
       +                else
       +                        name = g->name;
       +                if(strlen(cmd)+strlen(name) > sizeof buf) {
       +                        fprint(2, "command too long\n");
       +                        wexits("convert");
       +                }
       +                snprint(buf, sizeof buf, cmd, name);
       +                if(chatty) fprint(2, "using \"%s\" to convert \"%s\"...\n", buf, g->name);
       +                fd = spawnrc(buf, g->buf, g->nbuf);
       +                rcspawned++;
       +                if(fd < 0) {
       +                        fprint(2, "cannot spawn converter: %r\n");
       +                        wexits("convert");
       +                }        
       +        }
       +
       +        im = readimage(display, fd, 0);
       +        if(im == nil) {
       +                fprint(2, "warning: couldn't read image: %r\n");
       +        }
       +        close(fd);
       +
       +        /* for some reason rx doesn't work well with wait */
       +        /* for some reason 3to1 exits on success with a non-null status of |3to1 */
       +        if(rcspawned && g->type != Iccittg4) {
       +                if((w=wait())!=nil && w->msg[0] && !strstr(w->msg, "3to1"))
       +                        fprint(2, "slave wait error: %s\n", w->msg);
       +                free(w);
       +        }
       +        return im;
       +}
       +
       +static int
       +spawnrc(char *cmd, uchar *stdinbuf, int nstdinbuf)
       +{
       +        int pfd[2];
       +        int pid;
       +
       +        if(chatty) fprint(2, "spawning(%s)...", cmd);
       +
       +        if(pipe(pfd) < 0)
       +                return -1;
       +        if((pid = fork()) < 0)
       +                return -1;
       +
       +        if(pid == 0) {
       +                close(pfd[1]);
       +                if(stdinbuf)
       +                        dup(stdinpipe(stdinbuf, nstdinbuf), 0);
       +                else
       +                        dup(open("/dev/null", OREAD), 0);
       +                dup(pfd[0], 1);
       +                //dup(pfd[0], 2);
       +                execl("/bin/rc", "rc", "-c", cmd, nil);
       +                wexits("exec");
       +        }
       +        close(pfd[0]);
       +        return pfd[1];
       +}
       +
 (DIR) diff --git a/src/cmd/page/gs.c b/src/cmd/page/gs.c
       t@@ -0,0 +1,342 @@
       +/*
       + * gs interface for page.
       + * ps.c and pdf.c both use these routines.
       + * a caveat: if you run more than one gs, only the last 
       + * one gets killed by killgs 
       + */
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <event.h>
       +#include <bio.h>
       +#include "page.h"
       +
       +static int gspid;        /* globals for atexit */
       +static int gsfd;
       +static void        killgs(void);
       +
       +static void
       +killgs(void)
       +{
       +        char tmpfile[100];
       +
       +        close(gsfd);
       +        postnote(PNGROUP, getpid(), "die");
       +
       +        /*
       +         * from ghostscript's use.txt:
       +         * ``Ghostscript currently doesn't do a very good job of deleting temporary
       +         * files when it exits; you may have to delete them manually from time to
       +         * time.''
       +         */
       +        sprint(tmpfile, "/tmp/gs_%.5da", (gspid+300000)%100000);
       +        if(chatty) fprint(2, "remove %s...\n", tmpfile);
       +        remove(tmpfile);
       +        sleep(100);
       +        postnote(PNPROC, gspid, "die yankee pig dog");
       +}
       +
       +int
       +spawnwriter(GSInfo *g, Biobuf *b)
       +{
       +        char buf[4096];
       +        int n;
       +        int fd;
       +
       +        switch(fork()){
       +        case -1:        return -1;
       +        case 0:        break;
       +        default:        return 0;
       +        }
       +
       +        Bseek(b, 0, 0);
       +        fd = g->gsfd;
       +        while((n = Bread(b, buf, sizeof buf)) > 0)
       +                write(fd, buf, n);
       +        fprint(fd, "(/fd/3) (w) file dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring flushfile\n");
       +        _exits(0);
       +        return -1;
       +}
       +
       +int
       +spawnreader(int fd)
       +{
       +        int n, pfd[2];
       +        char buf[1024];
       +
       +        if(pipe(pfd)<0)
       +                return -1;
       +        switch(fork()){
       +        case -1:
       +                return -1;
       +        case 0:
       +                break;
       +        default:
       +                close(pfd[0]);
       +                return pfd[1];
       +        }
       +
       +        close(pfd[1]);
       +        switch(fork()){
       +        case -1:
       +                wexits("fork failed");
       +        case 0:
       +                while((n=read(fd, buf, sizeof buf)) > 0) {
       +                        write(1, buf, n);
       +                        write(pfd[0], buf, n);
       +                }
       +                break;
       +        default:
       +                while((n=read(pfd[0], buf, sizeof buf)) > 0) {
       +                        write(1, buf, n);
       +                        write(fd, buf, n);
       +                }
       +                break;
       +        }
       +        postnote(PNGROUP, getpid(), "i'm die-ing");
       +        _exits(0);
       +        return -1;
       +}
       +
       +void
       +spawnmonitor(int fd)
       +{
       +        char buf[4096];
       +        char *xbuf;
       +        int n;
       +        int out;
       +        int first;
       +
       +        switch(rfork(RFFDG|RFNOTEG|RFPROC)){
       +        case -1:
       +        default:
       +                return;
       +
       +        case 0:
       +                break;
       +        }
       +
       +        out = open("/dev/cons", OWRITE);
       +        if(out < 0)
       +                out = 2;
       +
       +        xbuf = buf;        /* for ease of acid */
       +        first = 1;
       +        while((n = read(fd, xbuf, sizeof buf)) > 0){
       +                if(first){
       +                        first = 0;
       +                        fprint(2, "Ghostscript Error:\n");
       +                }
       +                write(out, xbuf, n);
       +                alarm(500);
       +        }
       +        _exits(0);
       +}
       +
       +int 
       +spawngs(GSInfo *g)
       +{
       +        char *args[16];
       +        char tb[32], gb[32];
       +        int i, nargs;
       +        int devnull;
       +        int stdinout[2];
       +        int dataout[2];
       +        int errout[2];
       +
       +        /*
       +         * spawn gs
       +         *
       +          * gs's standard input is fed from stdinout.
       +         * gs output written to fd-2 (i.e. output we generate intentionally) is fed to stdinout.
       +         * gs output written to fd 1 (i.e. ouptut gs generates on error) is fed to errout.
       +         * gs data output is written to fd 3, which is dataout.
       +         */
       +        if(pipe(stdinout) < 0 || pipe(dataout)<0 || pipe(errout)<0)
       +                return -1;
       +
       +        nargs = 0;
       +        args[nargs++] = "gs";
       +        args[nargs++] = "-dNOPAUSE";
       +        args[nargs++] = "-dSAFER";
       +        args[nargs++] = "-sDEVICE=plan9";
       +        args[nargs++] = "-sOutputFile=/fd/3";
       +        args[nargs++] = "-dQUIET";
       +        args[nargs++] = "-r100";
       +        sprint(tb, "-dTextAlphaBits=%d", textbits);
       +        sprint(gb, "-dGraphicsAlphaBits=%d", gfxbits);
       +        if(textbits)
       +                args[nargs++] = tb;
       +        if(gfxbits)
       +                args[nargs++] = gb;
       +        args[nargs++] = "-";
       +        args[nargs] = nil;
       +
       +        gspid = fork();
       +        if(gspid == 0) {
       +                close(stdinout[1]);
       +                close(dataout[1]);
       +                close(errout[1]);
       +
       +                /*
       +                 * Horrible problem: we want to dup fd's 0-4 below,
       +                 * but some of the source fd's might have those small numbers.
       +                 * So we need to reallocate those.  In order to not step on
       +                 * anything else, we'll dup the fd's to higher ones using
       +                 * dup(x, -1), but we need to use up the lower ones first.
       +                 */
       +                while((devnull = open("/dev/null", ORDWR)) < 5)
       +                        ;
       +
       +                stdinout[0] = dup(stdinout[0], -1);
       +                errout[0] = dup(errout[0], -1);
       +                dataout[0] = dup(dataout[0], -1);
       +
       +                dup(stdinout[0], 0);
       +                dup(errout[0], 1);
       +                dup(devnull, 2);        /* never anything useful */
       +                dup(dataout[0], 3);
       +                dup(stdinout[0], 4);
       +                for(i=5; i<20; i++)
       +                        close(i);
       +                exec("/bin/gs", args);
       +                wexits("exec");
       +        }
       +        close(stdinout[0]);
       +        close(errout[0]);
       +        close(dataout[0]);
       +        atexit(killgs);
       +
       +        if(teegs)
       +                stdinout[1] = spawnreader(stdinout[1]);
       +
       +        gsfd = g->gsfd = stdinout[1];
       +        g->gsdfd = dataout[1];
       +        g->gspid = gspid;
       +
       +        spawnmonitor(errout[1]);
       +        Binit(&g->gsrd, g->gsfd, OREAD);
       +
       +        gscmd(g, "/PAGEOUT (/fd/4) (w) file def\n");
       +        gscmd(g, "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n");
       +        waitgs(g);
       +
       +        return 0;
       +}
       +
       +int
       +gscmd(GSInfo *gs, char *fmt, ...)
       +{
       +        char buf[1024];
       +        int n;
       +
       +        va_list v;
       +        va_start(v, fmt);
       +        n = vseprint(buf, buf+sizeof buf, fmt, v) - buf;
       +        if(n <= 0)
       +                return n;
       +
       +        if(chatty) {
       +                fprint(2, "cmd: ");
       +                write(2, buf, n);
       +        }
       +
       +        if(write(gs->gsfd, buf, n) != 0)
       +                return -1;
       +
       +        return n;
       +}
       +
       +/*
       + * set the dimensions of the bitmap we expect to get back from GS.
       + */
       +void
       +setdim(GSInfo *gs, Rectangle bbox, int ppi, int landscape)
       +{
       +        Rectangle pbox;
       +
       +        if(chatty)
       +                fprint(2, "setdim: bbox=%R\n", bbox);
       +
       +        if(ppi)
       +                gs->ppi = ppi;
       +
       +        gscmd(gs, "mark\n");
       +        if(ppi)
       +                gscmd(gs, "/HWResolution [%d %d]\n", ppi, ppi);
       +
       +        if(!Dx(bbox))
       +                bbox = Rect(0, 0, 612, 792);        /* 8½×11 */
       +
       +        switch(landscape){
       +        case 0:
       +                pbox = bbox;
       +                break;
       +        case 1:
       +                pbox = Rect(bbox.min.y, bbox.min.x, bbox.max.y, bbox.max.x);
       +                break;
       +        }
       +        gscmd(gs, "/PageSize [%d %d]\n", Dx(pbox), Dy(pbox));
       +        gscmd(gs, "/Margins [%d %d]\n", -pbox.min.x, -pbox.min.y);
       +        gscmd(gs, "currentdevice putdeviceprops pop\n");
       +        gscmd(gs, "/#copies 1 store\n");
       +
       +        if(!eqpt(bbox.min, ZP))
       +                gscmd(gs, "%d %d translate\n", -bbox.min.x, -bbox.min.y);
       +
       +        switch(landscape){
       +        case 0:
       +                break;
       +        case 1:
       +                gscmd(gs, "%d 0 translate\n", Dy(bbox));
       +                gscmd(gs, "90 rotate\n");
       +                break;
       +        }
       +
       +        waitgs(gs);
       +}
       +
       +void
       +waitgs(GSInfo *gs)
       +{
       +        /* we figure out that gs is done by telling it to
       +         * print something and waiting until it does.
       +         */
       +        char *p;
       +        Biobuf *b = &gs->gsrd;
       +        uchar buf[1024];
       +        int n;
       +
       +//        gscmd(gs, "(\\n**bstack\\n) print flush\n");
       +//        gscmd(gs, "stack flush\n");
       +//        gscmd(gs, "(**estack\\n) print flush\n");
       +        gscmd(gs, "(\\n//GO.SYSIN DD\\n) PAGE==\n");
       +
       +        alarm(300*1000);
       +        for(;;) {
       +                p = Brdline(b, '\n');
       +                if(p == nil) {
       +                        n = Bbuffered(b);
       +                        if(n <= 0)
       +                                break;
       +                        if(n > sizeof buf)
       +                                n = sizeof buf;
       +                        Bread(b, buf, n);
       +                        continue;
       +                }
       +                p[Blinelen(b)-1] = 0;
       +                if(chatty) fprint(2, "p: ");
       +                if(chatty) write(2, p, Blinelen(b)-1);
       +                if(chatty) fprint(2, "\n");
       +                if(strstr(p, "Error:")) {
       +                        alarm(0);
       +                        fprint(2, "ghostscript error: %s\n", p);
       +                        wexits("gs error");
       +                }
       +
       +                if(strstr(p, "//GO.SYSIN DD")) {
       +                        break;
       +                }
       +        }
       +        alarm(0);
       +}
 (DIR) diff --git a/src/cmd/page/mkfile b/src/cmd/page/mkfile
       t@@ -0,0 +1,23 @@
       +<$PLAN9/src/mkhdr
       +
       +TARG=page
       +
       +HFILES=page.h
       +OFILES=\
       +        filter.$O\
       +        gfx.$O\
       +        gs.$O\
       +        page.$O\
       +        pdf.$O\
       +        ps.$O\
       +        rotate.$O\
       +        util.$O\
       +        view.$O\
       +
       +<$PLAN9/src//mkone
       +
       +pdfprolog.c: pdfprolog.ps
       +        cat pdfprolog.ps | sed 's/.*/"&\\n"/g' >pdfprolog.c
       +
       +pdf.$O: pdfprolog.c
       +
 (DIR) diff --git a/src/cmd/page/nrotate.c b/src/cmd/page/nrotate.c
       t@@ -0,0 +1,277 @@
       +/*
       + * Rotate an image 180° in O(log Dx + log Dy)
       + * draw calls, using an extra buffer the same size
       + * as the image.
       + *
       + * The basic concept is that you can invert an array by
       + * inverting the top half, inverting the bottom half, and
       + * then swapping them.
       + * 
       + * This is usually overkill, but it speeds up slow remote
       + * connections quite a bit.
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include <bio.h>
       +#include <draw.h>
       +#include <event.h>
       +#include "page.h"
       +
       +int ndraw = 0;
       +
       +enum {
       +        Xaxis,
       +        Yaxis,
       +};
       +
       +static void reverse(Image*, Image*, int);
       +static void shuffle(Image*, Image*, int, int, Image*, int, int);
       +static void writefile(char *name, Image *im, int gran);
       +static void halvemaskdim(Image*);
       +static void swapranges(Image*, Image*, int, int, int, int);
       +
       +/*
       + * Rotate the image 180° by reflecting first
       + * along the X axis, and then along the Y axis.
       + */
       +void
       +rot180(Image *img)
       +{
       +        Image *tmp;
       +
       +        tmp = xallocimage(display, img->r, img->chan, 0, DNofill);
       +        if(tmp == nil)
       +                return;
       +
       +        reverse(img, tmp, Xaxis);
       +        reverse(img, tmp, Yaxis);
       +
       +        freeimage(tmp);
       +}
       +
       +Image *mtmp;
       +
       +static void
       +reverse(Image *img, Image *tmp, int axis)
       +{
       +        Image *mask;
       +        Rectangle r;
       +        int i, d;
       +
       +        /*
       +         * We start by swapping large chunks at a time.
       +         * The chunk size should be the largest power of
       +         * two that fits in the dimension.
       +         */
       +        d = axis==Xaxis ? Dx(img) : Dy(img);
       +        for(i = 1; i*2 <= d; i *= 2)
       +                ;
       +
       +        r = axis==Xaxis ? Rect(0,0, i,100) : Rect(0,0, 100,i);
       +        mask = xallocimage(display, r, GREY1, 1, DTransparent);
       +        mtmp = xallocimage(display, r, GREY1, 1, DTransparent);
       +
       +        /*
       +         * Now color the bottom (or left) half of the mask opaque.
       +         */
       +        if(axis==Xaxis)
       +                r.max.x /= 2;
       +        else
       +                r.max.y /= 2;
       +
       +        draw(mask, r, display->opaque, nil, ZP);
       +        writefile("mask", mask, i);
       +
       +        /*
       +         * Shuffle will recur, shuffling the pieces as necessary
       +         * and making the mask a finer and finer grating.
       +         */
       +        shuffle(img, tmp, axis, d, mask, i, 0);
       +
       +        freeimage(mask);
       +}
       +
       +/*
       + * Shuffle the image by swapping pieces of size maskdim.
       + */
       +static void
       +shuffle(Image *img, Image *tmp, int axis, int imgdim, Image *mask, int maskdim)
       +{
       +        int slop;
       +
       +        if(maskdim == 0)
       +                return;
       +
       +        /*
       +         * Figure out how much will be left over that needs to be
       +         * shifted specially to the bottom.
       +         */
       +        slop = imgdim % maskdim;
       +
       +        /*
       +         * Swap adjacent grating lines as per mask.
       +         */
       +        swapadjacent(img, tmp, axis, imgdim - slop, mask, maskdim);
       +
       +        /*
       +         * Calculate the mask with gratings half as wide and recur.
       +         */
       +        halvemaskdim(mask, maskdim, axis);
       +        writefile("mask", mask, maskdim/2);
       +
       +        shuffle(img, tmp, axis, imgdim, mask, maskdim/2);
       +
       +        /*
       +         * Move the slop down to the bottom of the image.
       +         */
       +        swapranges(img, tmp, 0, imgdim-slop, imgdim, axis);
       +        moveup(im, tmp, lastnn, nn, n, axis);
       +}
       +
       +/*
       + * Halve the grating period in the mask.
       + * The grating currently looks like 
       + * ####____####____####____####____
       + * where #### is opacity.
       + *
       + * We want
       + * ##__##__##__##__##__##__##__##__
       + * which is achieved by shifting the mask
       + * and drawing on itself through itself.
       + * Draw doesn't actually allow this, so 
       + * we have to copy it first.
       + *
       + *     ####____####____####____####____ (dst)
       + * +   ____####____####____####____#### (src)
       + * in  __####____####____####____####__ (mask)
       + * ===========================================
       + *     ##__##__##__##__##__##__##__##__
       + */
       +static void
       +halvemaskdim(Image *m, int maskdim, int axis)
       +{
       +        Point δ;
       +
       +        δ = axis==Xaxis ? Pt(maskdim,0) : Pt(0,maskdim);
       +        draw(mtmp, mtmp->r, mask, nil, mask->r.min);
       +        gendraw(mask, mask->r, mtmp, δ, mtmp, divpt(δ,2));
       +        writefile("mask", mask, maskdim/2);
       +}
       +
       +/*
       + * Swap the regions [a,b] and [b,c]
       + */
       +static void
       +swapranges(Image *img, Image *tmp, int a, int b, int c, int axis)
       +{
       +        Rectangle r;
       +        Point δ;
       +
       +        if(a == b || b == c)
       +                return;
       +
       +        writefile("swap", img, 0);
       +        draw(tmp, tmp->r, im, nil, im->r.min);
       +
       +        /* [a,a+(c-b)] gets [b,c] */
       +        r = img->r;
       +        if(axis==Xaxis){
       +                δ = Pt(1,0);
       +                r.min.x = img->r.min.x + a;
       +                r.max.x = img->r.min.x + a + (c-b);
       +        }else{
       +                δ = Pt(0,1);
       +                r.min.y = img->r.min.y + a;
       +                r.max.y = img->r.min.y + a + (c-b);
       +        }
       +        draw(img, r, tmp, nil, addpt(tmp->r.min, mulpt(δ, b)));
       +
       +        /* [a+(c-b), c] gets [a,b] */
       +        r = img->r;
       +        if(axis==Xaxis){
       +                r.min.x = img->r.min.x + a + (c-b);
       +                r.max.x = img->r.min.x + c;
       +        }else{
       +                r.min.y = img->r.min.y + a + (c-b);
       +                r.max.y = img->r.min.y + c;
       +        }
       +        draw(img, r, tmp, nil, addpt(tmp->r.min, mulpt(δ, a)));
       +        writefile("swap", img, 1);
       +}
       +
       +/*
       + * Swap adjacent regions as specified by the grating.
       + * We do this by copying the image through the mask twice,
       + * once aligned with the grading and once 180° out of phase.
       + */
       +static void
       +swapadjacent(Image *img, Image *tmp, int axis, int imgdim, Image *mask, int maskdim)
       +{
       +        Point δ;
       +        Rectangle r0, r1;
       +
       +        δ = axis==Xaxis ? Pt(1,0) : Pt(0,1);
       +
       +        r0 = img->r;
       +        r1 = img->r;
       +        switch(axis){
       +        case Xaxis:
       +                r0.max.x = imgdim;
       +                r1.min.x = imgdim;
       +                break;
       +        case Yaxis:
       +                r0.max.y = imgdim;
       +                r1.min.y = imgdim;
       +        }
       +
       +        /*
       +         * r0 is the lower rectangle, while r1 is the upper one.
       +         */
       +        draw(tmp, tmp->r, img, nil, 
       +}
       +
       +void
       +interlace(Image *im, Image *tmp, int axis, int n, Image *mask, int gran)
       +{
       +        Point p0, p1;
       +        Rectangle r0, r1;
       +
       +        r0 = im->r;
       +        r1 = im->r;
       +        switch(axis) {
       +        case Xaxis:
       +                r0.max.x = n;
       +                r1.min.x = n;
       +                p0 = (Point){gran, 0};
       +                p1 = (Point){-gran, 0};
       +                break;
       +        case Yaxis:
       +                r0.max.y = n;
       +                r1.min.y = n;
       +                p0 = (Point){0, gran};
       +                p1 = (Point){0, -gran};
       +                break;
       +        }
       +
       +        draw(tmp, im->r, im, display->black, im->r.min);
       +        gendraw(im, r0, tmp, p0, mask, mask->r.min);
       +        gendraw(im, r0, tmp, p1, mask, p1);
       +}
       +
       +
       +static void
       +writefile(char *name, Image *im, int gran)
       +{
       +        static int c = 100;
       +        int fd;
       +        char buf[200];
       +
       +        snprint(buf, sizeof buf, "%d%s%d", c++, name, gran);
       +        fd = create(buf, OWRITE, 0666);
       +        if(fd < 0)
       +                return;        
       +        writeimage(fd, im, 0);
       +        close(fd);
       +}
       +
 (DIR) diff --git a/src/cmd/page/page.c b/src/cmd/page/page.c
       t@@ -0,0 +1,236 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <event.h>
       +#include <bio.h>
       +#include "page.h"
       +
       +int resizing;
       +int mknewwindow;
       +int doabort;
       +int chatty;
       +int reverse = -1;
       +int goodps = 1;
       +int ppi = 100;
       +int teegs = 0;
       +int truetoboundingbox;
       +int textbits=4, gfxbits=4;
       +int wctlfd = -1;
       +int stdinfd;
       +int truecolor;
       +int imagemode;
       +int notewatcher;
       +int notegp;
       +
       +int
       +watcher(void *v, char *x)
       +{
       +        USED(v);
       +
       +        if(strcmp(x, "die") != 0)
       +                postnote(PNGROUP, notegp, x);
       +        _exits(0);
       +        return 0;
       +}
       +
       +int
       +bell(void *u, char *x)
       +{
       +        if(x && strcmp(x, "hangup") == 0)
       +                _exits(0);
       +
       +        if(x && strstr(x, "die") == nil)
       +                fprint(2, "postnote %d: %s\n", getpid(), x);
       +
       +        /* alarms come from the gs monitor */
       +        if(x && strstr(x, "alarm")){
       +                postnote(PNGROUP, getpid(), "die (gs error)");
       +                postnote(PNPROC, notewatcher, "die (gs error)");
       +        }
       +
       +        /* function mentions u so that it's in the stack trace */
       +        if((u == nil || u != x) && doabort)
       +                abort();
       +
       +/*        fprint(2, "exiting %d\n", getpid()); */
       +        wexits("note");
       +        return 0;
       +}
       +
       +static int
       +afmt(Fmt *fmt)
       +{
       +        char *s;
       +
       +        s = va_arg(fmt->args, char*);
       +        if(s == nil || s[0] == '\0')
       +                return fmtstrcpy(fmt, "");
       +        else
       +                return fmtprint(fmt, "%#q", s);
       +}
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: page [-biRrw] [-p ppi] file...\n");
       +        exits("usage");
       +}
       +
       +void
       +main(int argc, char **argv)
       +{
       +        Document *doc;
       +        Biobuf *b;
       +        enum { Ninput = 16 };
       +        uchar buf[Ninput+1];
       +        int readstdin;
       +
       +        ARGBEGIN{
       +        /* "temporary" debugging options */
       +        case 'P':
       +                goodps = 0;
       +                break;
       +        case 'v':
       +                chatty++;
       +                break;
       +        case 'V':
       +                teegs++;
       +                break;
       +        case 'a':
       +                doabort++;
       +                break;
       +        case 'T':
       +                textbits = atoi(EARGF(usage()));
       +                gfxbits = atoi(EARGF(usage()));
       +                break;
       +
       +        /* real options */
       +        case 'R':
       +                resizing = 1;
       +                break;
       +        case 'r':
       +                reverse = 1;
       +                break;
       +        case 'p':
       +                ppi = atoi(EARGF(usage()));
       +                break;
       +        case 'b':
       +                truetoboundingbox = 1;
       +                break;
       +        case 'w':
       +                mknewwindow = 1;
       +                resizing = 1;
       +                break;
       +        case 'i':
       +                imagemode = 1;
       +                break;
       +        default:
       +                usage();
       +        }ARGEND;
       +
       +        notegp = getpid();
       +
       +        switch(notewatcher = fork()){
       +        case -1:
       +                sysfatal("fork\n");
       +                exits(0);
       +        default:
       +                break;
       +        case 0:
       +                atnotify(watcher, 1);
       +                for(;;)
       +                        sleep(1000);
       +                _exits(0);
       +        }
       +
       +        rfork(RFNOTEG);
       +        atnotify(bell, 1);
       +
       +        readstdin = 0;
       +        if(imagemode == 0 && argc == 0){
       +                readstdin = 1;
       +                stdinfd = dup(0, -1);
       +                close(0);
       +                open("/dev/cons", OREAD);
       +        }
       +
       +        quotefmtinstall();
       +        fmtinstall('a', afmt);
       +
       +        fmtinstall('R', Rfmt);
       +        fmtinstall('P', Pfmt);
       +
       +        if(readstdin){
       +                b = nil;
       +                if(readn(stdinfd, buf, Ninput) != Ninput){
       +                        fprint(2, "page: short read reading %s\n", argv[0]);
       +                        wexits("read");
       +                }
       +        }else if(argc != 0){
       +                if(!(b = Bopen(argv[0], OREAD))) {
       +                        fprint(2, "page: cannot open \"%s\"\n", argv[0]);
       +                        wexits("open");
       +                }        
       +
       +                if(Bread(b, buf, Ninput) != Ninput) {
       +                        fprint(2, "page: short read reading %s\n", argv[0]);
       +                        wexits("read");
       +                }
       +        }else
       +                b = nil;
       +
       +        buf[Ninput] = '\0';
       +        if(imagemode)
       +                doc = initgfx(nil, 0, nil, nil, 0);
       +        else if(strncmp((char*)buf, "%PDF-", 5) == 0)
       +                doc = initpdf(b, argc, argv, buf, Ninput);
       +        else if(strncmp((char*)buf, "\x04%!", 2) == 0)
       +                doc = initps(b, argc, argv, buf, Ninput);
       +        else if(buf[0] == '\x1B' && strstr((char*)buf, "@PJL"))
       +                doc = initps(b, argc, argv, buf, Ninput);
       +        else if(strncmp((char*)buf, "%!", 2) == 0)
       +                doc = initps(b, argc, argv, buf, Ninput);
       +        else if(strcmp((char*)buf, "\xF7\x02\x01\x83\x92\xC0\x1C;") == 0)
       +                doc = initdvi(b, argc, argv, buf, Ninput);
       +        else if(strncmp((char*)buf, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 8) == 0)
       +                doc = initmsdoc(b, argc, argv, buf, Ninput);
       +        else if(strncmp((char*)buf, "x T ", 4) == 0)
       +                doc = inittroff(b, argc, argv, buf, Ninput);
       +        else {
       +                if(ppi != 100) {
       +                        fprint(2, "page: you can't specify -p with graphic files\n");
       +                        wexits("-p and graphics");
       +                }
       +                doc = initgfx(b, argc, argv, buf, Ninput);
       +        }
       +
       +        if(doc == nil) {
       +                fprint(2, "page: error reading file: %r\n");
       +                wexits("document init");
       +        }
       +
       +        if(doc->npage < 1 && !imagemode) {
       +                fprint(2, "page: no pages found?\n");
       +                wexits("pagecount");
       +        }
       +
       +        if(reverse == -1) /* neither cmdline nor ps reader set it */
       +                reverse = 0;
       +
       +        if(initdraw(0, 0, "page") < 0){
       +                fprint(2, "page: initdraw failed: %r\n");
       +                wexits("initdraw");
       +        }
       +        truecolor = screen->depth > 8;
       +        viewer(doc);
       +        wexits(0);
       +}
       +
       +void
       +wexits(char *s)
       +{
       +        if(s && *s && strcmp(s, "note") != 0 && mknewwindow)
       +                sleep(10*1000);
       +        postnote(PNPROC, notewatcher, "die");
       +        exits(s);
       +}
 (DIR) diff --git a/src/cmd/page/page.h b/src/cmd/page/page.h
       t@@ -0,0 +1,84 @@
       +#include <cursor.h>
       +
       +typedef struct Document Document;
       +
       +struct Document {
       +        char *docname;
       +        int npage;
       +        int fwdonly;
       +        char* (*pagename)(Document*, int);
       +        Image* (*drawpage)(Document*, int);
       +        int        (*addpage)(Document*, char*);
       +        int        (*rmpage)(Document*, int);
       +        Biobuf *b;
       +        void *extra;
       +};
       +
       +void *emalloc(int);
       +void *erealloc(void*, int);
       +char *estrdup(char*);
       +int spawncmd(char*, char **, int, int, int);
       +
       +int spooltodisk(uchar*, int, char**);
       +int stdinpipe(uchar*, int);
       +Document *initps(Biobuf*, int, char**, uchar*, int);
       +Document *initpdf(Biobuf*, int, char**, uchar*, int);
       +Document *initgfx(Biobuf*, int, char**, uchar*, int);
       +Document *inittroff(Biobuf*, int, char**, uchar*, int);
       +Document *initdvi(Biobuf*, int, char**, uchar*, int);
       +Document *initmsdoc(Biobuf*, int, char**, uchar*, int);
       +
       +void viewer(Document*);
       +extern Cursor reading;
       +extern int chatty;
       +extern int goodps;
       +extern int textbits, gfxbits;
       +extern int reverse;
       +extern int clean;
       +extern int ppi;
       +extern int teegs;
       +extern int truetoboundingbox;
       +extern int wctlfd;
       +extern int resizing;
       +extern int mknewwindow;
       +
       +void rot180(Image*);
       +Image *rot90(Image*);
       +Image *resample(Image*, Image*);
       +
       +/* ghostscript interface shared by ps, pdf */
       +typedef struct GSInfo        GSInfo;
       +struct GSInfo {
       +        int gsfd;
       +        Biobuf gsrd;
       +        int gspid;
       +        int gsdfd;
       +        int ppi;
       +};
       +void        waitgs(GSInfo*);
       +int        gscmd(GSInfo*, char*, ...);
       +int        spawngs(GSInfo*);
       +void        setdim(GSInfo*, Rectangle, int, int);
       +int        spawnwriter(GSInfo*, Biobuf*);
       +Rectangle        screenrect(void);
       +void        newwin(void);
       +void        zerox(void);
       +Rectangle winrect(void);
       +void        resize(int, int);
       +int        max(int, int);
       +int        min(int, int);
       +void        wexits(char*);
       +Image*        xallocimage(Display*, Rectangle, ulong, int, ulong);
       +int        bell(void*, char*);
       +int        opentemp(char *template);
       +
       +extern int stdinfd;
       +extern int truecolor;
       +
       +/* BUG BUG BUG BUG BUG: cannot use new draw operations in drawterm,
       + * or in vncs, and there is a bug in the kernel for copying images
       + * from cpu memory -> video memory (memmove is not being used).
       + * until all that is settled, ignore the draw operators.
       + */
       +#define drawop(a,b,c,d,e,f) draw(a,b,c,d,e)
       +#define gendrawop(a,b,c,d,e,f,g) gendraw(a,b,c,d,e,f)
 (DIR) diff --git a/src/cmd/page/pdf.c b/src/cmd/page/pdf.c
       t@@ -0,0 +1,155 @@
       +/*
       + * pdf.c
       + * 
       + * pdf file support for page
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <event.h>
       +#include <bio.h>
       +#include "page.h"
       +
       +typedef struct PDFInfo        PDFInfo;
       +struct PDFInfo {
       +        GSInfo gs;
       +        Rectangle *pagebbox;
       +};
       +
       +static Image*        pdfdrawpage(Document *d, int page);
       +static char*        pdfpagename(Document*, int);
       +
       +char *pdfprolog = 
       +#include "pdfprolog.c"
       +        ;
       +
       +Rectangle
       +pdfbbox(GSInfo *gs)
       +{
       +        char *p;
       +        char *f[4];
       +        Rectangle r;
       +        
       +        r = Rect(0,0,0,0);
       +        waitgs(gs);
       +        gscmd(gs, "/CropBox knownoget {} {[0 0 0 0]} ifelse PAGE==\n");
       +        p = Brdline(&gs->gsrd, '\n');
       +        p[Blinelen(&gs->gsrd)-1] ='\0';
       +        if(p[0] != '[')
       +                return r;
       +        if(tokenize(p+1, f, 4) != 4)
       +                return r;
       +        r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
       +        waitgs(gs);
       +        return r;
       +}
       +
       +Document*
       +initpdf(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
       +{
       +        Document *d;
       +        PDFInfo *pdf;
       +        char *p;
       +        char *fn;
       +        char fdbuf[20];
       +        int fd;
       +        int i, npage;
       +        Rectangle bbox;
       +
       +        if(argc > 1) {
       +                fprint(2, "can only view one pdf file at a time\n");
       +                return nil;
       +        }
       +
       +        fprint(2, "reading through pdf...\n");
       +        if(b == nil){        /* standard input; spool to disk (ouch) */
       +                fd = spooltodisk(buf, nbuf, &fn);
       +                sprint(fdbuf, "/fd/%d", fd);
       +                b = Bopen(fdbuf, OREAD);
       +                if(b == nil){
       +                        fprint(2, "cannot open disk spool file\n");
       +                        wexits("Bopen temp");
       +                }
       +        }else
       +                fn = argv[0];
       +
       +        /* sanity check */
       +        Bseek(b, 0, 0);
       +        if(!(p = Brdline(b, '\n')) && !(p = Brdline(b, '\r'))) {
       +                fprint(2, "cannot find end of first line\n");
       +                wexits("initps");
       +        }
       +        if(strncmp(p, "%PDF-", 5) != 0) {
       +                werrstr("not pdf");
       +                return nil;
       +        }
       +
       +        /* setup structures so one free suffices */
       +        p = emalloc(sizeof(*d) + sizeof(*pdf));
       +        d = (Document*) p;
       +        p += sizeof(*d);
       +        pdf = (PDFInfo*) p;
       +
       +        d->extra = pdf;
       +        d->b = b;
       +        d->drawpage = pdfdrawpage;
       +        d->pagename = pdfpagename;
       +        d->fwdonly = 0;
       +
       +        if(spawngs(&pdf->gs) < 0)
       +                return nil;
       +
       +        gscmd(&pdf->gs, "%s", pdfprolog);
       +        waitgs(&pdf->gs);
       +
       +        setdim(&pdf->gs, Rect(0,0,0,0), ppi, 0);
       +        gscmd(&pdf->gs, "(%s) (r) file pdfopen begin\n", fn);
       +        gscmd(&pdf->gs, "pdfpagecount PAGE==\n");
       +        p = Brdline(&pdf->gs.gsrd, '\n');
       +        npage = atoi(p);
       +        if(npage < 1) {
       +                fprint(2, "no pages?\n");
       +                return nil;
       +        }
       +        d->npage = npage;
       +        d->docname = argv[0];
       +
       +        gscmd(&pdf->gs, "Trailer\n");
       +        bbox = pdfbbox(&pdf->gs);
       +
       +        pdf->pagebbox = emalloc(sizeof(Rectangle)*npage);
       +        for(i=0; i<npage; i++) {
       +                gscmd(&pdf->gs, "%d pdfgetpage\n", i+1);
       +                pdf->pagebbox[i] = pdfbbox(&pdf->gs);
       +                if(Dx(pdf->pagebbox[i]) <= 0)
       +                        pdf->pagebbox[i] = bbox;
       +        }
       +
       +        return d;
       +}
       +
       +static Image*
       +pdfdrawpage(Document *doc, int page)
       +{
       +        PDFInfo *pdf = doc->extra;
       +        Image *im;
       +
       +        gscmd(&pdf->gs, "%d DoPDFPage\n", page+1);
       +        im = readimage(display, pdf->gs.gsdfd, 0);
       +        if(im == nil) {
       +                fprint(2, "fatal: readimage error %r\n");
       +                wexits("readimage");
       +        }
       +        waitgs(&pdf->gs);
       +        return im;
       +}
       +
       +static char*
       +pdfpagename(Document *d, int page)
       +{
       +        static char str[15];
       +        USED(d);
       +        sprint(str, "p %d", page+1);
       +        return str;
       +}
 (DIR) diff --git a/src/cmd/page/pdfprolog.c b/src/cmd/page/pdfprolog.c
       t@@ -0,0 +1,29 @@
       +"/Page null def\n"
       +"/Page# 0 def\n"
       +"/PDFSave null def\n"
       +"/DSCPageCount 0 def\n"
       +"/DoPDFPage {dup /Page# exch store pdfgetpage mypdfshowpage } def\n"
       +"\n"
       +"/pdfshowpage_mysetpage {        % <pagedict> pdfshowpage_mysetpage <pagedict>\n"
       +"  dup /CropBox pget {\n"
       +"      boxrect\n"
       +"      2 array astore /PageSize exch 4 2 roll\n"
       +"      neg exch neg exch 2 array astore /PageOffset exch\n"
       +"      << 5 1 roll >> setpagedevice\n"
       +"  } if\n"
       +"} bind def\n"
       +"\n"
       +"/mypdfshowpage                % <pagedict> pdfshowpage -\n"
       +" { dup /Page exch store\n"
       +"   pdfshowpage_init \n"
       +"   pdfshowpage_setpage \n"
       +"   pdfshowpage_mysetpage\n"
       +"   save /PDFSave exch store\n"
       +"   (before exec) VMDEBUG\n"
       +"     pdfshowpage_finish\n"
       +"   (after exec) VMDEBUG\n"
       +"   PDFSave restore\n"
       +" } bind def\n"
       +"\n"
       +"GS_PDF_ProcSet begin\n"
       +"pdfdict begin\n"
 (DIR) diff --git a/src/cmd/page/ps.c b/src/cmd/page/ps.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/page/rotate.c b/src/cmd/page/rotate.c
       t@@ -0,0 +1,474 @@
       +/*
       + * rotate an image 180° in O(log Dx + log Dy) /dev/draw writes,
       + * using an extra buffer same size as the image.
       + * 
       + * the basic concept is that you can invert an array by inverting
       + * the top half, inverting the bottom half, and then swapping them.
       + * the code does this slightly backwards to ensure O(log n) runtime.
       + * (If you do it wrong, you can get O(log² n) runtime.)
       + * 
       + * This is usually overkill, but it speeds up slow remote
       + * connections quite a bit.
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include <bio.h>
       +#include <draw.h>
       +#include <event.h>
       +#include "page.h"
       +
       +int ndraw = 0;
       +enum {
       +        Xaxis = 0,
       +        Yaxis = 1,
       +};
       +
       +Image *mtmp;
       +
       +void
       +writefile(char *name, Image *im, int gran)
       +{
       +        static int c = 100;
       +        int fd;
       +        char buf[200];
       +
       +        snprint(buf, sizeof buf, "%d%s%d", c++, name, gran);
       +        fd = create(buf, OWRITE, 0666);
       +        if(fd < 0)
       +                return;        
       +        writeimage(fd, im, 0);
       +        close(fd);
       +}
       +
       +void
       +moveup(Image *im, Image *tmp, int a, int b, int c, int axis)
       +{
       +        Rectangle range;
       +        Rectangle dr0, dr1;
       +        Point p0, p1;
       +
       +        if(a == b || b == c)
       +                return;
       +
       +        drawop(tmp, tmp->r, im, nil, im->r.min, S);
       +
       +        switch(axis){
       +        case Xaxis:
       +                range = Rect(a, im->r.min.y,  c, im->r.max.y);
       +                dr0 = range;
       +                dr0.max.x = dr0.min.x+(c-b);
       +                p0 = Pt(b, im->r.min.y);
       +
       +                dr1 = range;
       +                dr1.min.x = dr1.max.x-(b-a);
       +                p1 = Pt(a, im->r.min.y);
       +                break;
       +        case Yaxis:
       +                range = Rect(im->r.min.x, a,  im->r.max.x, c);
       +                dr0 = range;
       +                dr0.max.y = dr0.min.y+(c-b);
       +                p0 = Pt(im->r.min.x, b);
       +
       +                dr1 = range;
       +                dr1.min.y = dr1.max.y-(b-a);
       +                p1 = Pt(im->r.min.x, a);
       +                break;
       +        }
       +        drawop(im, dr0, tmp, nil, p0, S);
       +        drawop(im, dr1, tmp, nil, p1, S);
       +}
       +
       +void
       +interlace(Image *im, Image *tmp, int axis, int n, Image *mask, int gran)
       +{
       +        Point p0, p1;
       +        Rectangle r0, r1;
       +
       +        r0 = im->r;
       +        r1 = im->r;
       +        switch(axis) {
       +        case Xaxis:
       +                r0.max.x = n;
       +                r1.min.x = n;
       +                p0 = (Point){gran, 0};
       +                p1 = (Point){-gran, 0};
       +                break;
       +        case Yaxis:
       +                r0.max.y = n;
       +                r1.min.y = n;
       +                p0 = (Point){0, gran};
       +                p1 = (Point){0, -gran};
       +                break;
       +        }
       +
       +        drawop(tmp, im->r, im, display->opaque, im->r.min, S);
       +        gendrawop(im, r0, tmp, p0, mask, mask->r.min, S);
       +        gendrawop(im, r0, tmp, p1, mask, p1, S);
       +}
       +
       +/*
       + * Halve the grating period in the mask.
       + * The grating currently looks like 
       + * ####____####____####____####____
       + * where #### is opacity.
       + *
       + * We want
       + * ##__##__##__##__##__##__##__##__
       + * which is achieved by shifting the mask
       + * and drawing on itself through itself.
       + * Draw doesn't actually allow this, so 
       + * we have to copy it first.
       + *
       + *     ####____####____####____####____ (dst)
       + * +   ____####____####____####____#### (src)
       + * in  __####____####____####____####__ (mask)
       + * ===========================================
       + *     ##__##__##__##__##__##__##__##__
       + */
       +int
       +nextmask(Image *mask, int axis, int maskdim)
       +{
       +        Point delta;
       +
       +        delta = axis==Xaxis ? Pt(maskdim,0) : Pt(0,maskdim);
       +        drawop(mtmp, mtmp->r, mask, nil, mask->r.min, S);
       +        gendrawop(mask, mask->r, mtmp, delta, mtmp, divpt(delta,-2), S);
       +//        writefile("mask", mask, maskdim/2);
       +        return maskdim/2;
       +}
       +
       +void
       +shuffle(Image *im, Image *tmp, int axis, int n, Image *mask, int gran,
       +        int lastnn)
       +{
       +        int nn, left;
       +
       +        if(gran == 0)
       +                return;
       +        left = n%(2*gran);
       +        nn = n - left;
       +
       +        interlace(im, tmp, axis, nn, mask, gran);
       +//        writefile("interlace", im, gran);
       +        
       +        gran = nextmask(mask, axis, gran);
       +        shuffle(im, tmp, axis, n, mask, gran, nn);
       +//        writefile("shuffle", im, gran);
       +        moveup(im, tmp, lastnn, nn, n, axis);
       +//        writefile("move", im, gran);
       +}
       +
       +void
       +rot180(Image *im)
       +{
       +        Image *tmp, *tmp0;
       +        Image *mask;
       +        Rectangle rmask;
       +        int gran;
       +
       +        if(chantodepth(im->chan) < 8){
       +                /* this speeds things up dramatically; draw is too slow on sub-byte pixel sizes */
       +                tmp0 = xallocimage(display, im->r, CMAP8, 0, DNofill);
       +                drawop(tmp0, tmp0->r, im, nil, im->r.min, S);
       +        }else
       +                tmp0 = im;
       +
       +        tmp = xallocimage(display, tmp0->r, tmp0->chan, 0, DNofill);
       +        if(tmp == nil){
       +                if(tmp0 != im)
       +                        freeimage(tmp0);
       +                return;
       +        }
       +        for(gran=1; gran<Dx(im->r); gran *= 2)
       +                ;
       +        gran /= 4;
       +
       +        rmask.min = ZP;
       +        rmask.max = (Point){2*gran, 100};
       +
       +        mask = xallocimage(display, rmask, GREY1, 1, DTransparent);
       +        mtmp = xallocimage(display, rmask, GREY1, 1, DTransparent);
       +        if(mask == nil || mtmp == nil) {
       +                fprint(2, "out of memory during rot180: %r\n");
       +                wexits("memory");
       +        }
       +        rmask.max.x = gran;
       +        drawop(mask, rmask, display->opaque, nil, ZP, S);
       +//        writefile("mask", mask, gran);
       +        shuffle(im, tmp, Xaxis, Dx(im->r), mask, gran, 0);
       +        freeimage(mask);
       +        freeimage(mtmp);
       +
       +        for(gran=1; gran<Dy(im->r); gran *= 2)
       +                ;
       +        gran /= 4;
       +        rmask.max = (Point){100, 2*gran};
       +        mask = xallocimage(display, rmask, GREY1, 1, DTransparent);
       +        mtmp = xallocimage(display, rmask, GREY1, 1, DTransparent);
       +        if(mask == nil || mtmp == nil) {
       +                fprint(2, "out of memory during rot180: %r\n");
       +                wexits("memory");
       +        }
       +        rmask.max.y = gran;
       +        drawop(mask, rmask, display->opaque, nil, ZP, S);
       +        shuffle(im, tmp, Yaxis, Dy(im->r), mask, gran, 0);
       +        freeimage(mask);
       +        freeimage(mtmp);
       +        freeimage(tmp);
       +        if(tmp0 != im)
       +                freeimage(tmp0);
       +}
       +
       +/* rotates an image 90 degrees clockwise */
       +Image *
       +rot90(Image *im)
       +{
       +        Image *tmp;
       +        int i, j, dx, dy;
       +
       +        dx = Dx(im->r);
       +        dy = Dy(im->r);
       +        tmp = xallocimage(display, Rect(0, 0, dy, dx), im->chan, 0, DCyan);
       +        if(tmp == nil) {
       +                fprint(2, "out of memory during rot90: %r\n");
       +                wexits("memory");
       +        }
       +
       +        for(j = 0; j < dx; j++) {
       +                for(i = 0; i < dy; i++) {
       +                        drawop(tmp, Rect(i, j, i+1, j+1), im, nil, Pt(j, dy-(i+1)), S);
       +                }
       +        }
       +        freeimage(im);
       +
       +        return(tmp);
       +}
       +
       +/* from resample.c -- resize from → to using interpolation */
       +
       +
       +#define K2 7        /* from -.7 to +.7 inclusive, meaning .2 into each adjacent pixel */
       +#define NK (2*K2+1)
       +double K[NK];
       +
       +double
       +fac(int L)
       +{
       +        int i, f;
       +
       +        f = 1;
       +        for(i=L; i>1; --i)
       +                f *= i;
       +        return f;
       +}
       +
       +/* 
       + * i0(x) is the modified Bessel function, Σ (x/2)^2L / (L!)²
       + * There are faster ways to calculate this, but we precompute
       + * into a table so let's keep it simple.
       + */
       +double
       +i0(double x)
       +{
       +        double v;
       +        int L;
       +
       +        v = 1.0;
       +        for(L=1; L<10; L++)
       +                v += pow(x/2., 2*L)/pow(fac(L), 2);
       +        return v;
       +}
       +
       +double
       +kaiser(double x, double tau, double alpha)
       +{
       +        if(fabs(x) > tau)
       +                return 0.;
       +        return i0(alpha*sqrt(1-(x*x/(tau*tau))))/i0(alpha);
       +}
       +
       +void
       +resamplex(uchar *in, int off, int d, int inx, uchar *out, int outx)
       +{
       +        int i, x, k;
       +        double X, xx, v, rat;
       +
       +
       +        rat = (double)inx/(double)outx;
       +        for(x=0; x<outx; x++){
       +                if(inx == outx){
       +                        /* don't resample if size unchanged */
       +                        out[off+x*d] = in[off+x*d];
       +                        continue;
       +                }
       +                v = 0.0;
       +                X = x*rat;
       +                for(k=-K2; k<=K2; k++){
       +                        xx = X + rat*k/10.;
       +                        i = xx;
       +                        if(i < 0)
       +                                i = 0;
       +                        if(i >= inx)
       +                                i = inx-1;
       +                        v += in[off+i*d] * K[K2+k];
       +                }
       +                out[off+x*d] = v;
       +        }
       +}
       +
       +void
       +resampley(uchar **in, int off, int iny, uchar **out, int outy)
       +{
       +        int y, i, k;
       +        double Y, yy, v, rat;
       +
       +        rat = (double)iny/(double)outy;
       +        for(y=0; y<outy; y++){
       +                if(iny == outy){
       +                        /* don't resample if size unchanged */
       +                        out[y][off] = in[y][off];
       +                        continue;
       +                }
       +                v = 0.0;
       +                Y = y*rat;
       +                for(k=-K2; k<=K2; k++){
       +                        yy = Y + rat*k/10.;
       +                        i = yy;
       +                        if(i < 0)
       +                                i = 0;
       +                        if(i >= iny)
       +                                i = iny-1;
       +                        v += in[i][off] * K[K2+k];
       +                }
       +                out[y][off] = v;
       +        }
       +
       +}
       +
       +Image*
       +resample(Image *from, Image *to)
       +{
       +        int i, j, bpl, nchan;
       +        uchar **oscan, **nscan;
       +        char tmp[20];
       +        int xsize, ysize;
       +        double v;
       +        Image *t1, *t2;
       +        ulong tchan;
       +
       +        for(i=-K2; i<=K2; i++){
       +                K[K2+i] = kaiser(i/10., K2/10., 4.);
       +        }
       +
       +        /* normalize */
       +        v = 0.0;
       +        for(i=0; i<NK; i++)
       +                v += K[i];
       +        for(i=0; i<NK; i++)
       +                K[i] /= v;
       +
       +        switch(from->chan){
       +        case GREY8:
       +        case RGB24:
       +        case RGBA32:
       +        case ARGB32:
       +        case XRGB32:
       +                break;
       +
       +        case CMAP8:
       +        case RGB15:
       +        case RGB16:
       +                tchan = RGB24;
       +                goto Convert;
       +
       +        case GREY1:
       +        case GREY2:
       +        case GREY4:
       +                tchan = GREY8;
       +        Convert:
       +                /* use library to convert to byte-per-chan form, then convert back */
       +                t1 = xallocimage(display, Rect(0, 0, Dx(from->r), Dy(from->r)), tchan, 0, DNofill);
       +                if(t1 == nil) {
       +                        fprint(2, "out of memory for temp image 1 in resample: %r\n");
       +                        wexits("memory");
       +                }
       +                drawop(t1, t1->r, from, nil, ZP, S);
       +                t2 = xallocimage(display, to->r, tchan, 0, DNofill);
       +                if(t2 == nil) {
       +                        fprint(2, "out of memory temp image 2 in resample: %r\n");
       +                        wexits("memory");
       +                }
       +                resample(t1, t2);
       +                drawop(to, to->r, t2, nil, ZP, S);
       +                freeimage(t1);
       +                freeimage(t2);
       +                return to;
       +
       +        default:
       +                sysfatal("can't handle channel type %s", chantostr(tmp, from->chan));
       +        }
       +
       +        xsize = Dx(to->r);
       +        ysize = Dy(to->r);
       +        oscan = malloc(Dy(from->r)*sizeof(uchar*));
       +        nscan = malloc(max(ysize, Dy(from->r))*sizeof(uchar*));
       +        if(oscan == nil || nscan == nil)
       +                sysfatal("can't allocate: %r");
       +
       +        /* unload original image into scan lines */
       +        bpl = bytesperline(from->r, from->depth);
       +        for(i=0; i<Dy(from->r); i++){
       +                oscan[i] = malloc(bpl);
       +                if(oscan[i] == nil)
       +                        sysfatal("can't allocate: %r");
       +                j = unloadimage(from, Rect(from->r.min.x, from->r.min.y+i, from->r.max.x, from->r.min.y+i+1), oscan[i], bpl);
       +                if(j != bpl)
       +                        sysfatal("unloadimage");
       +        }
       +
       +        /* allocate scan lines for destination. we do y first, so need at least Dy(from->r) lines */
       +        bpl = bytesperline(Rect(0, 0, xsize, Dy(from->r)), from->depth);
       +        for(i=0; i<max(ysize, Dy(from->r)); i++){
       +                nscan[i] = malloc(bpl);
       +                if(nscan[i] == nil)
       +                        sysfatal("can't allocate: %r");
       +        }
       +
       +        /* resample in X */
       +        nchan = from->depth/8;
       +        for(i=0; i<Dy(from->r); i++){
       +                for(j=0; j<nchan; j++){
       +                        if(j==0 && from->chan==XRGB32)
       +                                continue;
       +                        resamplex(oscan[i], j, nchan, Dx(from->r), nscan[i], xsize);
       +                }
       +                free(oscan[i]);
       +                oscan[i] = nscan[i];
       +                nscan[i] = malloc(bpl);
       +                if(nscan[i] == nil)
       +                        sysfatal("can't allocate: %r");
       +        }
       +
       +        /* resample in Y */
       +        for(i=0; i<xsize; i++)
       +                for(j=0; j<nchan; j++)
       +                        resampley(oscan, nchan*i+j, Dy(from->r), nscan, ysize);
       +
       +        /* pack data into destination */
       +        bpl = bytesperline(to->r, from->depth);
       +        for(i=0; i<ysize; i++){
       +                j = loadimage(to, Rect(0, i, xsize, i+1), nscan[i], bpl);
       +                if(j != bpl)
       +                        sysfatal("loadimage: %r");
       +        }
       +
       +        for(i=0; i<Dy(from->r); i++){
       +                free(oscan[i]);
       +                free(nscan[i]);
       +        }
       +        free(oscan);
       +        free(nscan);
       +
       +        return to;
       +}
 (DIR) diff --git a/src/cmd/page/util.c b/src/cmd/page/util.c
       t@@ -0,0 +1,131 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <event.h>
       +#include <bio.h>
       +#include "page.h"
       +
       +void*
       +emalloc(int sz)
       +{
       +        void *v;
       +        v = malloc(sz);
       +        if(v == nil) {
       +                fprint(2, "out of memory allocating %d\n", sz);
       +                wexits("mem");
       +        }
       +        memset(v, 0, sz);
       +        return v;
       +}
       +
       +void*
       +erealloc(void *v, int sz)
       +{
       +        v = realloc(v, sz);
       +        if(v == nil) {
       +                fprint(2, "out of memory allocating %d\n", sz);
       +                wexits("mem");
       +        }
       +        return v;
       +}
       +
       +char*
       +estrdup(char *s)
       +{
       +        char *t;
       +        if((t = strdup(s)) == nil) {
       +                fprint(2, "out of memory in strdup(%.10s)\n", s);
       +                wexits("mem");
       +        }
       +        return t;
       +}
       +
       +int
       +opentemp(char *template)
       +{
       +        int fd, i;
       +        char *p;
       +
       +        p = estrdup(template);
       +        fd = -1;
       +        for(i=0; i<10; i++){
       +                mktemp(p);
       +                if(access(p, 0) < 0 && (fd=create(p, ORDWR|ORCLOSE, 0400)) >= 0)
       +                        break;
       +                strcpy(p, template);
       +        }
       +        if(fd < 0){
       +                fprint(2, "couldn't make temporary file\n");
       +                wexits("Ecreat");
       +        }
       +        strcpy(template, p);
       +        free(p);
       +
       +        return fd;
       +}
       +
       +/*
       + * spool standard input to /tmp.
       + * we've already read the initial in bytes into ibuf.
       + */
       +int
       +spooltodisk(uchar *ibuf, int in, char **name)
       +{
       +        uchar buf[8192];
       +        int fd, n;
       +        char temp[40];
       +
       +        strcpy(temp, "/tmp/pagespoolXXXXXXXXX");
       +        fd = opentemp(temp);
       +        if(name)
       +                *name = estrdup(temp);
       +
       +        if(write(fd, ibuf, in) != in){
       +                fprint(2, "error writing temporary file\n");
       +                wexits("write temp");
       +        }
       +
       +        while((n = read(stdinfd, buf, sizeof buf)) > 0){
       +                if(write(fd, buf, n) != n){
       +                        fprint(2, "error writing temporary file\n");
       +                        wexits("write temp0");
       +                }
       +        }
       +        seek(fd, 0, 0);
       +        return fd;
       +}
       +
       +/*
       + * spool standard input into a pipe.
       + * we've already ready the first in bytes into ibuf
       + */
       +int
       +stdinpipe(uchar *ibuf, int in)
       +{
       +        uchar buf[8192];
       +        int n;
       +        int p[2];
       +        if(pipe(p) < 0){
       +                fprint(2, "pipe fails: %r\n");        
       +                wexits("pipe");
       +        }
       +
       +        switch(rfork(RFPROC|RFFDG)){
       +        case -1:
       +                fprint(2, "fork fails: %r\n");
       +                wexits("fork");
       +        default:
       +                close(p[1]);
       +                return p[0];
       +        case 0:
       +                break;
       +        }
       +
       +        close(p[0]);
       +        write(p[1], ibuf, in);
       +        while((n = read(stdinfd, buf, sizeof buf)) > 0)
       +                write(p[1], buf, n);
       +
       +        _exits(0);
       +        return -1;        /* not reached */
       +}
 (DIR) diff --git a/src/cmd/page/view.c b/src/cmd/page/view.c
       t@@ -0,0 +1,1022 @@
       +/*
       + * the actual viewer that handles screen stuff
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <cursor.h>
       +#include <event.h>
       +#include <bio.h>
       +#include <plumb.h>
       +#include <ctype.h>
       +#include <keyboard.h>
       +#include "page.h"
       +
       +Document *doc;
       +Image *im;
       +int page;
       +int upside = 0;
       +int showbottom = 0;                /* on the next showpage, move the image so the bottom is visible. */
       +
       +Rectangle ulrange;        /* the upper left corner of the image must be in this rectangle */
       +Point ul;                        /* the upper left corner of the image is at this point on the screen */
       +
       +Point pclip(Point, Rectangle);
       +Rectangle mkrange(Rectangle screenr, Rectangle imr);
       +void redraw(Image*);
       +
       +Cursor reading={
       +        {-1, -1},
       +        {0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, 
       +         0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, 
       +         0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, 
       +         0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
       +        {0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, 
       +         0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, 
       +         0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, 
       +         0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
       +};
       +
       +Cursor query = {
       +        {-7,-7},
       +        {0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 
       +         0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8, 
       +         0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0, 
       +         0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
       +        {0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c, 
       +         0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0, 
       +         0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80, 
       +         0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
       +};
       +
       +enum {
       +        Left = 1,
       +        Middle = 2,
       +        Right = 4,
       +
       +        RMenu = 3,
       +};
       +
       +void
       +unhide(void)
       +{
       +        static int wctl = -1;
       +
       +        if(wctl < 0)
       +                wctl = open("/dev/wctl", OWRITE);
       +        if(wctl < 0)
       +                return;
       +
       +        write(wctl, "unhide", 6);
       +}
       +
       +int 
       +max(int a, int b)
       +{
       +        return a > b ? a : b;
       +}
       +
       +int 
       +min(int a, int b)
       +{
       +        return a < b ? a : b;
       +}
       +
       +
       +char*
       +menugen(int n)
       +{
       +        static char menustr[32];
       +        char *p;
       +        int len;
       +
       +        if(n == doc->npage)
       +                return "exit";
       +        if(n > doc->npage)
       +                return nil;
       +
       +        if(reverse)
       +                n = doc->npage-1-n;
       +
       +        p = doc->pagename(doc, n);
       +        len = (sizeof menustr)-2;
       +
       +        if(strlen(p) > len && strrchr(p, '/'))
       +                p = strrchr(p, '/')+1;
       +        if(strlen(p) > len)
       +                p = p+strlen(p)-len;
       +
       +        strcpy(menustr+1, p);
       +        if(page == n)
       +                menustr[0] = '>';
       +        else
       +                menustr[0] = ' ';
       +        return menustr;
       +}
       +
       +void
       +showpage(int page, Menu *m)
       +{
       +        Image *tmp;
       +
       +        if(doc->fwdonly)
       +                m->lasthit = 0;        /* this page */
       +        else
       +                m->lasthit = reverse ? doc->npage-1-page : page;
       +        
       +        esetcursor(&reading);
       +        freeimage(im);
       +        if((page < 0 || page >= doc->npage) && !doc->fwdonly){
       +                im = nil;
       +                return;
       +        }
       +        im = doc->drawpage(doc, page);
       +        if(im == nil) {
       +                if(doc->fwdonly)        /* this is how we know we're out of pages */
       +                        wexits(0);
       +
       +                im = xallocimage(display, Rect(0,0,50,50), GREY1, 1, DBlack);
       +                if(im == nil) {
       +                        fprint(2, "out of memory: %r\n");
       +                        wexits("memory");
       +                }
       +                string(im, ZP, display->white, ZP, display->defaultfont, "?");
       +        }else if(resizing){
       +                resize(Dx(im->r), Dy(im->r));
       +        }
       +        if(im->r.min.x > 0 || im->r.min.y > 0) {
       +                tmp = xallocimage(display, Rect(0, 0, Dx(im->r), Dy(im->r)), im->chan, 0, DNofill);
       +                if(tmp == nil) {
       +                        fprint(2, "out of memory during showpage: %r\n");
       +                        wexits("memory");
       +                }
       +                drawop(tmp, tmp->r, im, nil, im->r.min, S);
       +                freeimage(im);
       +                im = tmp;
       +        }
       +
       +        if(upside)
       +                rot180(im);
       +
       +        esetcursor(nil);
       +        if(showbottom){
       +                ul.y = screen->r.max.y - Dy(im->r);
       +                showbottom = 0;
       +        }
       +
       +        redraw(screen);
       +        flushimage(display, 1);
       +}
       +
       +char*
       +writebitmap(void)
       +{
       +        char basename[64];
       +        char name[64+30];
       +        static char result[200];
       +        char *p, *q;
       +        int fd;
       +
       +        if(im == nil)
       +                return "no image";
       +
       +        memset(basename, 0, sizeof basename);
       +        if(doc->docname)
       +                strncpy(basename, doc->docname, sizeof(basename)-1);
       +        else if((p = menugen(page)) && p[0] != '\0')
       +                strncpy(basename, p+1, sizeof(basename)-1);
       +
       +        if(basename[0]) {
       +                if(q = strrchr(basename, '/'))
       +                        q++;
       +                else
       +                        q = basename;
       +                if(p = strchr(q, '.'))
       +                        *p = 0;
       +                
       +                memset(name, 0, sizeof name);
       +                snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1);
       +                if(access(name, 0) >= 0) {
       +                        strcat(name, "XXXX");
       +                        mktemp(name);
       +                }
       +                if(access(name, 0) >= 0)
       +                        return "couldn't think of a name for bitmap";
       +        } else {
       +                strcpy(name, "bitXXXX");
       +                mktemp(name);
       +                if(access(name, 0) >= 0) 
       +                        return "couldn't think of a name for bitmap";
       +        }
       +
       +        if((fd = create(name, OWRITE, 0666)) < 0) {
       +                snprint(result, sizeof result, "cannot create %s: %r", name);
       +                return result;
       +        }
       +
       +        if(writeimage(fd, im, 0) < 0) {
       +                snprint(result, sizeof result, "cannot writeimage: %r");
       +                close(fd);
       +                return result;
       +        }
       +        close(fd);
       +
       +        snprint(result, sizeof result, "wrote %s", name);
       +        return result;
       +}
       +
       +static void translate(Point);
       +
       +static int
       +showdata(Plumbmsg *msg)
       +{
       +        char *s;
       +
       +        s = plumblookup(msg->attr, "action");
       +        return s && strcmp(s, "showdata")==0;
       +}
       +
       +/* correspond to entries in miditems[] below,
       + * changing one means you need to change
       + */
       +enum{
       +        Restore = 0,
       +        Zin,
       +        Fit,
       +        Rot,
       +        Upside,
       +        Empty1,
       +        Next,
       +        Prev,
       +        Zerox,
       +        Empty2,
       +        Reverse,
       +        Del,
       +        Write,
       +        Empty3,
       +        Exit,
       +};
       + 
       +void
       +viewer(Document *dd)
       +{
       +        int i, fd, n, oldpage;
       +        int nxt;
       +        Menu menu, midmenu;
       +        Mouse m;
       +        Event e;
       +        Point dxy, oxy, xy0;
       +        Rectangle r;
       +        Image *tmp;
       +        static char *fwditems[] = { "this page", "next page", "exit", 0 };
       +         static char *miditems[] = {
       +                 "orig size",
       +                 "zoom in",
       +                 "fit window",
       +                 "rotate 90",
       +                 "upside down",
       +                 "",
       +                 "next",
       +                 "prev",
       +                "zerox",
       +                 "", 
       +                 "reverse",
       +                 "discard",
       +                 "write",
       +                 "", 
       +                 "quit", 
       +                 0 
       +         };
       +        char *s;
       +        enum { Eplumb = 4 };
       +        Plumbmsg *pm;
       +
       +        doc = dd;    /* save global for menuhit */
       +        ul = screen->r.min;
       +        einit(Emouse|Ekeyboard);
       +        if(doc->addpage != nil)
       +                eplumb(Eplumb, "image");
       +
       +        esetcursor(&reading);
       +        r.min = ZP;
       +
       +        /*
       +         * im is a global pointer to the current image.
       +         * eventually, i think we will have a layer between
       +         * the display routines and the ps/pdf/whatever routines
       +         * to perhaps cache and handle images of different
       +         * sizes, etc.
       +         */
       +        im = 0;
       +        page = reverse ? doc->npage-1 : 0;
       +
       +        if(doc->fwdonly) {
       +                menu.item = fwditems;
       +                menu.gen = 0;
       +                menu.lasthit = 0;
       +        } else {
       +                menu.item = 0;
       +                menu.gen = menugen;
       +                menu.lasthit = 0;
       +        }
       +
       +        midmenu.item = miditems;
       +        midmenu.gen = 0;
       +        midmenu.lasthit = Next;
       +
       +        showpage(page, &menu);
       +        esetcursor(nil);
       +
       +        nxt = 0;
       +        for(;;) {
       +                /*
       +                 * throughout, if doc->fwdonly is set, we restrict the functionality
       +                 * a fair amount.  we don't care about doc->npage anymore, and
       +                 * all that can be done is select the next page.
       +                 */
       +                switch(eread(Emouse|Ekeyboard|Eplumb, &e)){
       +                case Ekeyboard:
       +                        if(e.kbdc <= 0xFF && isdigit(e.kbdc)) {
       +                                nxt = nxt*10+e.kbdc-'0';
       +                                break;
       +                        } else if(e.kbdc != '\n')
       +                                nxt = 0;
       +                        switch(e.kbdc) {
       +                        case 'r':        /* reverse page order */
       +                                if(doc->fwdonly)
       +                                        break;
       +                                reverse = !reverse;
       +                                menu.lasthit = doc->npage-1-menu.lasthit;
       +
       +                                /*
       +                                 * the theory is that if we are reversing the
       +                                 * document order and are on the first or last
       +                                 * page then we're just starting and really want
       +                                   * to view the other end.  maybe the if
       +                                 * should be dropped and this should happen always.
       +                                 */
       +                                if(page == 0 || page == doc->npage-1) {
       +                                        page = doc->npage-1-page;
       +                                        showpage(page, &menu);
       +                                }
       +                                break;
       +                        case 'w':        /* write bitmap of current screen */
       +                                esetcursor(&reading);
       +                                s = writebitmap();
       +                                if(s)
       +                                        string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
       +                                                display->defaultfont, s);
       +                                esetcursor(nil);
       +                                flushimage(display, 1);
       +                                break;
       +                        case 'd':        /* remove image from working set */
       +                                if(doc->rmpage && page < doc->npage) {
       +                                        if(doc->rmpage(doc, page) >= 0) {
       +                                                if(doc->npage < 0)
       +                                                        wexits(0);
       +                                                if(page >= doc->npage)
       +                                                        page = doc->npage-1;
       +                                                showpage(page, &menu);
       +                                        }
       +                                }
       +                                break;
       +                        case 'q':
       +                        case 0x04: /* ctrl-d */
       +                                wexits(0);
       +                        case 'u':
       +                                if(im==nil)
       +                                        break;
       +                                esetcursor(&reading);
       +                                rot180(im);
       +                                esetcursor(nil);
       +                                upside = !upside;
       +                                redraw(screen);
       +                                flushimage(display, 1);
       +                                break;
       +                        case '-':
       +                        case '\b':
       +                        case Kleft:
       +                                if(page > 0 && !doc->fwdonly) {
       +                                        --page;
       +                                        showpage(page, &menu);
       +                                }
       +                                break;
       +                        case '\n':
       +                                if(nxt) {
       +                                        nxt--;
       +                                        if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly)
       +                                                showpage(page=nxt, &menu);
       +                                        nxt = 0;
       +                                        break;
       +                                }
       +                                goto Gotonext;
       +                        case Kright:
       +                        case ' ':
       +                        Gotonext:
       +                                if(doc->npage && ++page >= doc->npage && !doc->fwdonly)
       +                                        wexits(0);
       +                                showpage(page, &menu);
       +                                break;
       +
       +                        /*
       +                         * The upper y coordinate of the image is at ul.y in screen->r.
       +                         * Panning up means moving the upper left corner down.  If the
       +                         * upper left corner is currently visible, we need to go back a page.
       +                         */
       +                        case Kup:
       +                                if(screen->r.min.y <= ul.y && ul.y < screen->r.max.y){
       +                                        if(page > 0 && !doc->fwdonly){
       +                                                --page;
       +                                                showbottom = 1;
       +                                                showpage(page, &menu);
       +                                        }
       +                                } else {
       +                                        i = Dy(screen->r)/2;
       +                                        if(i > 10)
       +                                                i -= 10;
       +                                        if(i+ul.y > screen->r.min.y)
       +                                                i = screen->r.min.y - ul.y;
       +                                        translate(Pt(0, i));
       +                                }
       +                                break;
       +
       +                        /*
       +                         * If the lower y coordinate is on the screen, we go to the next page.
       +                         * The lower y coordinate is at ul.y + Dy(im->r).
       +                         */
       +                        case Kdown:
       +                                i = ul.y + Dy(im->r);
       +                                if(screen->r.min.y <= i && i <= screen->r.max.y){
       +                                        ul.y = screen->r.min.y;
       +                                        goto Gotonext;
       +                                } else {
       +                                        i = -Dy(screen->r)/2;
       +                                        if(i < -10)
       +                                                i += 10;
       +                                        if(i+ul.y+Dy(im->r) <= screen->r.max.y)
       +                                                i = screen->r.max.y - Dy(im->r) - ul.y - 1;
       +                                        translate(Pt(0, i));
       +                                }
       +                                break;
       +                        default:
       +                                esetcursor(&query);
       +                                sleep(1000);
       +                                esetcursor(nil);
       +                                break;        
       +                        }
       +                        break;
       +
       +                case Emouse:
       +                        m = e.mouse;
       +                        switch(m.buttons){
       +                        case Left:
       +                                oxy = m.xy;
       +                                xy0 = oxy;
       +                                do {
       +                                        dxy = subpt(m.xy, oxy);
       +                                        oxy = m.xy;        
       +                                        translate(dxy);
       +                                        m = emouse();
       +                                } while(m.buttons == Left);
       +                                if(m.buttons) {
       +                                        dxy = subpt(xy0, oxy);
       +                                        translate(dxy);
       +                                }
       +                                break;
       +        
       +                        case Middle:
       +                                if(doc->npage == 0)
       +                                        break;
       +
       +                                n = emenuhit(Middle, &m, &midmenu);
       +                                if(n == -1)
       +                                        break;
       +                                switch(n){
       +                                case Next:         /* next */
       +                                        if(reverse)
       +                                                page--;
       +                                        else
       +                                                page++;
       +                                        if(page < 0) {
       +                                                if(reverse) return;
       +                                                else page = 0;
       +                                        }
       +
       +                                        if((page >= doc->npage) && !doc->fwdonly)
       +                                                return;
       +        
       +                                        showpage(page, &menu);
       +                                        nxt = 0;
       +                                        break;
       +                                case Prev:        /* prev */
       +                                        if(reverse)
       +                                                page++;
       +                                        else
       +                                                page--;
       +                                        if(page < 0) {
       +                                                if(reverse) return;
       +                                                else page = 0;
       +                                        }
       +
       +                                        if((page >= doc->npage) && !doc->fwdonly && !reverse)
       +                                                return;
       +        
       +                                        showpage(page, &menu);
       +                                        nxt = 0;
       +                                        break;
       +                                case Zerox:        /* prev */
       +                                        zerox();
       +                                        break;
       +                                case Zin:        /* zoom in */
       +                                        {
       +                                                double delta;
       +                                                Rectangle r;
       +
       +                                                r = egetrect(Middle, &m);
       +                                                if((rectclip(&r, rectaddpt(im->r, ul)) == 0) ||
       +                                                        Dx(r) == 0 || Dy(r) == 0)
       +                                                        break;
       +                                                /* use the smaller side to expand */
       +                                                if(Dx(r) < Dy(r))
       +                                                        delta = (double)Dx(im->r)/(double)Dx(r);
       +                                                else
       +                                                        delta = (double)Dy(im->r)/(double)Dy(r);
       +
       +                                                esetcursor(&reading);
       +                                                tmp = xallocimage(display, 
       +                                                                Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta)), 
       +                                                                im->chan, 0, DBlack);
       +                                                if(tmp == nil) {
       +                                                        fprint(2, "out of memory during zoom: %r\n");
       +                                                        wexits("memory");
       +                                                }
       +                                                resample(im, tmp);
       +                                                freeimage(im);
       +                                                im = tmp;
       +                                                esetcursor(nil);
       +                                                ul = screen->r.min;
       +                                                redraw(screen);
       +                                                flushimage(display, 1);
       +                                                break;
       +                                        }
       +                                case Fit:        /* fit */
       +                                        {
       +                                                double delta;
       +                                                Rectangle r;
       +                                                
       +                                                delta = (double)Dx(screen->r)/(double)Dx(im->r);
       +                                                if((double)Dy(im->r)*delta > Dy(screen->r))
       +                                                        delta = (double)Dy(screen->r)/(double)Dy(im->r);
       +
       +                                                r = Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta));
       +                                                esetcursor(&reading);
       +                                                tmp = xallocimage(display, r, im->chan, 0, DBlack);
       +                                                if(tmp == nil) {
       +                                                        fprint(2, "out of memory during fit: %r\n");
       +                                                        wexits("memory");
       +                                                }
       +                                                resample(im, tmp);
       +                                                freeimage(im);
       +                                                im = tmp;
       +                                                esetcursor(nil);
       +                                                ul = screen->r.min;
       +                                                redraw(screen);
       +                                                flushimage(display, 1);
       +                                                break;
       +                                        }
       +                                case Rot:        /* rotate 90 */
       +                                        esetcursor(&reading);
       +                                        im = rot90(im);
       +                                        esetcursor(nil);
       +                                        redraw(screen);
       +                                        flushimage(display, 1);
       +                                        break;
       +                                case Upside:         /* upside-down */
       +                                        if(im==nil)
       +                                                break;
       +                                        esetcursor(&reading);
       +                                        rot180(im);
       +                                        esetcursor(nil);
       +                                        upside = !upside;
       +                                        redraw(screen);
       +                                        flushimage(display, 1);
       +                                        break;
       +                                case Restore:        /* restore */
       +                                        showpage(page, &menu);
       +                                        break;
       +                                case Reverse:        /* reverse */
       +                                        if(doc->fwdonly)
       +                                                break;
       +                                        reverse = !reverse;
       +                                        menu.lasthit = doc->npage-1-menu.lasthit;
       +        
       +                                        if(page == 0 || page == doc->npage-1) {
       +                                                page = doc->npage-1-page;
       +                                                showpage(page, &menu);
       +                                        }
       +                                        break;
       +                                case Write: /* write */
       +                                        esetcursor(&reading);
       +                                        s = writebitmap();
       +                                        if(s)
       +                                                string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
       +                                                        display->defaultfont, s);
       +                                        esetcursor(nil);
       +                                        flushimage(display, 1);
       +                                        break;
       +                                case Del: /* delete */
       +                                        if(doc->rmpage && page < doc->npage) {
       +                                                if(doc->rmpage(doc, page) >= 0) {
       +                                                        if(doc->npage < 0)
       +                                                                wexits(0);
       +                                                        if(page >= doc->npage)
       +                                                                page = doc->npage-1;
       +                                                        showpage(page, &menu);
       +                                                }
       +                                        }
       +                                        break;
       +                                case Exit:        /* exit */
       +                                        return;
       +                                case Empty1:
       +                                case Empty2:
       +                                case Empty3:
       +                                        break;
       +
       +                                }; 
       +
       +        
       +        
       +                        case Right:
       +                                if(doc->npage == 0)
       +                                        break;
       +
       +                                oldpage = page;
       +                                n = emenuhit(RMenu, &m, &menu);
       +                                if(n == -1)
       +                                        break;
       +        
       +                                if(doc->fwdonly) {
       +                                        switch(n){
       +                                        case 0:        /* this page */
       +                                                break;
       +                                        case 1:        /* next page */
       +                                                showpage(++page, &menu);
       +                                                break;
       +                                        case 2:        /* exit */
       +                                                return;
       +                                        }
       +                                        break;
       +                                }
       +        
       +                                if(n == doc->npage)
       +                                        return;
       +                                else
       +                                        page = reverse ? doc->npage-1-n : n;
       +        
       +                                if(oldpage != page)
       +                                        showpage(page, &menu);
       +                                nxt = 0;
       +                                break;
       +                        }
       +                        break;
       +
       +                case Eplumb:
       +                        pm = e.v;
       +                        if(pm->ndata <= 0){
       +                                plumbfree(pm);
       +                                break;
       +                        }
       +                        if(showdata(pm)) {
       +                                s = estrdup("/tmp/pageplumbXXXXXXX");
       +                                fd = opentemp(s);
       +                                write(fd, pm->data, pm->ndata);
       +                                /* lose fd reference on purpose; the file is open ORCLOSE */
       +                        } else if(pm->data[0] == '/') {
       +                                s = estrdup(pm->data);
       +                        } else {
       +                                s = emalloc(strlen(pm->wdir)+1+pm->ndata+1);
       +                                sprint(s, "%s/%s", pm->wdir, pm->data);
       +                                cleanname(s);
       +                        }
       +                        if((i = doc->addpage(doc, s)) >= 0) {
       +                                page = i;
       +                                unhide();
       +                                showpage(page, &menu);
       +                        }
       +                        free(s);
       +                        plumbfree(pm);
       +                        break;
       +                }
       +        }
       +}
       +
       +Image *gray;
       +
       +/*
       + * A draw operation that touches only the area contained in bot but not in top.
       + * mp and sp get aligned with bot.min.
       + */
       +static void
       +gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
       +        Image *src, Point sp, Image *mask, Point mp, int op)
       +{
       +        Rectangle r;
       +        Point origin;
       +        Point delta;
       +
       +        USED(op);
       +
       +        if(Dx(bot)*Dy(bot) == 0)
       +                return;
       +
       +        /* no points in bot - top */
       +        if(rectinrect(bot, top))
       +                return;
       +
       +        /* bot - top ≡ bot */
       +        if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
       +                gendrawop(dst, bot, src, sp, mask, mp, op);
       +                return;
       +        }
       +
       +        origin = bot.min;
       +        /* split bot into rectangles that don't intersect top */
       +        /* left side */
       +        if(bot.min.x < top.min.x){
       +                r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
       +                delta = subpt(r.min, origin);
       +                gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
       +                bot.min.x = top.min.x;
       +        }
       +
       +        /* right side */
       +        if(bot.max.x > top.max.x){
       +                r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
       +                delta = subpt(r.min, origin);
       +                gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
       +                bot.max.x = top.max.x;
       +        }
       +
       +        /* top */
       +        if(bot.min.y < top.min.y){
       +                r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
       +                delta = subpt(r.min, origin);
       +                gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
       +                bot.min.y = top.min.y;
       +        }
       +
       +        /* bottom */
       +        if(bot.max.y > top.max.y){
       +                r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
       +                delta = subpt(r.min, origin);
       +                gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
       +                bot.max.y = top.max.y;
       +        }
       +}
       +
       +static void
       +drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p, int op)
       +{
       +        gendrawdiff(dst, bot, top, src, p, mask, p, op);
       +}
       +
       +/*
       + * Translate the image in the window by delta.
       + */
       +static void
       +translate(Point delta)
       +{
       +        Point u;
       +        Rectangle r, or;
       +
       +        if(im == nil)
       +                return;
       +
       +        u = pclip(addpt(ul, delta), ulrange);
       +        delta = subpt(u, ul);
       +        if(delta.x == 0 && delta.y == 0)
       +                return;
       +
       +        /*
       +         * The upper left corner of the image is currently at ul.
       +         * We want to move it to u.
       +         */
       +        or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul);
       +        r = rectaddpt(or, delta);
       +
       +        drawop(screen, r, screen, nil, ul, S);
       +        ul = u;
       +
       +        /* fill in gray where image used to be but isn't. */
       +        drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP, S);
       +
       +        /* fill in black border */
       +        drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP, S);
       +
       +        /* fill in image where it used to be off the screen. */
       +        if(rectclip(&or, screen->r))
       +                drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min, S);
       +        else
       +                drawop(screen, r, im, nil, im->r.min, S);
       +        flushimage(display, 1);
       +}
       +
       +void
       +redraw(Image *screen)
       +{
       +        Rectangle r;
       +
       +        if(im == nil)
       +                return;
       +
       +        ulrange.max = screen->r.max;
       +        ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r)));
       +
       +        ul = pclip(ul, ulrange);
       +        drawop(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min)), S);
       +
       +        if(im->repl)
       +                return;
       +
       +        /* fill in any outer edges */
       +        /* black border */
       +        r = rectaddpt(im->r, subpt(ul, im->r.min));
       +        border(screen, r, -2, display->black, ZP);
       +        r.min = subpt(r.min, Pt(2,2));
       +        r.max = addpt(r.max, Pt(2,2));
       +
       +        /* gray for the rest */
       +        if(gray == nil) {
       +                gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF);
       +                if(gray == nil) {
       +                        fprint(2, "g out of memory: %r\n");
       +                        wexits("mem");
       +                }
       +        }
       +        border(screen, r, -4000, gray, ZP);
       +//        flushimage(display, 0);        
       +}
       +
       +void
       +eresized(int new)
       +{
       +        Rectangle r;
       +        r = screen->r;
       +        if(new && getwindow(display, Refnone) < 0)
       +                fprint(2,"can't reattach to window");
       +        ul = addpt(ul, subpt(screen->r.min, r.min));
       +        redraw(screen);
       +}
       +
       +/* clip p to be in r */
       +Point
       +pclip(Point p, Rectangle r)
       +{
       +        if(p.x < r.min.x)
       +                p.x = r.min.x;
       +        else if(p.x >= r.max.x)
       +                p.x = r.max.x-1;
       +
       +        if(p.y < r.min.y)
       +                p.y = r.min.y;
       +        else if(p.y >= r.max.y)
       +                p.y = r.max.y-1;
       +
       +        return p;
       +}
       +
       +/*
       + * resize is perhaps a misnomer. 
       + * this really just grows the window to be at least dx across
       + * and dy high.  if the window hits the bottom or right edge,
       + * it is backed up until it hits the top or left edge.
       + */
       +void
       +resize(int dx, int dy)
       +{
       +        static Rectangle sr;
       +        Rectangle r, or;
       +
       +        dx += 2*Borderwidth;
       +        dy += 2*Borderwidth;
       +        if(wctlfd < 0){
       +                wctlfd = open("/dev/wctl", OWRITE);
       +                if(wctlfd < 0)
       +                        return;
       +        }
       +
       +        r = insetrect(screen->r, -Borderwidth);
       +        if(Dx(r) >= dx && Dy(r) >= dy)
       +                return;
       +
       +        if(Dx(sr)*Dy(sr) == 0)
       +                sr = screenrect();
       +
       +        or = r;
       +
       +        r.max.x = max(r.min.x+dx, r.max.x);
       +        r.max.y = max(r.min.y+dy, r.max.y);
       +        if(r.max.x > sr.max.x){
       +                if(Dx(r) > Dx(sr)){
       +                        r.min.x = 0;
       +                        r.max.x = sr.max.x;
       +                }else
       +                        r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0));
       +        }
       +        if(r.max.y > sr.max.y){
       +                if(Dy(r) > Dy(sr)){
       +                        r.min.y = 0;
       +                        r.max.y = sr.max.y;
       +                }else
       +                        r = rectaddpt(r, Pt(0, sr.max.y-r.max.y));
       +        }
       +
       +        /*
       +         * Sometimes we can't actually grow the window big enough,
       +         * and resizing it to the same shape makes it flash.
       +         */
       +        if(Dx(r) == Dx(or) && Dy(r) == Dy(or))
       +                return;
       +
       +        fprint(wctlfd, "resize -minx %d -miny %d -maxx %d -maxy %d\n",
       +                r.min.x, r.min.y, r.max.x, r.max.y);
       +}
       +
       +/*
       + * If we allocimage after a resize but before flushing the draw buffer,
       + * we won't have seen the reshape event, and we won't have called
       + * getwindow, and allocimage will fail.  So we flushimage before every alloc.
       + */
       +Image*
       +xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val)
       +{
       +        flushimage(display, 0);
       +        return allocimage(d, r, chan, repl, val);
       +}
       +
       +/* all code below this line should be in the library, but is stolen from colors instead */
       +static char*
       +rdenv(char *name)
       +{
       +        char *v;
       +        int fd, size;
       +
       +        fd = open(name, OREAD);
       +        if(fd < 0)
       +                return 0;
       +        size = seek(fd, 0, 2);
       +        v = malloc(size+1);
       +        if(v == 0){
       +                fprint(2, "page: can't malloc: %r\n");
       +                wexits("no mem");
       +        }
       +        seek(fd, 0, 0);
       +        read(fd, v, size);
       +        v[size] = 0;
       +        close(fd);
       +        return v;
       +}
       +
       +Rectangle
       +screenrect(void)
       +{
       +        int fd;
       +        char buf[12*5];
       +
       +        fd = open("/dev/screen", OREAD);
       +        if(fd == -1)
       +                fd=open("/mnt/term/dev/screen", OREAD);
       +        if(fd == -1){
       +                fprint(2, "page: can't open /dev/screen: %r\n");
       +                wexits("window read");
       +        }
       +        if(read(fd, buf, sizeof buf) != sizeof buf){
       +                fprint(2, "page: can't read /dev/screen: %r\n");
       +                wexits("screen read");
       +        }
       +        close(fd);
       +        return Rect(atoi(buf+12), atoi(buf+24), atoi(buf+36), atoi(buf+48));
       +}
       +
       +void
       +zerox(void)
       +{
       +        int pfd[2];
       +
       +        pipe(pfd);
       +        switch(rfork(RFFDG|RFPROC)) {
       +                case -1:
       +                        wexits("cannot fork in zerox: %r");
       +                case 0: 
       +                        dup(pfd[1], 0);
       +                        close(pfd[0]);
       +                        execl("/bin/page", "page", "-w", 0);
       +                        wexits("cannot exec in zerox: %r\n");
       +                default:
       +                        close(pfd[1]);
       +                        writeimage(pfd[0], im, 0);
       +                        close(pfd[0]);
       +                        break;
       +        }
       +}