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