9 typedef struct Page Page;
39 Image *frame, *paper, *ground;
41 char pagespool[] = "/tmp/pagespool.";
50 char *pagemenugen(int i);
85 {0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00,
86 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0,
87 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0,
88 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
89 {0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00,
90 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0,
91 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40,
92 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
95 void showpage(Page *);
96 void drawpage(Page *);
97 Point pagesize(Page *);
100 addpage(Page *up, char *label, int (*popen)(Page *), void *pdata, int fd)
104 p = mallocz(sizeof(*p), 1);
105 p->label = strdup(label);
119 up->down = up->tail = p;
127 if(up && current == up)
133 resizewin(Point size)
137 if((wctl = open("/dev/wctl", OWRITE)) < 0)
140 size = addpt(size, Pt(Borderwidth*2, Borderwidth*2));
141 fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y);
150 snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id++);
151 return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
155 catchnote(void *, char *msg)
157 if(strstr(msg, "sys: write on closed pipe"))
159 if(strstr(msg, "hangup"))
161 if(strstr(msg, "alarm"))
174 for(mfd = 0; fd >= 0; fd = va_arg(arg, int), mfd++)
179 if((fd = open("/fd", OREAD)) < 0)
180 sysfatal("open: %r");
181 n = dirreadall(fd, &dir);
183 if(strstr(dir[i].name, "ctl"))
185 fd = atoi(dir[i].name);
193 pipeline(int fd, char *fmt, ...)
195 char buf[NPATH], *argv[4];
204 switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
210 dupfds(fd, pfd[1], 2, -1);
212 vsnprint(buf, sizeof buf, fmt, arg);
218 exec("/bin/rc", argv);
219 sysfatal("exec: %r");
230 if(x = strrchr(s, '/'))
245 if((fd = dup(p->fd, -1)) < 0){
253 pipeline(fd, "%s", (char*)p->data);
256 * dont keep the file descriptor arround if it can simply
259 fd2path(p->fd, nam, sizeof(nam));
260 if(strncmp(nam, pagespool, strlen(pagespool))){
263 p->data = strdup(nam);
275 pipeline(p->fd, "%s", (char*)p->data);
285 char mnt[32], cmd[64], *argv[4];
288 snprint(mnt, sizeof(mnt), "/n/tapefs.%.12d%.8lux", getpid(), (ulong)p);
289 switch(rfork(RFPROC|RFFDG|RFREND)){
295 dupfds(p->fd, 1, 2, -1);
298 snprint(cmd, sizeof(cmd), "%s -m %s /fd/0", p->data, mnt);
301 exec("/bin/rc", argv);
302 sysfatal("exec: %r");
307 p->data = strdup(mnt);
315 char buf[NPATH], *s, *e;
321 e = buf+sizeof(buf)-1;
322 s += snprint(s, e-s, "%s/", (char*)p->data);
325 pipeline(fd, "awk '/\\<rootfile/{"
326 "if(match($0, /full\\-path\\=\\\"([^\\\"]+)\\\"/)){"
327 "print substr($0, RSTART+11,RLENGTH-12);exit}}'");
328 n = read(fd, s, e - s);
332 while(n > 0 && s[n-1] == '\n')
336 if((fd = open(buf, OREAD)) < 0)
338 pipeline(fd, "awk '/\\<item/{"
339 "if(match($0, /id\\=\\\"([^\\\"]+)\\\"/)){"
340 "id=substr($0, RSTART+4, RLENGTH-5);"
341 "if(match($0, /href\\=\\\"([^\\\"]+)\\\"/)){"
342 "item[id]=substr($0, RSTART+6, RLENGTH-7)}}};"
344 "if(match($0, /idref\\=\\\"([^\\\"]+)\\\"/)){"
345 "ref=substr($0, RSTART+7, RLENGTH-8);"
346 "print item[ref]; fflush}}'");
347 s = strrchr(buf, '/')+1;
348 while((n = read(fd, s, e-s)) > 0){
349 while(n > 0 && s[n-1] == '\n')
352 addpage(p, shortname(buf), popenfile, strdup(buf), -1);
358 typedef struct Ghost Ghost;
377 switch(rfork(RFPROC|RFFDG|RFMEM|RFNOWAIT)){
385 dupfds(gs->pdat, gs->pin, pfd[1], -1);
386 fprint(1, "%s DoPDFPage\n"
389 "dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
390 "flushfile\n", p->label);
391 while((n = read(0, buf, sizeof buf)) > 0){
392 if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
404 infernobithdr(char *buf, int n)
407 if(memcmp(buf, "compressed\n", 11) == 0)
409 if(strtochan((char*)buf))
411 if(memcmp(buf, " ", 10) == 0 &&
412 '0' <= buf[10] && buf[10] <= '9' &&
422 int n, i, pdf, ifd, ofd, pin[2], pout[2], pdat[2];
423 char buf[NBUF], nam[32], *argv[16];
429 if(read(ifd, buf, 5) != 5)
432 if(memcmp(buf, "%PDF-", 5) == 0)
452 switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
457 dupfds(pin[1], pout[1], nullfd, pdat[1], ifd, -1);
459 dupfds(nullfd, nullfd, nullfd, pdat[1], ifd, -1);
461 pipeline(4, "%s", (char*)p->data);
464 argv[2] = "-sDEVICE=plan9";
465 argv[3] = "-sOutputFile=/fd/3";
467 argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
469 argv[7] = "-dTextAlphaBits=4";
470 argv[8] = "-dGraphicsAlphaBits=4";
471 snprint(buf, sizeof buf, "-r%d", ppi);
473 argv[10] = "-dDOINTERPOLATE";
474 argv[11] = pdf ? "-" : "/fd/4";
476 exec("/bin/gs", argv);
477 sysfatal("exec: %r");
488 "/PAGEOUT (/fd/1) (w) file def\n"
489 "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
493 "/PDFSave null def\n"
494 "/DSCPageCount 0 def\n"
495 "/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
497 "GS_PDF_ProcSet begin\n"
499 "(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
501 "pdfpagecount PAGE==\n";
504 if(write(pin[0], prolog, n) != n)
506 if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
514 gs = mallocz(sizeof(*gs), 1);
519 snprint(nam, sizeof nam, "%d", i);
520 addpage(p, nam, popenpdf, gs, -1);
523 /* keep ghostscript arround */
528 while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
529 if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
530 snprint(nam, sizeof nam, "%d", i);
531 addpage(p, nam, popenimg, nil, ofd);
537 snprint(nam, sizeof nam, "%.4d", ++i);
538 if((ofd = createtmp(nam)) < 0)
539 ofd = dup(nullfd, -1);
541 if(write(ofd, buf, n) != n)
555 filetype(char *buf, int nbuf, char *typ, int ntyp)
557 int n, ifd[2], ofd[2];
560 if(infernobithdr(buf, nbuf)){
561 strncpy(typ, "image/p9bit", ntyp);
573 if(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT) == 0){
574 dupfds(ifd[1], ofd[1], 2, -1);
578 exec("/bin/file", argv);
582 if(rfork(RFPROC|RFFDG|RFNOWAIT) == 0){
588 if((n = readn(ofd[0], typ, ntyp-1)) < 0)
591 while(n > 0 && typ[n-1] == '\n')
598 dircmp(void *p1, void *p2)
605 return strcmp(d1->name, d2->name);
616 "application/pdf", popengs, nil,
617 "application/postscript", popengs, nil,
618 "application/troff", popengs, "lp -dstdout",
619 "text/plain", popengs, "lp -dstdout",
620 "text/html", popengs, "uhtml | html2ms | tbl | troff -ms | lp -dstdout",
621 "application/dvi", popengs, "dvips -Pps -r0 -q1 -f1",
622 "application/doc", popengs, "doc2ps",
623 "application/zip", popentape, "fs/zipfs",
624 "application/x-tar", popentape, "fs/tarfs",
625 "application/x-ustar", popentape, "fs/tarfs",
626 "application/x-compress", popenfilter, "uncompress",
627 "application/x-gzip", popenfilter, "gunzip",
628 "application/x-bzip2", popenfilter, "bunzip2",
629 "image/gif", popenimg, "gif -t9",
630 "image/jpeg", popenimg, "jpg -t9",
631 "image/png", popenimg, "png -t9",
632 "image/ppm", popenimg, "ppm -t9",
633 "image/bmp", popenimg, "bmp -t9",
634 "image/p9bit", popenimg, nil,
637 char buf[NBUF], typ[128], *file;
646 if((fd = open(file, OREAD)) < 0){
653 if((d = dirfstat(fd)) == nil){
662 snprint(buf, sizeof(buf), "%s/META-INF/container.xml", file);
663 if((tfd = open(buf, OREAD)) >= 0){
671 if((n = dirreadall(fd, &d)) < 0)
673 qsort(d, n, sizeof d[0], dircmp);
675 addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
681 memset(buf, 0, NBUF/2);
682 if((n = readn(fd, buf, NBUF/2)) <= 0)
684 filetype(buf, n, typ, sizeof(typ));
685 for(i=0; i<nelem(tab); i++)
686 if(strncmp(typ, tab[i].typ, strlen(tab[i].typ)) == 0)
689 werrstr("unknown image format: %s", typ);
693 p->data = tab[i].data;
694 p->open = tab[i].open;
695 if(seek(fd, 0, 0) < 0)
697 if((i = read(fd, buf+n, n)) < 0)
699 if(i != n || memcmp(buf, buf+n, i)){
702 if((tfd = createtmp("file")) < 0)
705 if(write(tfd, buf, n) != n)
707 if((n = read(fd, buf, sizeof(buf))) < 0)
710 if(dup(tfd, fd) < 0){
741 for(p = root->down; p; p = t)
742 if((t = nextpage(p)) == x)
754 if(p->open == nil || (fd = p->open(p)) < 0)
758 pipeline(fd, "rotate -r %d", rotate);
760 pipeline(fd, "resize -x %d", resize.x);
762 pipeline(fd, "resize -y %d", resize.y);
772 if(p->open && p->image == nil){
773 if((fd = openpage(p)) >= 0){
775 p->image = readimage(display, fd, 1);
787 if(p->open == nil || p->image == nil)
789 lockdisplay(display);
791 unlockdisplay(display);
800 for(p = root->down; p; p = nextpage(p)){
801 if(age == 0) /* synchronous flush */
803 else if(!canqlock(p))
805 if((pagegen - p->gen) >= age)
812 loadpages(Page *p, int ahead, int oviewgen)
816 ahead++; /* load at least one */
817 unloadpages(ahead*2);
818 for(i = 0; i < ahead && p; p = nextpage(p), i++){
819 if(viewgen != oviewgen)
823 if(viewgen != oviewgen){
833 if(size.x && size.y && newwin){
837 lockdisplay(display);
839 unlockdisplay(display);
847 * A draw operation that touches only the area contained in bot but not in top.
848 * mp and sp get aligned with bot.min.
851 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
852 Image *src, Point sp, Image *mask, Point mp, int op)
860 if(Dx(bot)*Dy(bot) == 0)
863 /* no points in bot - top */
864 if(rectinrect(bot, top))
867 /* bot - top ≡ bot */
868 if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
869 gendrawop(dst, bot, src, sp, mask, mp, op);
874 /* split bot into rectangles that don't intersect top */
876 if(bot.min.x < top.min.x){
877 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
878 delta = subpt(r.min, origin);
879 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
880 bot.min.x = top.min.x;
884 if(bot.max.x > top.max.x){
885 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
886 delta = subpt(r.min, origin);
887 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
888 bot.max.x = top.max.x;
892 if(bot.min.y < top.min.y){
893 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
894 delta = subpt(r.min, origin);
895 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
896 bot.min.y = top.min.y;
900 if(bot.max.y > top.max.y){
901 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
902 delta = subpt(r.min, origin);
903 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
904 bot.max.y = top.max.y;
909 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *s, Point sp, int f)
916 gendrawdiff(d, r, top, paper, sp, nil, ZP, SoverD);
917 gendrawdiff(d, r, top, s, sp, nil, ZP, SoverD);
921 if(r.min.x < d->r.min.x){
922 sp.x += (d->r.min.x - r.min.x)/f;
923 a.x = (d->r.min.x - r.min.x)%f;
924 r.min.x = d->r.min.x;
926 if(r.min.y < d->r.min.y){
927 sp.y += (d->r.min.y - r.min.y)/f;
928 a.y = (d->r.min.y - r.min.y)%f;
929 r.min.y = d->r.min.y;
932 w = s->r.max.x - sp.x;
935 t = allocimage(display, Rect(r.min.x, r.min.y, r.min.x+w, r.max.y), s->chan, 0, 0);
938 for(y=r.min.y; y<r.max.y; y++){
939 draw(t, Rect(r.min.x, y, r.min.x+w, y+1), s, nil, sp);
946 for(sp=r.min; x<r.max.x; sp.x++){
947 gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, paper, sp, nil, ZP, SoverD);
948 gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, t, sp, nil, ZP, SoverD);
949 for(x++; ++a.x<f && x<r.max.x; x++)
950 gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, d,
951 Pt(x-1, r.min.y), nil, ZP, SoverD);
960 return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
964 drawframe(Rectangle r)
966 border(screen, r, -Borderwidth, frame, ZP);
967 gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), ground, ZP, nil, ZP, SoverD);
968 flushimage(display, 1);
978 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
979 zoomdraw(screen, r, ZR, i, i->r.min, zoom);
981 r = Rpt(ZP, stringsize(font, p->label));
982 r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2),
983 divpt(r.max, 2)), screen->r.min));
984 draw(screen, r, paper, nil, ZP);
985 string(screen, r.min, display->black, ZP, font, p->label);
991 translate(Page *p, Point d)
997 if(i==0 || d.x==0 && d.y==0)
999 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1000 pos = addpt(pos, d);
1001 nr = rectaddpt(r, d);
1002 rectclip(&r, screen->r);
1003 draw(screen, rectaddpt(r, d), screen, nil, r.min);
1004 zoomdraw(screen, nr, rectaddpt(r, d), i, i->r.min, zoom);
1009 findpage(char *name)
1015 /* look in current document first */
1016 if(current && current->up){
1017 for(p = current->up->down; p; p = p->next)
1018 if(cistrncmp(p->label, name, n) == 0)
1021 /* look everywhere */
1022 for(p = root->down; p; p = nextpage(p))
1023 if(cistrncmp(p->label, name, n) == 0)
1033 for(p = root->down; i > 0 && p; p = nextpage(p))
1044 for(i = 0, p = root->down; p && p != x; p = nextpage(p))
1046 return (p == x) ? i : -1;
1066 esetcursor(&reading);
1072 switch(rfork(RFPROC|RFMEM)){
1074 sysfatal("rfork: %r");
1076 loadpages(p, NAHEAD, oviewgen);
1086 for(p = nextpage(current); p; p = nextpage(p))
1087 if(p->image || p->open)
1097 for(p = prevpage(current); p; p = prevpage(p))
1098 if(p->image || p->open)
1106 char nam[64], *argv[4];
1111 esetcursor(&reading);
1113 if((fd = openpage(p)) < 0)
1115 if(rfork(RFPROC|RFFDG|RFREND|RFENVG|RFNOTEG|RFNOWAIT) == 0){
1116 dupfds(fd, 1, 2, -1);
1117 snprint(nam, sizeof nam, "/bin/%s", argv0);
1122 sysfatal("exec: %r");
1135 lockdisplay(display);
1136 if(new && getwindow(display, Refnone) == -1)
1137 sysfatal("getwindow: %r");
1144 unlockdisplay(display);
1147 void killcohort(void)
1150 for(i=0;i!=3;i++){ /* It's a long way to the kitchen */
1151 postnote(PNGROUP, getpid(), "kill");
1156 void drawerr(Display *, char *msg)
1158 sysfatal("draw: %s", msg);
1164 fprint(2, "usage: %s [ -iRw ] [ -p ppi ] [ file ... ]\n", argv0);
1169 main(int argc, char *argv[])
1171 enum { Eplumb = 4 };
1196 ppi = atoi(EARGF(usage()));
1203 * so that we can stop all subprocesses with a note,
1204 * and to isolate rendezvous from other processes
1206 rfork(RFNOTEG|RFNAMEG|RFREND);
1208 atnotify(catchnote, 1);
1210 s = smprint("-pid %d", getpid());
1211 if(newwindow(s) < 0)
1212 sysfatal("newwindow: %r");
1215 initdraw(drawerr, nil, argv0);
1216 paper = display->white;
1217 frame = display->black;
1218 ground = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
1219 display->locking = 1;
1220 unlockdisplay(display);
1221 einit(Ekeyboard|Emouse);
1222 eplumb(Eplumb, "image");
1223 if((nullfd = open("/dev/null", ORDWR)) < 0)
1224 sysfatal("open: %r");
1225 current = root = addpage(nil, "root", nil, nil, -1);
1227 if(*argv == nil && !imode)
1228 addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
1229 for(; *argv; argv++)
1230 addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
1237 lockdisplay(display);
1240 if(current == nil || !canqlock(current))
1245 if((m.buttons & 1) == 0)
1247 translate(current, subpt(m.xy, o));
1254 i = emenuhit(2, &m, &menu);
1255 if(i < 0 || i >= nelem(menuitems) || menuitems[i]==nil)
1258 if(strcmp(s, "orig size")==0){
1265 unlockdisplay(display);
1266 esetcursor(&reading);
1271 if(strncmp(s, "rotate ", 7)==0){
1272 rotate += atoi(s+7);
1276 if(strcmp(s, "upside down")==0){
1280 if(strcmp(s, "fit width")==0){
1283 resize = subpt(screen->r.max, screen->r.min);
1287 if(strcmp(s, "fit height")==0){
1290 resize = subpt(screen->r.max, screen->r.min);
1294 if(strncmp(s, "zoom", 4)==0){
1295 if(current && canqlock(current)){
1296 o = subpt(o, screen->r.min);
1297 if(strstr(s, "in")){
1298 if(zoom < 0x40000000){
1300 pos = addpt(mulpt(subpt(pos, o), 2), o);
1305 pos = addpt(divpt(subpt(pos, o), 2), o);
1312 unlockdisplay(display);
1313 if(strcmp(s, "next")==0)
1315 if(strcmp(s, "prev")==0)
1317 if(strcmp(s, "zerox")==0)
1319 if(strcmp(s, "quit")==0)
1324 if(root->down == nil)
1326 pagemenu.lasthit = pageindex(current);
1327 i = emenuhit(3, &m, &pagemenu);
1328 unlockdisplay(display);
1330 showpage(pageat(i));
1334 unlockdisplay(display);
1343 if(current == nil || !canqlock(current))
1345 lockdisplay(display);
1347 translate(current, Pt(0, Dy(screen->r)/2));
1348 unlockdisplay(display);
1352 unlockdisplay(display);
1354 if(prevpage(current))
1362 if(current == nil || !canqlock(current))
1364 o = addpt(pos, pagesize(current));
1365 lockdisplay(display);
1366 if(o.y > Dy(screen->r)){
1367 translate(current, Pt(0, -Dy(screen->r)/2));
1368 unlockdisplay(display);
1372 unlockdisplay(display);
1374 if(nextpage(current))
1378 showpage(findpage(jump));
1388 if(i+1 < sizeof(jump)){
1396 if(pm && pm->ndata > 0){
1400 s = plumblookup(pm->attr, "action");
1401 if(s && strcmp(s, "quit")==0)
1403 if(s && strcmp(s, "showdata")==0){
1404 if((fd = createtmp("plumb")) < 0){
1405 fprint(2, "plumb: createtmp: %r\n");
1409 if(fd2path(fd, s, NPATH) < 0){
1413 write(fd, pm->data, pm->ndata);
1414 }else if(pm->data[0] == '/'){
1415 s = strdup(pm->data);
1417 s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
1418 sprint(s, "%s/%s", pm->wdir, pm->data);
1421 showpage(addpage(root, shortname(s), popenfile, s, fd));