]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/page.c
handle initdraw errors
[plan9front.git] / sys / src / cmd / page.c
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <event.h>
5 #include <cursor.h>
6 #include <keyboard.h>
7 #include <plumb.h>
8
9 typedef struct Page Page;
10 struct Page {
11         char    *label;
12
13         QLock;
14         void    *data;
15         int     (*open)(Page *);
16
17         Image   *image;
18         int     fd;
19         int     gen;
20
21         Page    *up;
22         Page    *next;
23         Page    *down;
24         Page    *tail;
25 };
26
27 int zoom = 1;
28 int ppi = 100;
29 int imode;
30 int newwin;
31 int rotate;
32 int viewgen;
33 int pagegen;
34 Point resize, pos;
35 Page *root, *current;
36 QLock pagelock;
37 int nullfd;
38
39 Image *frame, *paper, *ground;
40
41 char pagespool[] = "/tmp/pagespool.";
42
43 enum {
44         NPROC = 4,
45         NAHEAD = 2,
46         NBUF = 8*1024,
47         NPATH = 1024,
48 };
49
50 char *pagemenugen(int i);
51
52 char *menuitems[] = {
53         "orig size",
54         "rotate 90",
55         "upside down",
56         "",
57         "fit width",
58         "fit height",
59         "",
60         "zoom in",
61         "zoom out",
62         "",
63         "next",
64         "prev",
65         "zerox",
66         "write",
67         "",
68         "quit",
69         nil
70 };
71
72 Menu pagemenu = {
73         nil,
74         pagemenugen,
75         -1,
76 };
77
78 Menu menu = {
79         menuitems,
80         nil,
81         -1,
82 };
83
84 Cursor reading = {
85         {-1, -1},
86         {0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, 
87          0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, 
88          0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, 
89          0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
90         {0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, 
91          0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, 
92          0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, 
93          0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
94 };
95
96 void showpage(Page *);
97 void drawpage(Page *);
98 Point pagesize(Page *);
99
100 Page*
101 addpage(Page *up, char *label, int (*popen)(Page *), void *pdata, int fd)
102 {
103         Page *p;
104
105         p = mallocz(sizeof(*p), 1);
106         p->label = strdup(label);
107         p->gen = pagegen;
108         p->image = nil;
109         p->data = pdata;
110         p->open = popen;
111         p->fd = fd;
112
113         p->down = nil;
114         p->tail = nil;
115         p->next = nil;
116
117         qlock(&pagelock);
118         if(p->up = up){
119                 if(up->tail == nil)
120                         up->down = up->tail = p;
121                 else {
122                         up->tail->next = p;
123                         up->tail = p;
124                 }
125         }
126         qunlock(&pagelock);
127
128         if(up && current == up)
129                 showpage(p);
130         return p;
131 }
132
133 void
134 resizewin(Point size)
135 {
136         int wctl;
137
138         if((wctl = open("/dev/wctl", OWRITE)) < 0)
139                 return;
140         /* add rio border */
141         size = addpt(size, Pt(Borderwidth*2, Borderwidth*2));
142         fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y);
143         close(wctl);
144 }
145
146 int
147 createtmp(char *pfx)
148 {
149         static ulong id = 1;
150         char nam[64];
151         snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id++);
152         return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
153 }
154
155 int
156 catchnote(void *, char *msg)
157 {
158         if(strstr(msg, "sys: write on closed pipe"))
159                 return 1;
160         if(strstr(msg, "hangup"))
161                 return 1;
162         if(strstr(msg, "alarm"))
163                 return 1;
164         return 0;
165 }
166
167 void
168 dupfds(int fd, ...)
169 {
170         int mfd, n, i;
171         va_list arg;
172         Dir *dir;
173
174         va_start(arg, fd);
175         for(mfd = 0; fd >= 0; fd = va_arg(arg, int), mfd++)
176                 if(fd != mfd)
177                         if(dup(fd, mfd) < 0)
178                                 sysfatal("dup: %r");
179         va_end(arg);
180         if((fd = open("/fd", OREAD)) < 0)
181                 sysfatal("open: %r");
182         n = dirreadall(fd, &dir);
183         for(i=0; i<n; i++){
184                 if(strstr(dir[i].name, "ctl"))
185                         continue;
186                 fd = atoi(dir[i].name);
187                 if(fd >= mfd)
188                         close(fd);
189         }
190         free(dir);
191 }
192
193 void
194 pipeline(int fd, char *fmt, ...)
195 {
196         char buf[NPATH], *argv[4];
197         va_list arg;
198         int pfd[2];
199
200         if(pipe(pfd) < 0){
201         Err:
202                 dup(nullfd, fd);
203                 return;
204         }
205         va_start(arg, fmt);
206         vsnprint(buf, sizeof buf, fmt, arg);
207         va_end(arg);
208         switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
209         case -1:
210                 close(pfd[0]);
211                 close(pfd[1]);
212                 goto Err;
213         case 0:
214                 dupfds(fd, pfd[1], 2, -1);
215                 argv[0] = "rc";
216                 argv[1] = "-c";
217                 argv[2] = buf;
218                 argv[3] = nil;
219                 exec("/bin/rc", argv);
220                 sysfatal("exec: %r");
221         }
222         close(pfd[1]);
223         dup(pfd[0], fd);
224         close(pfd[0]);
225 }
226
227 char*
228 shortname(char *s)
229 {
230         char *x;
231         if(x = strrchr(s, '/'))
232                 if(x[1] != 0)
233                         return x+1;
234         return s;
235 }
236
237 int
238 popenfile(Page*);
239
240 int
241 popenimg(Page *p)
242 {
243         char nam[NPATH];
244         int fd;
245
246         if((fd = dup(p->fd, -1)) < 0){
247                 close(p->fd);
248                 p->fd = -1;
249                 return -1;
250         }
251
252         seek(fd, 0, 0);
253         if(p->data)
254                 pipeline(fd, "%s", (char*)p->data);
255
256         /*
257          * dont keep the file descriptor arround if it can simply
258          * be reopened.
259          */
260         fd2path(p->fd, nam, sizeof(nam));
261         if(strncmp(nam, pagespool, strlen(pagespool))){
262                 close(p->fd);
263                 p->fd = -1;
264                 p->data = strdup(nam);
265                 p->open = popenfile;
266         }
267
268         return fd;
269 }
270
271 int
272 popenfilter(Page *p)
273 {
274         seek(p->fd, 0, 0);
275         if(p->data){
276                 pipeline(p->fd, "%s", (char*)p->data);
277                 p->data = nil;
278         }
279         p->open = popenfile;
280         return p->open(p);
281 }
282
283 int
284 popentape(Page *p)
285 {
286         char mnt[32], cmd[64], *argv[4];
287
288         seek(p->fd, 0, 0);
289         snprint(mnt, sizeof(mnt), "/n/tapefs.%.12d%.8lux", getpid(), (ulong)p);
290         snprint(cmd, sizeof(cmd), "%s -m %s /fd/0", p->data, mnt);
291         switch(rfork(RFPROC|RFMEM|RFFDG|RFREND)){
292         case -1:
293                 close(p->fd);
294                 p->fd = -1;
295                 return -1;
296         case 0:
297                 dupfds(p->fd, 1, 2, -1);
298                 argv[0] = "rc";
299                 argv[1] = "-c";
300                 argv[2] = cmd;
301                 argv[3] = nil;
302                 exec("/bin/rc", argv);
303                 sysfatal("exec: %r");
304         }
305         close(p->fd);
306         waitpid();
307         p->fd = -1;
308         p->data = strdup(mnt);
309         p->open = popenfile;
310         return p->open(p);
311 }
312
313 int
314 popenepub(Page *p)
315 {
316         char buf[NPATH], *s, *e;
317         int n, fd;
318
319         fd = p->fd;
320         p->fd = -1;
321         s = buf;
322         e = buf+sizeof(buf)-1;
323         s += snprint(s, e-s, "%s/", (char*)p->data);
324         free(p->data);
325         p->data = nil;
326         pipeline(fd, "awk '/\\<rootfile/{"
327                 "if(match($0, /full\\-path\\=\\\"([^\\\"]+)\\\"/)){"
328                 "print substr($0, RSTART+11,RLENGTH-12);exit}}'");
329         n = read(fd, s, e - s);
330         close(fd);
331         if(n <= 0)
332                 return -1;
333         while(n > 0 && s[n-1] == '\n')
334                 n--;
335         s += n;
336         *s = 0;
337         if((fd = open(buf, OREAD)) < 0)
338                 return -1;
339         pipeline(fd, "awk '/\\<item/{"
340                 "if(match($0, /id\\=\\\"([^\\\"]+)\\\"/)){"
341                 "id=substr($0, RSTART+4, RLENGTH-5);"
342                 "if(match($0, /href\\=\\\"([^\\\"]+)\\\"/)){"
343                 "item[id]=substr($0, RSTART+6, RLENGTH-7)}}};"
344                 "/\\<itemref/{"
345                 "if(match($0, /idref\\=\\\"([^\\\"]+)\\\"/)){"
346                 "ref=substr($0, RSTART+7, RLENGTH-8);"
347                 "print item[ref]; fflush}}'");
348         s = strrchr(buf, '/')+1;
349         while((n = read(fd, s, e-s)) > 0){
350                 while(n > 0 && s[n-1] == '\n')
351                         n--;
352                 s[n] = 0;
353                 addpage(p, shortname(buf), popenfile, strdup(buf), -1);
354         }
355         close(fd);
356         return -1;
357 }
358
359 typedef struct Ghost Ghost;
360 struct Ghost
361 {
362         QLock;
363
364         int     pin;
365         int     pout;
366         int     pdat;
367 };
368
369 int
370 popenpdf(Page *p)
371 {
372         char buf[NBUF];
373         int n, pfd[2];
374         Ghost *gs;
375
376         if(pipe(pfd) < 0)
377                 return -1;
378         switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)){
379         case -1:
380                 close(pfd[0]);
381                 close(pfd[1]);
382                 return -1;
383         case 0:
384                 gs = p->data;
385                 qlock(gs);
386                 dupfds(gs->pdat, gs->pin, pfd[1], -1);
387                 fprint(1, "%s DoPDFPage\n"
388                         "(/fd/3) (w) file "
389                         "dup flushfile "
390                         "dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
391                         "flushfile\n", p->label);
392                 while((n = read(0, buf, sizeof buf)) > 0){
393                         if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
394                                 break;
395                         write(2, buf, n);
396                 }
397                 qunlock(gs);
398                 exits(nil);
399         }
400         close(pfd[1]);
401         return pfd[0];
402 }
403
404 int
405 infernobithdr(char *buf, int n)
406 {
407         if(n >= 11){
408                 if(memcmp(buf, "compressed\n", 11) == 0)
409                         return 1;
410                 if(strtochan((char*)buf))
411                         return 1;
412                 if(memcmp(buf, "          ", 10) == 0 && 
413                         '0' <= buf[10] && buf[10] <= '9' &&
414                         buf[11] == ' ')
415                         return 1;
416         }
417         return 0;
418 }
419
420 int
421 popengs(Page *p)
422 {
423         int n, i, pdf, ifd, ofd, pin[2], pout[2], pdat[2];
424         char buf[NBUF], nam[32], *argv[16];
425
426         pdf = 0;
427         ifd = p->fd;
428         p->fd = -1;
429         seek(ifd, 0, 0);
430         if(read(ifd, buf, 5) != 5)
431                 goto Err0;
432         seek(ifd, 0, 0);
433         if(memcmp(buf, "%PDF-", 5) == 0)
434                 pdf = 1;
435         if(pipe(pin) < 0){
436         Err0:
437                 close(ifd);
438                 return -1;
439         }
440         if(pipe(pout) < 0){
441         Err1:
442                 close(pin[0]);
443                 close(pin[1]);
444                 goto Err0;
445         }
446         if(pipe(pdat) < 0){
447         Err2:
448                 close(pdat[0]);
449                 close(pdat[1]);
450                 goto Err1;
451         }
452
453         argv[0] = p->data;
454         switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
455         case -1:
456                 goto Err2;
457         case 0:
458                 if(pdf)
459                         dupfds(pin[1], pout[1], 2, pdat[1], ifd, -1);
460                 else
461                         dupfds(nullfd, nullfd, 2, pdat[1], ifd, -1);
462                 if(argv[0])
463                         pipeline(4, "%s", argv[0]);
464                 argv[0] = "gs";
465                 argv[1] = "-q";
466                 argv[2] = "-sDEVICE=plan9";
467                 argv[3] = "-sOutputFile=/fd/3";
468                 argv[4] = "-dBATCH";
469                 argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
470                 argv[6] = "-dQUIET";
471                 argv[7] = "-dTextAlphaBits=4";
472                 argv[8] = "-dGraphicsAlphaBits=4";
473                 snprint(buf, sizeof buf, "-r%d", ppi);
474                 argv[9] = buf;
475                 argv[10] = "-dDOINTERPOLATE";
476                 argv[11] = pdf ? "-" : "/fd/4";
477                 argv[12] = nil;
478                 exec("/bin/gs", argv);
479                 sysfatal("exec: %r");
480         }
481
482         close(pin[1]);
483         close(pout[1]);
484         close(pdat[1]);
485         close(ifd);
486
487         if(pdf){
488                 Ghost *gs;
489                 char *prolog =
490                         "/PAGEOUT (/fd/1) (w) file def\n"
491                         "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
492                         "\n"
493                         "/Page null def\n"
494                         "/Page# 0 def\n"
495                         "/PDFSave null def\n"
496                         "/DSCPageCount 0 def\n"
497                         "/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
498                         "\n"
499                         "GS_PDF_ProcSet begin\n"
500                         "pdfdict begin\n"
501                         "(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
502                         "\n"
503                         "pdfpagecount PAGE==\n";
504
505                 n = strlen(prolog);
506                 if(write(pin[0], prolog, n) != n)
507                         goto Out;
508                 if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
509                         goto Out;
510                 buf[n] = 0;
511                 n = atoi(buf);
512                 if(n <= 0){
513                         werrstr("no pages");
514                         goto Out;
515                 }
516                 gs = mallocz(sizeof(*gs), 1);
517                 gs->pin = pin[0];
518                 gs->pout = pout[0];
519                 gs->pdat = pdat[0];
520                 for(i=1; i<=n; i++){
521                         snprint(nam, sizeof nam, "%d", i);
522                         addpage(p, nam, popenpdf, gs, -1);
523                 }
524
525                 /* keep ghostscript arround */
526                 return -1;
527         } else {
528                 i = 0;
529                 ofd = -1;
530                 while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
531                         if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
532                                 snprint(nam, sizeof nam, "%d", i);
533                                 addpage(p, nam, popenimg, nil, ofd);
534                                 ofd = -1;
535                         }
536                         if(n <= 0)
537                                 break;
538                         if(ofd < 0){
539                                 snprint(nam, sizeof nam, "%.4d", ++i);
540                                 if((ofd = createtmp(nam)) < 0)
541                                         ofd = dup(nullfd, -1);
542                         }
543                         if(write(ofd, buf, n) != n)
544                                 break;
545                 }
546                 if(ofd >= 0)
547                         close(ofd);
548         }
549 Out:
550         close(pin[0]);
551         close(pout[0]);
552         close(pdat[0]);
553         return -1;
554 }
555
556 int
557 filetype(char *buf, int nbuf, char *typ, int ntyp)
558 {
559         int n, ifd[2], ofd[2];
560         char *argv[3];
561
562         if(infernobithdr(buf, nbuf)){
563                 strncpy(typ, "image/p9bit", ntyp);
564                 return 0;
565         }
566
567         typ[0] = 0;
568         if(pipe(ifd) < 0)
569                 return -1;
570         if(pipe(ofd) < 0){
571                 close(ifd[0]);
572                 close(ifd[1]);
573                 return -1;
574         }
575         if(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT) == 0){
576                 dupfds(ifd[1], ofd[1], 2, -1);
577                 argv[0] = "file";
578                 argv[1] = "-m";
579                 argv[2] = 0;
580                 exec("/bin/file", argv);
581         }
582         close(ifd[1]);
583         close(ofd[1]);
584         if(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT) == 0){
585                 dupfds(ifd[0], -1);
586                 write(0, buf, nbuf);
587                 exits(nil);
588         }
589         close(ifd[0]);
590         if((n = readn(ofd[0], typ, ntyp-1)) < 0)
591                 n = 0;
592         close(ofd[0]);
593         while(n > 0 && typ[n-1] == '\n')
594                 n--;
595         typ[n] = 0;
596         return 0;
597 }
598
599 int
600 dircmp(void *p1, void *p2)
601 {
602         Dir *d1, *d2;
603
604         d1 = p1;
605         d2 = p2;
606
607         return strcmp(d1->name, d2->name);
608 }
609
610 int
611 popenfile(Page *p)
612 {
613         static struct {
614                 char    *typ;
615                 void    *open;
616                 void    *data;
617         } tab[] = {
618         "application/pdf",              popengs,        nil,
619         "application/postscript",       popengs,        nil,
620         "application/troff",            popengs,        "lp -dstdout",
621         "text/plain",                   popengs,        "lp -dstdout",
622         "text/html",                    popengs,        "uhtml | html2ms | tbl | troff -ms | lp -dstdout",
623         "application/dvi",              popengs,        "dvips -Pps -r0 -q1 -f1",
624         "application/doc",              popengs,        "doc2ps",
625         "application/zip",              popentape,      "fs/zipfs",
626         "application/x-tar",            popentape,      "fs/tarfs",
627         "application/x-ustar",          popentape,      "fs/tarfs",
628         "application/x-compress",       popenfilter,    "uncompress",
629         "application/x-gzip",           popenfilter,    "gunzip",
630         "application/x-bzip2",          popenfilter,    "bunzip2",
631         "image/gif",                    popenimg,       "gif -t9",
632         "image/jpeg",                   popenimg,       "jpg -t9",
633         "image/png",                    popenimg,       "png -t9",
634         "image/ppm",                    popenimg,       "ppm -t9",
635         "image/bmp",                    popenimg,       "bmp -t9",
636         "image/p9bit",                  popenimg,       nil,
637         };
638
639         char buf[NBUF], typ[128], *file;
640         int i, n, fd, tfd;
641         Dir *d;
642
643         fd = p->fd;
644         p->fd = -1;
645         file = p->data;
646         p->data = nil;
647         if(fd < 0){
648                 if((fd = open(file, OREAD)) < 0){
649                 Err0:
650                         free(file);
651                         return -1;
652                 }
653         }
654         seek(fd, 0, 0);
655         if((d = dirfstat(fd)) == nil){
656         Err1:
657                 close(fd);
658                 goto Err0;
659         }
660         if(d->mode & DMDIR){
661                 free(d);
662                 d = nil;
663
664                 snprint(buf, sizeof(buf), "%s/META-INF/container.xml", file);
665                 if((tfd = open(buf, OREAD)) >= 0){
666                         close(fd);
667                         p->fd = tfd;
668                         p->data = file;
669                         p->open = popenepub;
670                         return p->open(p);
671                 }
672
673                 if((n = dirreadall(fd, &d)) < 0)
674                         goto Err1;
675                 qsort(d, n, sizeof d[0], dircmp);
676                 for(i = 0; i<n; i++)
677                         addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
678                 free(d);
679                 goto Err1;
680         }
681         free(d);
682
683         memset(buf, 0, NBUF/2);
684         if((n = readn(fd, buf, NBUF/2)) <= 0)
685                 goto Err1;
686         filetype(buf, n, typ, sizeof(typ));
687         for(i=0; i<nelem(tab); i++)
688                 if(strncmp(typ, tab[i].typ, strlen(tab[i].typ)) == 0)
689                         break;
690         if(i == nelem(tab)){
691                 werrstr("unknown image format: %s", typ);
692                 goto Err1;
693         }
694         p->fd = fd;
695         p->data = tab[i].data;
696         p->open = tab[i].open;
697         if(seek(fd, 0, 0) < 0)
698                 goto Noseek;
699         if((i = readn(fd, buf+n, n)) < 0)
700                 goto Err1;
701         if(i != n || memcmp(buf, buf+n, i)){
702                 n += i;
703         Noseek:
704                 if((tfd = createtmp("file")) < 0)
705                         goto Err1;
706                 while(n > 0){
707                         if(write(tfd, buf, n) != n)
708                                 goto Err2;
709                         if((n = read(fd, buf, sizeof(buf))) < 0)
710                                 goto Err2;
711                 }
712                 if(dup(tfd, fd) < 0){
713                 Err2:
714                         close(tfd);
715                         goto Err1;
716                 }
717                 close(tfd);
718         }
719         free(file);
720         return p->open(p);
721 }
722
723 Page*
724 nextpage(Page *p)
725 {
726         if(p && p->down)
727                 return p->down;
728         while(p){
729                 if(p->next)
730                         return p->next;
731                 p = p->up;
732         }
733         return nil;
734 }
735
736 Page*
737 prevpage(Page *x)
738 {
739         Page *p, *t;
740
741         if(x){
742                 for(p = root->down; p; p = t)
743                         if((t = nextpage(p)) == x)
744                                 return p;
745         }
746         return nil;
747 }
748
749 int
750 openpage(Page *p)
751 {
752         int fd;
753
754         fd = -1;
755         if(p->open == nil || (fd = p->open(p)) < 0)
756                 p->open = nil;
757         else {
758                 if(rotate)
759                         pipeline(fd, "rotate -r %d", rotate);
760                 if(resize.x)
761                         pipeline(fd, "resize -x %d", resize.x);
762                 else if(resize.y)
763                         pipeline(fd, "resize -y %d", resize.y);
764         }
765         return fd;
766 }
767
768 void
769 loadpage(Page *p)
770 {
771         int fd;
772
773         if(p->open && p->image == nil){
774                 if((fd = openpage(p)) >= 0){
775                         pagegen++;
776                         if((p->image = readimage(display, fd, 1)) == nil)
777                                 fprint(2, "readimage: %r\n");
778                         close(fd);
779                 }
780                 if(p->image == nil)
781                         p->open = nil;
782         }
783         p->gen = pagegen;
784 }
785
786 void
787 unloadpage(Page *p)
788 {
789         if(p->open == nil || p->image == nil)
790                 return;
791         lockdisplay(display);
792         freeimage(p->image);
793         unlockdisplay(display);
794         p->image = nil;
795 }
796
797 void
798 unloadpages(int age)
799 {
800         Page *p;
801
802         for(p = root->down; p; p = nextpage(p)){
803                 if(age == 0)    /* synchronous flush */
804                         qlock(p);
805                 else if(!canqlock(p))
806                         continue;
807                 if((pagegen - p->gen) >= age)
808                         unloadpage(p);
809                 qunlock(p);
810         }
811 }
812
813 void
814 loadpages(Page *p, int ahead, int oviewgen)
815 {
816         int i;
817
818         ahead++;        /* load at least one */
819         unloadpages(ahead*2);
820         for(i = 0; i < ahead && p; p = nextpage(p), i++){
821                 if(viewgen != oviewgen)
822                         break;
823                 if(canqlock(p)){
824                         loadpage(p);
825                         if(viewgen != oviewgen){
826                                 unloadpage(p);
827                                 qunlock(p);
828                                 break;
829                         }
830                         if(p == current){
831                                 Point size;
832
833                                 esetcursor(nil);
834                                 size = pagesize(p);
835                                 if(size.x && size.y && newwin){
836                                         newwin = 0;
837                                         resizewin(size);
838                                 }
839                                 lockdisplay(display);
840                                 drawpage(p);
841                                 unlockdisplay(display);
842                         }
843                         qunlock(p);
844                 }
845         }
846 }
847
848 /*
849  * A draw operation that touches only the area contained in bot but not in top.
850  * mp and sp get aligned with bot.min.
851  */
852 static void
853 gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
854         Image *src, Point sp, Image *mask, Point mp, int op)
855 {
856         Rectangle r;
857         Point origin;
858         Point delta;
859
860         if(Dx(bot)*Dy(bot) == 0)
861                 return;
862
863         /* no points in bot - top */
864         if(rectinrect(bot, top))
865                 return;
866
867         /* bot - top â‰¡ bot */
868         if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
869                 gendrawop(dst, bot, src, sp, mask, mp, op);
870                 return;
871         }
872
873         origin = bot.min;
874         /* split bot into rectangles that don't intersect top */
875         /* left side */
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;
881         }
882
883         /* right side */
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;
889         }
890
891         /* top */
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;
897         }
898
899         /* bottom */
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;
905         }
906 }
907
908 int
909 alphachan(ulong chan)
910 {
911         for(; chan; chan >>= 8)
912                 if(TYPE(chan) == CAlpha)
913                         return 1;
914         return 0;
915 }
916
917 void
918 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
919 {
920         Rectangle dr;
921         Image *t;
922         Point a;
923         int w;
924
925         a = ZP;
926         if(r.min.x < d->r.min.x){
927                 sp.x += (d->r.min.x - r.min.x)/f;
928                 a.x = (d->r.min.x - r.min.x)%f;
929                 r.min.x = d->r.min.x;
930         }
931         if(r.min.y < d->r.min.y){
932                 sp.y += (d->r.min.y - r.min.y)/f;
933                 a.y = (d->r.min.y - r.min.y)%f;
934                 r.min.y = d->r.min.y;
935         }
936         rectclip(&r, d->r);
937         w = s->r.max.x - sp.x;
938         if(w > Dx(r))
939                 w = Dx(r);
940         dr = r;
941         dr.max.x = dr.min.x+w;
942         if(!alphachan(s->chan))
943                 b = nil;
944         if(f <= 1){
945                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
946                 gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
947                 return;
948         }
949         if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
950                 return;
951         for(; dr.min.y < r.max.y; dr.min.y++){
952                 dr.max.y = dr.min.y+1;
953                 draw(t, dr, s, nil, sp);
954                 if(++a.y == f){
955                         a.y = 0;
956                         sp.y++;
957                 }
958         }
959         dr = r;
960         for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
961                 dr.max.x = dr.min.x+1;
962                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
963                 gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
964                 for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
965                         dr.max.x = dr.min.x+1;
966                         gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
967                 }
968                 a.x = 0;
969         }
970         freeimage(t);
971 }
972
973 Point
974 pagesize(Page *p)
975 {
976         return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
977 }
978
979 void
980 drawframe(Rectangle r)
981 {
982         border(screen, r, -Borderwidth, frame, ZP);
983         gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), ground, ZP, nil, ZP, SoverD);
984         flushimage(display, 1);
985 }
986
987 void
988 drawpage(Page *p)
989 {
990         Rectangle r;
991         Image *i;
992
993         if(i = p->image){
994                 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
995                 zoomdraw(screen, r, ZR, paper, i, i->r.min, zoom);
996         } else {
997                 r = Rpt(ZP, stringsize(font, p->label));
998                 r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2),
999                         divpt(r.max, 2)), screen->r.min));
1000                 draw(screen, r, paper, nil, ZP);
1001                 string(screen, r.min, display->black, ZP, font, p->label);
1002         }
1003         drawframe(r);
1004 }
1005
1006 void
1007 translate(Page *p, Point d)
1008 {
1009         Rectangle r, nr;
1010         Image *i;
1011
1012         i = p->image;
1013         if(i==0 || d.x==0 && d.y==0)
1014                 return;
1015         r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1016         pos = addpt(pos, d);
1017         nr = rectaddpt(r, d);
1018         rectclip(&r, screen->r);
1019         draw(screen, rectaddpt(r, d), screen, nil, r.min);
1020         zoomdraw(screen, nr, rectaddpt(r, d), paper, i, i->r.min, zoom);
1021         drawframe(nr);
1022 }
1023
1024 Page*
1025 findpage(char *name)
1026 {
1027         Page *p;
1028         int n;
1029
1030         n = strlen(name);
1031         /* look in current document first */
1032         if(current && current->up){
1033                 for(p = current->up->down; p; p = p->next)
1034                         if(cistrncmp(p->label, name, n) == 0)
1035                                 return p;
1036         }
1037         /* look everywhere */
1038         for(p = root->down; p; p = nextpage(p))
1039                 if(cistrncmp(p->label, name, n) == 0)
1040                         return p;
1041         return nil;
1042 }
1043
1044 Page*
1045 pageat(int i)
1046 {
1047         Page *p;
1048
1049         for(p = root->down; i > 0 && p; p = nextpage(p))
1050                 i--;
1051         return i ? nil : p;
1052 }
1053
1054 int
1055 pageindex(Page *x)
1056 {
1057         Page *p;
1058         int i;
1059
1060         for(i = 0, p = root->down; p && p != x; p = nextpage(p))
1061                 i++;
1062         return (p == x) ? i : -1;
1063 }
1064
1065 char*
1066 pagemenugen(int i)
1067 {
1068         Page *p;
1069         if(p = pageat(i))
1070                 return p->label;
1071         return nil;
1072 }
1073
1074 void
1075 showpage(Page *p)
1076 {
1077         static int nproc;
1078         int oviewgen;
1079
1080         if(p == nil)
1081                 return;
1082         esetcursor(&reading);
1083         current = p;
1084         oviewgen = viewgen;
1085         if(++nproc > NPROC)
1086                 if(waitpid() > 0)
1087                         nproc--;
1088         switch(rfork(RFPROC|RFMEM)){
1089         case -1:
1090                 sysfatal("rfork: %r");
1091         case 0:
1092                 loadpages(p, NAHEAD, oviewgen);
1093                 exits(nil);
1094         }
1095 }
1096
1097 void
1098 shownext(void)
1099 {
1100         Page *p;
1101
1102         for(p = nextpage(current); p; p = nextpage(p))
1103                 if(p->image || p->open)
1104                         break;
1105         showpage(p);
1106 }
1107
1108 void
1109 showprev(void)
1110 {
1111         Page *p;
1112
1113         for(p = prevpage(current); p; p = prevpage(p))
1114                 if(p->image || p->open)
1115                         break;
1116         showpage(p);
1117 }
1118
1119 void
1120 zerox(Page *p)
1121 {
1122         char nam[64], *argv[4];
1123         int fd;
1124
1125         if(p == nil)
1126                 return;
1127         esetcursor(&reading);
1128         qlock(p);
1129         if((fd = openpage(p)) < 0)
1130                 goto Out;
1131         if(rfork(RFPROC|RFMEM|RFFDG|RFENVG|RFNOTEG|RFNOWAIT) == 0){
1132                 dupfds(fd, 1, 2, -1);
1133                 snprint(nam, sizeof nam, "/bin/%s", argv0);
1134                 argv[0] = argv0;
1135                 argv[1] = "-w";
1136                 argv[2] = nil;
1137                 exec(nam, argv);
1138                 sysfatal("exec: %r");
1139         }
1140         close(fd);
1141 Out:
1142         qunlock(p);
1143         esetcursor(nil);
1144 }
1145
1146 void
1147 eresized(int new)
1148 {
1149         Page *p;
1150
1151         lockdisplay(display);
1152         if(new && getwindow(display, Refnone) == -1)
1153                 sysfatal("getwindow: %r");
1154         if(p = current){
1155                 if(canqlock(p)){
1156                         drawpage(p);
1157                         qunlock(p);
1158                 }
1159         }
1160         unlockdisplay(display);
1161 }
1162
1163 void killcohort(void)
1164 {
1165         int i;
1166         for(i=0;i!=3;i++){      /* It's a long way to the kitchen */
1167                 postnote(PNGROUP, getpid(), "kill");
1168                 sleep(1);
1169         }
1170 }
1171
1172 void drawerr(Display *, char *msg)
1173 {
1174         sysfatal("draw: %s", msg);
1175 }
1176
1177 void
1178 usage(void)
1179 {
1180         fprint(2, "usage: %s [ -iRw ] [ -p ppi ] [ file ... ]\n", argv0);
1181         exits("usage");
1182 }
1183
1184 void
1185 main(int argc, char *argv[])
1186 {
1187         enum { Eplumb = 4 };
1188         char buf[NPATH];
1189         Plumbmsg *pm;
1190         Point o;
1191         Mouse m;
1192         Event e;
1193         char *s;
1194         int i;
1195
1196         ARGBEGIN {
1197         case 'a':
1198         case 'v':
1199         case 'V':
1200         case 'P':
1201                 break;
1202         case 'R':
1203                 newwin = -1;
1204                 break;
1205         case 'w':
1206                 newwin = 1;
1207                 break;
1208         case 'i':
1209                 imode = 1;
1210                 break;
1211         case 'p':
1212                 ppi = atoi(EARGF(usage()));
1213                 break;
1214         default:
1215                 usage();
1216         } ARGEND;
1217
1218         /*
1219          * so that we can stop all subprocesses with a note,
1220          * and to isolate rendezvous from other processes
1221          */
1222         rfork(RFNOTEG|RFNAMEG|RFREND);
1223         atexit(killcohort);
1224         atnotify(catchnote, 1);
1225         if(newwin > 0){
1226                 s = smprint("-pid %d", getpid());
1227                 if(newwindow(s) < 0)
1228                         sysfatal("newwindow: %r");
1229                 free(s);
1230         }
1231         if(initdraw(drawerr, nil, argv0) < 0)
1232                 sysfatal("initdraw: %r");
1233         paper = display->white;
1234         frame = display->black;
1235         ground = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
1236         display->locking = 1;
1237         unlockdisplay(display);
1238         einit(Ekeyboard|Emouse);
1239         eplumb(Eplumb, "image");
1240         memset(&m, 0, sizeof(m));
1241         if((nullfd = open("/dev/null", ORDWR)) < 0)
1242                 sysfatal("open: %r");
1243         current = root = addpage(nil, "", nil, nil, -1);
1244
1245         if(*argv == nil && !imode)
1246                 addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
1247         for(; *argv; argv++)
1248                 addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
1249
1250         for(;;){
1251                 i=event(&e);
1252                 switch(i){
1253                 case Emouse:
1254                         lockdisplay(display);
1255                         m = e.mouse;
1256                         if(m.buttons & 1){
1257                                 if(current == nil || !canqlock(current))
1258                                         goto Unlock;
1259                                 for(;;) {
1260                                         o = m.xy;
1261                                         m = emouse();
1262                                         if((m.buttons & 1) == 0)
1263                                                 break;
1264                                         translate(current, subpt(m.xy, o));
1265                                 }
1266                                 qunlock(current);
1267                                 goto Unlock;
1268                         }
1269                         if(m.buttons & 2){
1270                                 o = m.xy;
1271                                 i = emenuhit(2, &m, &menu);
1272                                 if(i < 0 || i >= nelem(menuitems) || menuitems[i]==nil)
1273                                         goto Unlock;
1274                                 s = menuitems[i];
1275                         PageMenu:
1276                                 if(strcmp(s, "orig size")==0){
1277                                         pos = ZP;
1278                                         zoom = 1;
1279                                         resize = ZP;
1280                                         rotate = 0;
1281                                 Unload:
1282                                         viewgen++;
1283                                         unlockdisplay(display);
1284                                         esetcursor(&reading);
1285                                         unloadpages(0);
1286                                         showpage(current);
1287                                         continue;
1288                                 }
1289                                 if(strncmp(s, "rotate ", 7)==0){
1290                                         rotate += atoi(s+7);
1291                                         rotate %= 360;
1292                                         goto Unload;
1293                                 }
1294                                 if(strcmp(s, "upside down")==0){
1295                                         rotate += 180;
1296                                         goto Unload;
1297                                 }
1298                                 if(strcmp(s, "fit width")==0){
1299                                         pos = ZP;
1300                                         zoom = 1;
1301                                         resize = subpt(screen->r.max, screen->r.min);
1302                                         resize.y = 0;
1303                                         goto Unload;
1304                                 }
1305                                 if(strcmp(s, "fit height")==0){
1306                                         pos = ZP;
1307                                         zoom = 1;
1308                                         resize = subpt(screen->r.max, screen->r.min);
1309                                         resize.x = 0;
1310                                         goto Unload;
1311                                 }
1312                                 if(strncmp(s, "zoom", 4)==0){
1313                                         if(current && canqlock(current)){
1314                                                 o = subpt(o, screen->r.min);
1315                                                 if(strstr(s, "in")){
1316                                                         if(zoom < 0x40000000){
1317                                                                 zoom *= 2;
1318                                                                 pos =  addpt(mulpt(subpt(pos, o), 2), o);
1319                                                         }
1320                                                 }else{
1321                                                         if(zoom > 1){
1322                                                                 zoom /= 2;
1323                                                                 pos =  addpt(divpt(subpt(pos, o), 2), o);
1324                                                         }
1325                                                 }
1326                                                 drawpage(current);
1327                                                 qunlock(current);
1328                                         }
1329                                         goto Unlock;
1330                                 }
1331                                 if(strcmp(s, "write")==0){
1332                                         if(current && canqlock(current)){
1333                                                 if(current->image){
1334                                                         s = nil;
1335                                                         if(current->up && current->up != root)
1336                                                                 s = current->up->label;
1337                                                         snprint(buf, sizeof(buf), "%s%s%s.bit",
1338                                                                 s ? s : "",
1339                                                                 s ? "." : "",
1340                                                                 current->label);
1341                                                         if(eenter("Write", buf, sizeof(buf), &m) > 0){
1342                                                                 int fd;
1343
1344                                                                 if((fd = create(buf, OWRITE, 0666)) < 0){
1345                                                                         errstr(buf, sizeof(buf));
1346                                                                         eenter(buf, 0, 0, &m);
1347                                                                 } else {
1348                                                                         esetcursor(&reading);
1349                                                                         writeimage(fd, current->image, 0);
1350                                                                         close(fd);
1351                                                                         esetcursor(nil);
1352                                                                 }
1353                                                         }
1354                                                 }
1355                                                 qunlock(current);
1356                                         }
1357                                         goto Unlock;
1358                                 }
1359                                 unlockdisplay(display);
1360                                 if(strcmp(s, "next")==0)
1361                                         shownext();
1362                                 if(strcmp(s, "prev")==0)
1363                                         showprev();
1364                                 if(strcmp(s, "zerox")==0)
1365                                         zerox(current);
1366                                 if(strcmp(s, "quit")==0)
1367                                         exits(0);
1368                                 continue;
1369                         }
1370                         if(m.buttons & 4){
1371                                 Page *x;
1372
1373                                 if(root->down == nil)
1374                                         goto Unlock;
1375                                 qlock(&pagelock);
1376                                 pagemenu.lasthit = pageindex(current);
1377                                 x = pageat(emenuhit(3, &m, &pagemenu));
1378                                 qunlock(&pagelock);
1379                                 unlockdisplay(display);
1380                                 showpage(x);
1381                                 continue;
1382                         }
1383                 Unlock:
1384                         unlockdisplay(display);
1385                         break;
1386                 case Ekeyboard:
1387                         switch(e.kbdc){
1388                         case 'q':
1389                         case Kdel:
1390                         case Keof:
1391                                 exits(0);
1392                                 break;
1393                         case 'w':
1394                                 s = "write";
1395                                 goto DoMenu;
1396                         case 'u':
1397                                 s = "upside down";
1398                                 goto DoMenu;
1399                         case '+':
1400                                 s = "zoom in";
1401                                 goto DoMenu;
1402                         case '-':
1403                                 s = "zoom out";
1404                                 goto DoMenu;
1405                         case Kesc:
1406                                 s = "orig size";
1407                         DoMenu:
1408                                 lockdisplay(display);
1409                                 o = m.xy;
1410                                 goto PageMenu;
1411
1412                         case Kup:
1413                                 if(current == nil || !canqlock(current))
1414                                         break;
1415                                 lockdisplay(display);
1416                                 if(pos.y < 0){
1417                                         translate(current, Pt(0, Dy(screen->r)/2));
1418                                         unlockdisplay(display);
1419                                         qunlock(current);
1420                                         continue;
1421                                 }
1422                                 unlockdisplay(display);
1423                                 qunlock(current);
1424                                 if(prevpage(current))
1425                                         pos.y = 0;
1426                         case Kbs:
1427                         case Kleft:
1428                                 showprev();
1429                                 break;
1430                         case Kdown:
1431                                 if(current == nil || !canqlock(current))
1432                                         break;
1433                                 o = addpt(pos, pagesize(current));
1434                                 lockdisplay(display);
1435                                 if(o.y > Dy(screen->r)){
1436                                         translate(current, Pt(0, -Dy(screen->r)/2));
1437                                         unlockdisplay(display);
1438                                         qunlock(current);
1439                                         continue;
1440                                 }
1441                                 unlockdisplay(display);
1442                                 qunlock(current);
1443                                 if(nextpage(current))
1444                                         pos.y = 0;
1445                         case '\n':
1446                         case ' ':
1447                         case Kright:
1448                                 shownext();
1449                                 break;
1450                         default:
1451                                 if((e.kbdc < 0x20) || 
1452                                    (e.kbdc & 0xFF00) == KF || 
1453                                    (e.kbdc & 0xFF00) == Spec)
1454                                         break;
1455                                 snprint(buf, sizeof(buf), "%C", (Rune)e.kbdc);
1456                                 lockdisplay(display);
1457                                 i = eenter("Go to", buf, sizeof(buf), &m);
1458                                 unlockdisplay(display);
1459                                 if(i > 0)
1460                                         showpage(findpage(buf));
1461                         }
1462                         break;
1463                 case Eplumb:
1464                         pm = e.v;
1465                         if(pm && pm->ndata > 0){
1466                                 int fd;
1467
1468                                 fd = -1;
1469                                 s = plumblookup(pm->attr, "action");
1470                                 if(s && strcmp(s, "quit")==0)
1471                                         exits(0);
1472                                 if(s && strcmp(s, "showdata")==0){
1473                                         if((fd = createtmp("plumb")) < 0){
1474                                                 fprint(2, "plumb: createtmp: %r\n");
1475                                                 goto Plumbfree;
1476                                         }
1477                                         s = malloc(NPATH);
1478                                         if(fd2path(fd, s, NPATH) < 0){
1479                                                 close(fd);
1480                                                 goto Plumbfree;
1481                                         }
1482                                         write(fd, pm->data, pm->ndata);
1483                                 }else if(pm->data[0] == '/'){
1484                                         s = strdup(pm->data);
1485                                 }else{
1486                                         s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
1487                                         sprint(s, "%s/%s", pm->wdir, pm->data);
1488                                         cleanname(s);
1489                                 }
1490                                 showpage(addpage(root, shortname(s), popenfile, s, fd));
1491                         }
1492                 Plumbfree:
1493                         plumbfree(pm);
1494                         break;
1495                 }
1496         }
1497 }