9 typedef struct Page Page;
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);
120 up->down = up->tail = p;
128 if(up && current == up)
134 resizewin(Point size)
138 if((wctl = open("/dev/wctl", OWRITE)) < 0)
141 size = addpt(size, Pt(Borderwidth*2, Borderwidth*2));
142 fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y);
147 createtmp(ulong id, char *pfx)
151 snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id);
152 return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
156 catchnote(void *, char *msg)
158 if(strstr(msg, "sys: write on closed pipe"))
160 if(strstr(msg, "hangup"))
162 if(strstr(msg, "alarm"))
168 pipeline(int fd, char *fmt, ...)
170 char buf[NPATH], *argv[4];
179 switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
193 vsnprint(buf, sizeof buf, fmt, arg);
200 exec("/bin/rc", argv);
201 sysfatal("exec: %r");
212 if(x = strrchr(s, '/'))
227 if((fd = dup(p->fd, -1)) < 0){
235 pipeline(fd, "%s", (char*)p->data);
238 * dont keep the file descriptor arround if it can simply
241 fd2path(p->fd, nam, sizeof(nam));
242 if(strncmp(nam, pagespool, strlen(pagespool))){
245 p->data = strdup(nam);
255 char mnt[32], cmd[64], *argv[4];
258 snprint(mnt, sizeof(mnt), "/n/tapefs.%.12d%.8lux", getpid(), (ulong)p);
259 switch(rfork(RFREND|RFPROC|RFFDG)){
269 snprint(cmd, sizeof(cmd), "%s -m %s /fd/0", p->data, mnt);
272 exec("/bin/rc", argv);
273 sysfatal("exec: %r");
278 p->data = strdup(mnt);
286 char buf[NPATH], *s, *e;
292 e = buf+sizeof(buf)-1;
293 s += snprint(s, e-s, "%s/", (char*)p->data);
296 pipeline(fd, "awk '/\\<rootfile/{"
297 "if(match($0, /full\\-path\\=\\\"([^\\\"]+)\\\"/)){"
298 "print substr($0, RSTART+11,RLENGTH-12);exit}}'");
299 n = read(fd, s, e - s);
303 while(n > 0 && s[n-1] == '\n')
307 if((fd = open(buf, OREAD)) < 0)
309 pipeline(fd, "awk '/\\<item/{"
310 "if(match($0, /id\\=\\\"([^\\\"]+)\\\"/)){"
311 "id=substr($0, RSTART+4, RLENGTH-5);"
312 "if(match($0, /href\\=\\\"([^\\\"]+)\\\"/)){"
313 "item[id]=substr($0, RSTART+6, RLENGTH-7)}}};"
315 "if(match($0, /idref\\=\\\"([^\\\"]+)\\\"/)){"
316 "ref=substr($0, RSTART+7, RLENGTH-8);"
317 "print item[ref]; fflush}}'");
318 s = strrchr(buf, '/')+1;
319 while((n = read(fd, s, e-s)) > 0){
320 while(n > 0 && s[n-1] == '\n')
323 addpage(p, shortname(buf), popenfile, strdup(buf), -1);
326 p->text = strdup(p->label);
330 typedef struct Ghost Ghost;
349 switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
358 fprint(gs->pin, "%s DoPDFPage\n"
361 "dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
362 "flushfile\n", p->label);
363 while((n = read(gs->pdat, buf, sizeof buf)) > 0){
364 if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
368 if(write(pfd[1], buf, n) != n){
381 infernobithdr(char *buf, int n)
384 if(memcmp(buf, "compressed\n", 11) == 0)
386 if(strtochan((char*)buf))
388 if(memcmp(buf, " ", 10) == 0 &&
389 '0' <= buf[10] && buf[10] <= '9' &&
399 int n, i, pdf, ifd, ofd, pin[2], pout[2], pdat[2];
400 char buf[NBUF], nam[32], *argv[16];
406 if(read(ifd, buf, 5) != 5)
409 if(memcmp(buf, "%PDF-", 5) == 0)
411 p->text = strdup(p->label);
430 switch(rfork(RFREND|RFPROC|RFFDG|RFNOWAIT)){
437 if(dup(pout[1], 1)<0)
447 if(dup(pdat[1], 3)<0)
461 pipeline(4, "%s", (char*)p->data);
465 argv[2] = "-sDEVICE=plan9";
466 argv[3] = "-sOutputFile=/fd/3";
468 argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
470 argv[7] = "-dTextAlphaBits=4";
471 argv[8] = "-dGraphicsAlphaBits=4";
472 snprint(buf, sizeof buf, "-r%d", ppi);
474 argv[10] = "-dDOINTERPOLATE";
475 argv[11] = pdf ? "-" : "/fd/4";
477 exec("/bin/gs", argv);
478 sysfatal("exec: %r");
489 "/PAGEOUT (/fd/1) (w) file def\n"
490 "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
494 "/PDFSave null def\n"
495 "/DSCPageCount 0 def\n"
496 "/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
498 "GS_PDF_ProcSet begin\n"
500 "(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
502 "pdfpagecount PAGE==\n";
505 if(write(pin[0], prolog, n) != n)
507 if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
515 gs = mallocz(sizeof(*gs), 1);
520 snprint(nam, sizeof nam, "%d", i);
521 addpage(p, nam, popenpdf, gs, -1);
524 /* keep ghostscript arround */
529 while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
530 if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
531 snprint(nam, sizeof nam, "%d", i);
532 addpage(p, nam, popenconv, nil, ofd);
538 snprint(nam, sizeof nam, "%.4d", ++i);
539 if((ofd = createtmp((ulong)p, nam)) < 0)
540 ofd = dup(nullfd, -1);
542 if(write(ofd, buf, n) != n)
556 dircmp(void *p1, void *p2)
563 return strcmp(d1->name, d2->name);
569 char buf[NBUF], *file;
577 if((fd = open(file, OREAD)) < 0){
585 if((d = dirfstat(fd)) == nil){
594 snprint(buf, sizeof(buf), "%s/META-INF/container.xml", file);
595 if((tfd = open(buf, OREAD)) >= 0){
602 if((n = dirreadall(fd, &d)) < 0)
604 qsort(d, n, sizeof d[0], dircmp);
606 addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
608 p->text = strdup(p->label);
613 memset(buf, 0, 32+1);
614 if((n = read(fd, buf, 32)) <= 0)
620 if(memcmp(buf, "%PDF-", 5) == 0 || strstr(buf, "%!"))
622 else if(memcmp(buf, "x T ", 4) == 0){
623 p->data = "lp -dstdout";
626 else if(cistrncmp(buf, "<?xml", 5) == 0 ||
627 cistrncmp(buf, "<!DOCTYPE", 9) == 0 ||
628 cistrncmp(buf, "<HTML", 5) == 0){
629 p->data = "htmlfmt -c utf8 | lp -dstdout";
632 else if(memcmp(buf, "\xF7\x02\x01\x83\x92\xC0\x1C;", 8) == 0){
633 p->data = "dvips -Pps -r0 -q1 -f1";
636 else if(memcmp(buf, "\x1F\x8B", 2) == 0){
640 else if(memcmp(buf, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 8) == 0){
644 else if(memcmp(buf, "PK\x03\x04", 4) == 0){
645 p->data = "fs/zipfs";
647 }else if(memcmp(buf, "GIF", 3) == 0)
649 else if(memcmp(buf, "\111\111\052\000", 4) == 0)
650 p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
651 else if(memcmp(buf, "\115\115\000\052", 4) == 0)
652 p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
653 else if(memcmp(buf, "\377\330\377", 3) == 0)
655 else if(memcmp(buf, "\211PNG\r\n\032\n", 3) == 0)
657 else if(memcmp(buf, "\0PC Research, Inc", 17) == 0)
658 p->data = "aux/g3p9bit -g";
659 else if(memcmp(buf, "TYPE=ccitt-g31", 14) == 0)
660 p->data = "aux/g3p9bit -g";
661 else if(memcmp(buf, "II*", 3) == 0)
662 p->data = "aux/g3p9bit -g";
663 else if(memcmp(buf, "TYPE=", 5) == 0)
664 p->data = "fb/3to1 rgbv |fb/pcp -tplan9";
665 else if(buf[0] == 'P' && '0' <= buf[1] && buf[1] <= '9')
667 else if(memcmp(buf, "BM", 2) == 0)
669 else if(infernobithdr(buf, n))
672 werrstr("unknown image format");
676 if(seek(fd, 0, 0) < 0)
678 if((i = read(fd, buf+n, n)) < 0)
680 if(i != n || memcmp(buf, buf+n, i)){
683 if((tfd = createtmp((ulong)p, "file")) < 0)
686 if(write(tfd, buf, n) != n)
688 if((n = read(fd, buf, sizeof(buf))) < 0)
691 if(dup(tfd, fd) < 0){
722 for(p = root->down; p; p = t)
723 if((t = nextpage(p)) == x)
735 if(p->open == nil || (fd = p->open(p)) < 0)
739 pipeline(fd, "rotate -r %d", rotate);
741 pipeline(fd, "resize -x %d", resize.x);
743 pipeline(fd, "resize -y %d", resize.y);
753 if(p->open && p->image == nil && p->text == nil){
754 if((fd = openpage(p)) >= 0){
756 p->image = readimage(display, fd, 1);
759 if(p->image == nil && p->text == nil)
760 p->text = smprint("%s: %r", p->label);
773 lockdisplay(display);
775 unlockdisplay(display);
786 for(p = root->down; p; p = nextpage(p)){
787 if(age == 0) /* synchronous flush */
789 else if(!canqlock(p))
791 if((pagegen - p->gen) >= age)
798 loadpages(Page *p, int ahead, int oviewgen)
802 ahead++; /* load at least one */
803 unloadpages(ahead*2);
804 for(i = 0; i < ahead && p; p = nextpage(p), i++){
805 if(viewgen != oviewgen)
809 if(viewgen != oviewgen){
819 if(size.x && size.y && newwin){
823 lockdisplay(display);
825 unlockdisplay(display);
833 * A draw operation that touches only the area contained in bot but not in top.
834 * mp and sp get aligned with bot.min.
837 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
838 Image *src, Point sp, Image *mask, Point mp, int op)
846 if(Dx(bot)*Dy(bot) == 0)
849 /* no points in bot - top */
850 if(rectinrect(bot, top))
853 /* bot - top ≡ bot */
854 if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
855 gendrawop(dst, bot, src, sp, mask, mp, op);
860 /* split bot into rectangles that don't intersect top */
862 if(bot.min.x < top.min.x){
863 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
864 delta = subpt(r.min, origin);
865 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
866 bot.min.x = top.min.x;
870 if(bot.max.x > top.max.x){
871 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
872 delta = subpt(r.min, origin);
873 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
874 bot.max.x = top.max.x;
878 if(bot.min.y < top.min.y){
879 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
880 delta = subpt(r.min, origin);
881 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
882 bot.min.y = top.min.y;
886 if(bot.max.y > top.max.y){
887 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
888 delta = subpt(r.min, origin);
889 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
890 bot.max.y = top.max.y;
895 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *s, Point sp, int f)
902 gendrawdiff(d, r, top, s, sp, nil, ZP, S);
906 if(r.min.x < d->r.min.x){
907 sp.x += (d->r.min.x - r.min.x)/f;
908 a.x = (d->r.min.x - r.min.x)%f;
909 r.min.x = d->r.min.x;
911 if(r.min.y < d->r.min.y){
912 sp.y += (d->r.min.y - r.min.y)/f;
913 a.y = (d->r.min.y - r.min.y)%f;
914 r.min.y = d->r.min.y;
917 w = s->r.max.x - sp.x;
920 t = allocimage(display, Rect(r.min.x, r.min.y, r.min.x+w, r.max.y), s->chan, 0, DNofill);
923 for(y=r.min.y; y<r.max.y; y++){
924 draw(t, Rect(r.min.x, y, r.min.x+w, y+1), s, nil, sp);
931 for(x=r.min.x; x<r.max.x; x++){
932 gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, t, sp, nil, ZP, S);
944 return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
953 if((i = p->image) == nil){
956 if((s = p->text) == nil)
959 r.max = stringsize(font, p->text);
960 r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2), divpt(r.max, 2)),
962 draw(screen, r, display->white, nil, ZP);
963 string(screen, r.min, display->black, ZP, font, s);
965 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
966 zoomdraw(screen, r, ZR, i, i->r.min, zoom);
968 gendrawdiff(screen, screen->r, r, background, ZP, nil, ZP, S);
969 border(screen, r, -Borderwidth, display->black, ZP);
970 flushimage(display, 1);
974 translate(Page *p, Point d)
980 if((i==0) || (d.x==0 && d.y==0))
982 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
984 nr = rectaddpt(r, d);
986 rectclip(&or, screen->r);
987 draw(screen, rectaddpt(or, d), screen, nil, or.min);
988 zoomdraw(screen, nr, rectaddpt(or, d), i, i->r.min, zoom);
989 gendrawdiff(screen, screen->r, nr, background, ZP, nil, ZP, S);
990 border(screen, nr, -Borderwidth, display->black, ZP);
991 flushimage(display, 1);
1001 /* look in current document first */
1002 if(current && current->up){
1003 for(p = current->up->down; p; p = p->next)
1004 if(cistrncmp(p->label, name, n) == 0)
1007 /* look everywhere */
1008 for(p = root->down; p; p = nextpage(p))
1009 if(cistrncmp(p->label, name, n) == 0)
1019 for(p = root->down; i > 0 && p; p = nextpage(p))
1030 for(i = 0, p = root->down; p && p != x; p = nextpage(p))
1032 return (p == x) ? i : -1;
1052 esetcursor(&reading);
1058 switch(rfork(RFPROC|RFMEM)){
1060 sysfatal("rfork: %r");
1062 loadpages(p, NAHEAD, oviewgen);
1070 char nam[64], *argv[4];
1075 esetcursor(&reading);
1077 if((fd = openpage(p)) < 0)
1079 if(rfork(RFREND|RFFDG|RFPROC|RFENVG|RFNOTEG|RFNOWAIT) == 0){
1083 snprint(nam, sizeof nam, "/bin/%s", argv0);
1088 sysfatal("exec: %r");
1101 lockdisplay(display);
1102 if(new && getwindow(display, Refnone) == -1)
1103 sysfatal("getwindow: %r");
1110 unlockdisplay(display);
1113 void killcohort(void)
1116 for(i=0;i!=3;i++){ /* It's a long way to the kitchen */
1117 postnote(PNGROUP, getpid(), "kill");
1122 void drawerr(Display *, char *msg)
1124 sysfatal("draw: %s", msg);
1130 fprint(2, "usage: %s [ -iRw ] [ -p ppi ] [ file ... ]\n", argv0);
1135 main(int argc, char *argv[])
1137 enum { Eplumb = 4 };
1162 ppi = atoi(EARGF(usage()));
1169 * so that we can stop all subprocesses with a note,
1170 * and to isolate rendezvous from other processes
1172 rfork(RFNOTEG|RFNAMEG|RFREND);
1174 atnotify(catchnote, 1);
1176 s = smprint("-pid %d", getpid());
1177 if(newwindow(s) < 0)
1178 sysfatal("newwindow: %r");
1181 initdraw(drawerr, nil, argv0);
1182 background = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
1183 draw(screen, screen->r, background, nil, ZP);
1184 flushimage(display, 1);
1185 display->locking = 1;
1186 unlockdisplay(display);
1187 einit(Ekeyboard|Emouse);
1188 eplumb(Eplumb, "image");
1189 nullfd = open("/dev/null", ORDWR);
1190 current = root = addpage(nil, "root", nil, nil, -1);
1192 if(*argv == nil && !imode)
1193 addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
1194 for(; *argv; argv++)
1195 addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
1202 lockdisplay(display);
1205 if(current == nil || !canqlock(current))
1210 if((m.buttons & 1) == 0)
1212 translate(current, subpt(m.xy, o));
1218 i = emenuhit(2, &m, &menu);
1219 if(i < 0 || i >= nelem(menuitems) || menuitems[i]==nil)
1222 if(strcmp(s, "orig size")==0){
1229 unlockdisplay(display);
1230 esetcursor(&reading);
1235 if(strncmp(s, "rotate ", 7)==0){
1236 rotate += atoi(s+7);
1240 if(strcmp(s, "upside down")==0){
1244 if(strcmp(s, "fit width")==0){
1247 resize = subpt(screen->r.max, screen->r.min);
1251 if(strcmp(s, "fit height")==0){
1254 resize = subpt(screen->r.max, screen->r.min);
1258 if(strncmp(s, "zoom", 4)==0){
1259 if(current && canqlock(current)){
1260 o = subpt(m.xy, screen->r.min);
1261 if(strstr(s, "in")){
1262 if(zoom < 0x40000000){
1264 pos = addpt(mulpt(subpt(pos, o), 2), o);
1269 pos = addpt(divpt(subpt(pos, o), 2), o);
1276 unlockdisplay(display);
1277 if(strcmp(s, "next")==0)
1278 showpage(nextpage(current));
1279 if(strcmp(s, "prev")==0)
1280 showpage(prevpage(current));
1281 if(strcmp(s, "zerox")==0)
1283 if(strcmp(s, "quit")==0)
1288 if(root->down == nil)
1290 pagemenu.lasthit = pageindex(current);
1291 i = emenuhit(3, &m, &pagemenu);
1292 unlockdisplay(display);
1294 showpage(pageat(i));
1298 unlockdisplay(display);
1307 if(current == nil || !canqlock(current))
1309 lockdisplay(display);
1311 translate(current, Pt(0, Dy(screen->r)/2));
1312 unlockdisplay(display);
1316 unlockdisplay(display);
1318 if(prevpage(current))
1323 showpage(prevpage(current));
1326 if(current == nil || !canqlock(current))
1328 o = addpt(pos, pagesize(current));
1329 lockdisplay(display);
1330 if(o.y > Dy(screen->r)){
1331 translate(current, Pt(0, -Dy(screen->r)/2));
1332 unlockdisplay(display);
1336 unlockdisplay(display);
1338 if(nextpage(current))
1342 showpage(findpage(jump));
1348 showpage(nextpage(current));
1352 if(i+1 < sizeof(jump)){
1360 if(pm && pm->ndata > 0){
1364 s = plumblookup(pm->attr, "action");
1365 if(s && strcmp(s, "quit")==0)
1367 if(s && strcmp(s, "showdata")==0){
1368 static ulong plumbid;
1370 if((fd = createtmp(plumbid++, "plumb")) < 0){
1371 fprint(2, "plumb: createtmp: %r\n");
1375 if(fd2path(fd, s, NPATH) < 0){
1379 write(fd, pm->data, pm->ndata);
1380 }else if(pm->data[0] == '/'){
1381 s = strdup(pm->data);
1383 s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
1384 sprint(s, "%s/%s", pm->wdir, pm->data);
1387 showpage(addpage(root, shortname(s), popenfile, s, fd));