From 24c02865d8fcc97d1fb5cb9281810d8074aa5eb1 Mon Sep 17 00:00:00 2001 From: rsc Date: Tue, 4 Jan 2005 21:23:50 +0000 Subject: placeholder; does not yet build --- src/cmd/page/filter.c | 107 +++++ src/cmd/page/gfx.c | 331 +++++++++++++++ src/cmd/page/gs.c | 342 ++++++++++++++++ src/cmd/page/mkfile | 23 ++ src/cmd/page/nrotate.c | 277 +++++++++++++ src/cmd/page/page.c | 236 +++++++++++ src/cmd/page/page.h | 84 ++++ src/cmd/page/pdf.c | 155 +++++++ src/cmd/page/pdfprolog.c | 29 ++ src/cmd/page/ps.c | 450 ++++++++++++++++++++ src/cmd/page/rotate.c | 474 +++++++++++++++++++++ src/cmd/page/util.c | 131 ++++++ src/cmd/page/view.c | 1022 ++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 3661 insertions(+) create mode 100644 src/cmd/page/filter.c create mode 100644 src/cmd/page/gfx.c create mode 100644 src/cmd/page/gs.c create mode 100644 src/cmd/page/mkfile create mode 100644 src/cmd/page/nrotate.c create mode 100644 src/cmd/page/page.c create mode 100644 src/cmd/page/page.h create mode 100644 src/cmd/page/pdf.c create mode 100644 src/cmd/page/pdfprolog.c create mode 100644 src/cmd/page/ps.c create mode 100644 src/cmd/page/rotate.c create mode 100644 src/cmd/page/util.c create mode 100644 src/cmd/page/view.c (limited to 'src/cmd/page') 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 +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#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; iextra; + + assert((name == nil) ^ (buf == nil)); + assert(name != nil || doc->npage == 0); + + for(i=0; inpage; 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; inpage; 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 +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#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 + +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 +#include +#include +#include +#include +#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; igs, "%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 { % pdfshowpage_mysetpage \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 % 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 +#include +#include +#include +#include +#include +#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; inpage = 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 +#include +#include +#include +#include +#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; granr); 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; granr); 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= 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= 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; ichan){ + 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; ir); 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; ir)); i++){ + nscan[i] = malloc(bpl); + if(nscan[i] == nil) + sysfatal("can't allocate: %r"); + } + + /* resample in X */ + nchan = from->depth/8; + for(i=0; ir); i++){ + for(j=0; jchan==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; ir), nscan, ysize); + + /* pack data into destination */ + bpl = bytesperline(to->r, from->depth); + for(i=0; ir); 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 +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#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; + } +} -- cgit v1.2.3