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