]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/page.c
page: add cbz support
[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         char    *text;
18         Image   *image;
19         int     fd;
20         int     gen;
21
22         Page    *up;
23         Page    *next;
24         Page    *down;
25         Page    *tail;
26 };
27
28 int zoom = 1;
29 int ppi = 100;
30 int imode;
31 int newwin;
32 int rotate;
33 int viewgen;
34 int pagegen;
35 Point resize, pos;
36 Page *root, *current;
37 QLock pagelock;
38 int nullfd;
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->text = nil;
107         p->image = nil;
108         p->data = pdata;
109         p->open = popen;
110         p->fd = fd;
111
112         p->down = nil;
113         p->tail = nil;
114         p->next = nil;
115
116         qlock(&pagelock);
117         if(p->up = up){
118                 if(up->tail == nil)
119                         up->down = up->tail = p;
120                 else {
121                         up->tail->next = p;
122                         up->tail = p;
123                 }
124         }
125         qunlock(&pagelock);
126
127         if(up && current == up)
128                 showpage(p);
129         return p;
130 }
131
132 void
133 resizewin(Point size)
134 {
135         int wctl;
136
137         if((wctl = open("/dev/wctl", OWRITE)) < 0)
138                 return;
139         /* add rio border */
140         size = addpt(size, Pt(Borderwidth*2, Borderwidth*2));
141         fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y);
142         close(wctl);
143 }
144
145 int
146 createtmp(ulong id, char *pfx)
147 {
148         char nam[64];
149
150         snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id ^ 0xcafebabe);
151         return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
152 }
153
154 int
155 catchnote(void *, char *msg)
156 {
157         if(strstr(msg, "sys: write on closed pipe"))
158                 return 1;
159         if(strstr(msg, "hangup"))
160                 return 1;
161         if(strstr(msg, "alarm"))
162                 return 1;
163         return 0;
164 }
165
166 void
167 pipeline(int fd, char *fmt, ...)
168 {
169         char buf[128], *argv[4];
170         va_list arg;
171         int pfd[2];
172
173         if(pipe(pfd) < 0){
174         Err:
175                 dup(nullfd, fd);
176                 return;
177         }
178         switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
179         case -1:
180                 close(pfd[0]);
181                 close(pfd[1]);
182                 goto Err;
183         case 0:
184                 if(dup(fd, 0)<0)
185                         exits("dup");
186                 if(dup(pfd[1], 1)<0)
187                         exits("dup");
188                 close(fd);
189                 close(pfd[1]);
190                 close(pfd[0]);
191                 va_start(arg, fmt);
192                 vsnprint(buf, sizeof buf, fmt, arg);
193                 va_end(arg);
194
195                 argv[0] = "rc";
196                 argv[1] = "-c";
197                 argv[2] = buf;
198                 argv[3] = nil;
199                 exec("/bin/rc", argv);
200                 sysfatal("exec: %r");
201         }
202         close(pfd[1]);
203         dup(pfd[0], fd);
204         close(pfd[0]);
205 }
206
207 int
208 popenfile(Page*);
209
210 int
211 popenconv(Page *p)
212 {
213         char nam[NPATH];
214         int fd;
215
216         if((fd = dup(p->fd, -1)) < 0){
217                 close(p->fd);
218                 p->fd = -1;
219                 return -1;
220         }
221
222         seek(fd, 0, 0);
223         if(p->data)
224                 pipeline(fd, "%s", (char*)p->data);
225
226         /*
227          * dont keep the file descriptor arround if it can simply
228          * be reopened.
229          */
230         fd2path(p->fd, nam, sizeof(nam));
231         if(strncmp(nam, pagespool, strlen(pagespool))){
232                 close(p->fd);
233                 p->fd = -1;
234                 p->data = strdup(nam);
235                 p->open = popenfile;
236         }
237
238         return fd;
239 }
240
241 int
242 popentape(Page *p)
243 {
244         char mnt[32], cmd[NPATH], *argv[4];
245
246         snprint(mnt, sizeof(mnt), "/n/tapefs.%.12d%.8lux", getpid(), (ulong)p);
247         switch(rfork(RFREND|RFPROC|RFFDG)){
248         case -1:
249                 close(p->fd);
250                 p->fd = -1;
251                 return -1;
252         case 0:
253                 dup(p->fd, 0);
254                 argv[0] = "rc";
255                 argv[1] = "-c";
256                 snprint(cmd, sizeof(cmd), "%s -m %s /fd/0", p->data, mnt);
257                 argv[2] = cmd;
258                 argv[3] = nil;
259                 exec("/bin/rc", argv);
260                 sysfatal("exec: %r");
261         }
262         waitpid();
263         close(p->fd);
264         p->fd = -1;
265         p->open = popenfile;
266         p->data = strdup(mnt);
267         return p->open(p);
268 }
269
270
271 typedef struct Ghost Ghost;
272 struct Ghost
273 {
274         QLock;
275
276         int     pin;
277         int     pout;
278         int     pdat;
279 };
280
281 int
282 popenpdf(Page *p)
283 {
284         char buf[NBUF];
285         int n, pfd[2];
286         Ghost *gs;
287
288         if(pipe(pfd) < 0)
289                 return -1;
290         switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
291         case -1:
292                 close(pfd[0]);
293                 close(pfd[1]);
294                 return -1;
295         case 0:
296                 close(pfd[0]);
297                 gs = p->data;
298                 qlock(gs);
299                 fprint(gs->pin, "%s DoPDFPage\n"
300                         "(/fd/3) (w) file "
301                         "dup flushfile "
302                         "dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
303                         "flushfile\n", p->label);
304                 while((n = read(gs->pdat, buf, sizeof buf)) > 0){
305                         if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
306                                 break;
307                         if(pfd[1] < 0)
308                                 continue;
309                         if(write(pfd[1], buf, n) != n){
310                                 close(pfd[1]);
311                                 pfd[1]=-1;
312                         }
313                 }
314                 qunlock(gs);
315                 exits(nil);
316         }
317         close(pfd[1]);
318         return pfd[0];
319 }
320
321 int
322 infernobithdr(char *buf, int n)
323 {
324         if(n >= 11){
325                 if(memcmp(buf, "compressed\n", 11) == 0)
326                         return 1;
327                 if(strtochan((char*)buf))
328                         return 1;
329                 if(memcmp(buf, "          ", 10) == 0 && 
330                         '0' <= buf[10] && buf[10] <= '9' &&
331                         buf[11] == ' ')
332                         return 1;
333         }
334         return 0;
335 }
336
337 int
338 popengs(Page *p)
339 {
340         int n, i, pdf, ifd, ofd, pin[2], pout[2], pdat[2];
341         char buf[NBUF], nam[32], *argv[16];
342
343         pdf = 0;
344         ifd = p->fd;
345         p->fd = -1;
346         seek(ifd, 0, 0);
347         if(read(ifd, buf, 5) != 5)
348                 goto Err0;
349         seek(ifd, 0, 0);
350         if(memcmp(buf, "%PDF-", 5) == 0)
351                 pdf = 1;
352         p->text = strdup(p->label);
353         if(pipe(pin) < 0){
354         Err0:
355                 close(ifd);
356                 return -1;
357         }
358         if(pipe(pout) < 0){
359         Err1:
360                 close(pin[0]);
361                 close(pin[1]);
362                 goto Err0;
363         }
364         if(pipe(pdat) < 0){
365         Err2:
366                 close(pdat[0]);
367                 close(pdat[1]);
368                 goto Err1;
369         }
370
371         switch(rfork(RFREND|RFPROC|RFFDG|RFNOWAIT)){
372         case -1:
373                 goto Err2;
374         case 0:
375                 if(pdf){
376                         if(dup(pin[1], 0)<0)
377                                 exits("dup");
378                         if(dup(pout[1], 1)<0)
379                                 exits("dup");
380                 } else {
381                         if(dup(nullfd, 0)<0)
382                                 exits("dup");
383                         if(dup(nullfd, 1)<0)
384                                 exits("dup");
385                 }
386                 if(dup(nullfd, 2)<0)
387                         exits("dup");
388                 if(dup(pdat[1], 3)<0)
389                         exits("dup");
390                 if(dup(ifd, 4)<0)
391                         exits("dup");
392
393                 close(pin[0]);
394                 close(pin[1]);
395                 close(pout[0]);
396                 close(pout[1]);
397                 close(pdat[0]);
398                 close(pdat[1]);
399                 close(ifd);
400
401                 if(p->data)
402                         pipeline(4, "%s", (char*)p->data);
403
404                 argv[0] = "gs";
405                 argv[1] = "-q";
406                 argv[2] = "-sDEVICE=plan9";
407                 argv[3] = "-sOutputFile=/fd/3";
408                 argv[4] = "-dBATCH";
409                 argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
410                 argv[6] = "-dQUIET";
411                 argv[7] = "-dTextAlphaBits=4";
412                 argv[8] = "-dGraphicsAlphaBits=4";
413                 snprint(buf, sizeof buf, "-r%d", ppi);
414                 argv[9] = buf;
415                 argv[10] = "-dDOINTERPOLATE";
416                 argv[11] = pdf ? "-" : "/fd/4";
417                 argv[12] = nil;
418                 exec("/bin/gs", argv);
419                 sysfatal("exec: %r");
420         }
421
422         close(pin[1]);
423         close(pout[1]);
424         close(pdat[1]);
425         close(ifd);
426
427         if(pdf){
428                 Ghost *gs;
429                 char *prolog =
430                         "/PAGEOUT (/fd/1) (w) file def\n"
431                         "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
432                         "\n"
433                         "/Page null def\n"
434                         "/Page# 0 def\n"
435                         "/PDFSave null def\n"
436                         "/DSCPageCount 0 def\n"
437                         "/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
438                         "\n"
439                         "GS_PDF_ProcSet begin\n"
440                         "pdfdict begin\n"
441                         "(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
442                         "\n"
443                         "pdfpagecount PAGE==\n";
444
445                 n = strlen(prolog);
446                 if(write(pin[0], prolog, n) != n)
447                         goto Out;
448                 if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
449                         goto Out;
450                 buf[n] = 0;
451                 n = atoi(buf);
452                 if(n <= 0){
453                         werrstr("no pages");
454                         goto Out;
455                 }
456                 gs = mallocz(sizeof(*gs), 1);
457                 gs->pin = pin[0];
458                 gs->pout = pout[0];
459                 gs->pdat = pdat[0];
460                 for(i=1; i<=n; i++){
461                         snprint(nam, sizeof nam, "%d", i);
462                         addpage(p, nam, popenpdf, gs, -1);
463                 }
464
465                 /* keep ghostscript arround */
466                 return -1;
467         } else {
468                 i = 0;
469                 ofd = -1;
470                 while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
471                         if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
472                                 snprint(nam, sizeof nam, "%d", i);
473                                 addpage(p, nam, popenconv, nil, ofd);
474                                 ofd = -1;
475                         }
476                         if(n <= 0)
477                                 break;
478                         if(ofd < 0){
479                                 snprint(nam, sizeof nam, "%.4d", ++i);
480                                 if((ofd = createtmp((ulong)p, nam)) < 0)
481                                         ofd = dup(nullfd, -1);
482                         }
483                         if(write(ofd, buf, n) != n)
484                                 break;
485                 }
486                 if(ofd >= 0)
487                         close(ofd);
488         }
489 Out:
490         close(pin[0]);
491         close(pout[0]);
492         close(pdat[0]);
493         return -1;
494 }
495
496 int
497 popenfile(Page *p)
498 {
499         char buf[NBUF], *file;
500         int i, n, fd, tfd;
501         Dir *d;
502
503         fd = p->fd;
504         p->fd = -1;
505         file = p->data;
506         if(fd < 0){
507                 if((fd = open(file, OREAD)) < 0){
508                 Err0:
509                         p->data = nil;
510                         free(file);
511                         return -1;
512                 }
513         }
514         seek(fd, 0, 0);
515         if((d = dirfstat(fd)) == nil){
516         Err1:
517                 close(fd);
518                 goto Err0;
519         }
520         if(d->mode & DMDIR){
521                 free(d);
522                 d = nil;
523                 if((n = dirreadall(fd, &d)) < 0)
524                         goto Err1;
525                 for(i = 0; i<n; i++)
526                         addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
527                 free(d);
528                 p->text = strdup(p->label);
529                 goto Err1;
530         }
531         free(d);
532
533         memset(buf, 0, 32+1);
534         if((n = read(fd, buf, 32)) <= 0)
535                 goto Err1;
536
537         p->fd = fd;
538         p->data = nil;
539         p->open = popenconv;
540         if(memcmp(buf, "%PDF-", 5) == 0 || strstr(buf, "%!"))
541                 p->open = popengs;
542         else if(memcmp(buf, "x T ", 4) == 0){
543                 p->data = "lp -dstdout";
544                 p->open = popengs;
545         }
546         else if(memcmp(buf, "\xF7\x02\x01\x83\x92\xC0\x1C;", 8) == 0){
547                 p->data = "dvips -Pps -r0 -q1 -f1";
548                 p->open = popengs;
549         }
550         else if(memcmp(buf, "\x1F\x8B", 2) == 0){
551                 p->data = "gunzip";
552                 p->open = popengs;
553         }
554         else if(memcmp(buf, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 8) == 0){
555                 p->data = "doc2ps";
556                 p->open = popengs;
557         }
558         else if(memcmp(buf, "PK\x03\x04", 4) == 0){
559                 p->data = "fs/zipfs";
560                 p->open = popentape;
561         }
562         else if(memcmp(buf, "GIF", 3) == 0)
563                 p->data = "gif -t9";
564         else if(memcmp(buf, "\111\111\052\000", 4) == 0) 
565                 p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
566         else if(memcmp(buf, "\115\115\000\052", 4) == 0)
567                 p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
568         else if(memcmp(buf, "\377\330\377", 3) == 0)
569                 p->data = "jpg -t9";
570         else if(memcmp(buf, "\211PNG\r\n\032\n", 3) == 0)
571                 p->data = "png -t9";
572         else if(memcmp(buf, "\0PC Research, Inc", 17) == 0)
573                 p->data = "aux/g3p9bit -g";
574         else if(memcmp(buf, "TYPE=ccitt-g31", 14) == 0)
575                 p->data = "aux/g3p9bit -g";
576         else if(memcmp(buf, "II*", 3) == 0)
577                 p->data = "aux/g3p9bit -g";
578         else if(memcmp(buf, "TYPE=", 5) == 0)
579                 p->data = "fb/3to1 rgbv |fb/pcp -tplan9";
580         else if(buf[0] == 'P' && '0' <= buf[1] && buf[1] <= '9')
581                 p->data = "ppm -t9";
582         else if(memcmp(buf, "BM", 2) == 0)
583                 p->data = "bmp -t9";
584         else if(infernobithdr(buf, n))
585                 p->data = nil;
586         else {
587                 werrstr("unknown image format");
588                 goto Err1;
589         }
590
591         if(seek(fd, 0, 0) < 0)
592                 goto Noseek;
593         if((i = read(fd, buf+n, n)) < 0)
594                 goto Err1;
595         if(i != n || memcmp(buf, buf+n, i)){
596                 n += i;
597         Noseek:
598                 if((tfd = createtmp((ulong)p, "file")) < 0)
599                         goto Err1;
600                 while(n > 0){
601                         if(write(tfd, buf, n) != n)
602                                 goto Err2;
603                         if((n = read(fd, buf, sizeof(buf))) < 0)
604                                 goto Err2;
605                 }
606                 if(dup(tfd, fd) < 0){
607                 Err2:
608                         close(tfd);
609                         goto Err1;
610                 }
611                 close(tfd);
612         }
613         free(file);
614         return p->open(p);
615 }
616
617 Page*
618 nextpage(Page *p)
619 {
620         if(p){
621                 if(p->down)
622                         return p->down;
623                 if(p->next)
624                         return p->next;
625                 if(p->up)
626                         return p->up->next;
627         }
628         return nil;
629 }
630
631 Page*
632 prevpage(Page *x)
633 {
634         Page *p, *t;
635
636         if(x){
637                 for(p = root->down; p; p = t)
638                         if((t = nextpage(p)) == x)
639                                 return p;
640         }
641         return nil;
642 }
643
644 int
645 openpage(Page *p)
646 {
647         int fd;
648
649         fd = -1;
650         if(p->open == nil || (fd = p->open(p)) < 0)
651                 p->open = nil;
652         else {
653                 if(rotate)
654                         pipeline(fd, "rotate -r %d", rotate);
655                 if(resize.x)
656                         pipeline(fd, "resize -x %d", resize.x);
657                 else if(resize.y)
658                         pipeline(fd, "resize -y %d", resize.y);
659         }
660         return fd;
661 }
662
663 void
664 loadpage(Page *p)
665 {
666         int fd;
667
668         if(p->open && p->image == nil && p->text == nil){
669                 if((fd = openpage(p)) >= 0){
670                         pagegen++;
671                         p->image = readimage(display, fd, 1);
672                         close(fd);
673                 }
674                 if(p->image == nil && p->text == nil)
675                         p->text = smprint("%s: %r", p->label);
676         }
677         p->gen = pagegen;
678 }
679
680 void
681 unloadpage(Page *p)
682 {
683         if(p->open){
684                 if(p->text)
685                         free(p->text);
686                 p->text = nil;
687                 if(p->image){
688                         lockdisplay(display);
689                         freeimage(p->image);
690                         unlockdisplay(display);
691                 }
692                 p->image = nil;
693         }
694 }
695
696 void
697 unloadpages(int age)
698 {
699         Page *p;
700
701         for(p = root->down; p; p = nextpage(p)){
702                 if(age == 0)    /* synchronous flush */
703                         qlock(p);
704                 else if(!canqlock(p))
705                         continue;
706                 if((pagegen - p->gen) >= age)
707                         unloadpage(p);
708                 qunlock(p);
709         }
710 }
711
712 void
713 loadpages(Page *p, int ahead, int oviewgen)
714 {
715         int i;
716
717         ahead++;        /* load at least one */
718         unloadpages(ahead*2);
719         for(i = 0; i < ahead && p; p = nextpage(p), i++){
720                 if(viewgen != oviewgen)
721                         break;
722                 if(canqlock(p)){
723                         loadpage(p);
724                         if(viewgen != oviewgen){
725                                 unloadpage(p);
726                                 qunlock(p);
727                                 break;
728                         }
729                         if(p == current){
730                                 Point size;
731
732                                 esetcursor(nil);
733                                 size = pagesize(p);
734                                 if(size.x && size.y && newwin){
735                                         newwin = 0;
736                                         resizewin(size);
737                                 }
738                                 lockdisplay(display);
739                                 drawpage(p);
740                                 unlockdisplay(display);
741                         }
742                         qunlock(p);
743                 }
744         }
745 }
746
747 /*
748  * A draw operation that touches only the area contained in bot but not in top.
749  * mp and sp get aligned with bot.min.
750  */
751 static void
752 gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
753         Image *src, Point sp, Image *mask, Point mp, int op)
754 {
755         Rectangle r;
756         Point origin;
757         Point delta;
758
759         USED(op);
760
761         if(Dx(bot)*Dy(bot) == 0)
762                 return;
763
764         /* no points in bot - top */
765         if(rectinrect(bot, top))
766                 return;
767
768         /* bot - top â‰¡ bot */
769         if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
770                 gendrawop(dst, bot, src, sp, mask, mp, op);
771                 return;
772         }
773
774         origin = bot.min;
775         /* split bot into rectangles that don't intersect top */
776         /* left side */
777         if(bot.min.x < top.min.x){
778                 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
779                 delta = subpt(r.min, origin);
780                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
781                 bot.min.x = top.min.x;
782         }
783
784         /* right side */
785         if(bot.max.x > top.max.x){
786                 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
787                 delta = subpt(r.min, origin);
788                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
789                 bot.max.x = top.max.x;
790         }
791
792         /* top */
793         if(bot.min.y < top.min.y){
794                 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
795                 delta = subpt(r.min, origin);
796                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
797                 bot.min.y = top.min.y;
798         }
799
800         /* bottom */
801         if(bot.max.y > top.max.y){
802                 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
803                 delta = subpt(r.min, origin);
804                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
805                 bot.max.y = top.max.y;
806         }
807 }
808
809 void
810 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *s, Point sp, int f)
811 {
812         int w, x, y;
813         Image *t;
814         Point a;
815
816         if(f <= 1){
817                 gendrawdiff(d, r, top, s, sp, nil, ZP, S);
818                 return;
819         }
820         a = ZP;
821         if(r.min.x < d->r.min.x){
822                 sp.x += (d->r.min.x - r.min.x)/f;
823                 a.x = (d->r.min.x - r.min.x)%f;
824                 r.min.x = d->r.min.x;
825         }
826         if(r.min.y < d->r.min.y){
827                 sp.y += (d->r.min.y - r.min.y)/f;
828                 a.y = (d->r.min.y - r.min.y)%f;
829                 r.min.y = d->r.min.y;
830         }
831         rectclip(&r, d->r);
832         w = s->r.max.x - sp.x;
833         if(w > Dx(r))
834                 w = Dx(r);
835         t = allocimage(display, Rect(r.min.x, r.min.y, r.min.x+w, r.max.y), s->chan, 0, DNofill);
836         if(t == nil)
837                 return;
838         for(y=r.min.y; y<r.max.y; y++){
839                 draw(t, Rect(r.min.x, y, r.min.x+w, y+1), s, nil, sp);
840                 if(++a.y == zoom){
841                         a.y = 0;
842                         sp.y++;
843                 }
844         }
845         sp = r.min;
846         for(x=r.min.x; x<r.max.x; x++){
847                 gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, t, sp, nil, ZP, S);
848                 if(++a.x == f){
849                         a.x = 0;
850                         sp.x++;
851                 }
852         }
853         freeimage(t);
854 }
855
856 Point
857 pagesize(Page *p)
858 {
859         return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
860 }
861
862 void
863 drawpage(Page *p)
864 {
865         Rectangle r;
866         Image *i;
867
868         if((i = p->image) == nil){
869                 char *s;
870
871                 if((s = p->text) == nil)
872                         s = "...";
873                 r.min = ZP;
874                 r.max = stringsize(font, p->text);
875                 r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2), divpt(r.max, 2)),
876                         screen->r.min));
877                 draw(screen, r, display->white, nil, ZP);
878                 string(screen, r.min, display->black, ZP, font, s);
879         } else {
880                 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
881                 zoomdraw(screen, r, ZR, i, i->r.min, zoom);
882         }
883         gendrawdiff(screen, screen->r, r, display->white, ZP, nil, ZP, S);
884         border(screen, r, -Borderwidth, display->black, ZP);
885         flushimage(display, 1);
886 }
887
888 void
889 translate(Page *p, Point d)
890 {
891         Rectangle r, or, nr;
892         Image *i;
893
894         i = p->image;
895         if((i==0) || (d.x==0 && d.y==0))
896                 return;
897         r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
898         pos = addpt(pos, d);
899         nr = rectaddpt(r, d);
900         or = r;
901         rectclip(&or, screen->r);
902         draw(screen, rectaddpt(or, d), screen, nil, or.min);
903         zoomdraw(screen, nr, rectaddpt(or, d), i, i->r.min, zoom);
904         gendrawdiff(screen, screen->r, nr, display->white, ZP, nil, ZP, S);
905         border(screen, nr, -Borderwidth, display->black, ZP);
906         flushimage(display, 1);
907 }
908
909 Page*
910 findpage(char *name)
911 {
912         Page *p;
913         int n;
914
915         n = strlen(name);
916         /* look in current document first */
917         if(current && current->up){
918                 for(p = current->up->down; p; p = p->next)
919                         if(cistrncmp(p->label, name, n) == 0)
920                                 return p;
921         }
922         /* look everywhere */
923         for(p = root->down; p; p = nextpage(p))
924                 if(cistrncmp(p->label, name, n) == 0)
925                         return p;
926         return nil;
927 }
928
929 Page*
930 pageat(int i)
931 {
932         Page *p;
933
934         for(p = root->down; i > 0 && p; p = nextpage(p))
935                 i--;
936         return i ? nil : p;
937 }
938
939 int
940 pageindex(Page *x)
941 {
942         Page *p;
943         int i;
944
945         for(i = 0, p = root->down; p && p != x; p = nextpage(p))
946                 i++;
947         return (p == x) ? i : -1;
948 }
949
950 char*
951 pagemenugen(int i)
952 {
953         Page *p;
954         if(p = pageat(i))
955                 return p->label;
956         return nil;
957 }
958
959 void
960 showpage(Page *p)
961 {
962         static int nproc;
963         int oviewgen;
964
965         if(p == nil)
966                 return;
967         esetcursor(&reading);
968         current = p;
969         oviewgen = viewgen;
970         if(++nproc > NPROC)
971                 if(waitpid() > 0)
972                         nproc--;
973         switch(rfork(RFPROC|RFMEM)){
974         case -1:
975                 sysfatal("rfork: %r");
976         case 0:
977                 loadpages(p, NAHEAD, oviewgen);
978                 exits(nil);
979         }
980 }
981
982 void
983 zerox(Page *p)
984 {
985         char nam[64], *argv[4];
986         int fd;
987
988         if(p == nil)
989                 return;
990         esetcursor(&reading);
991         qlock(p);
992         if((fd = openpage(p)) < 0)
993                 goto Out;
994         if(rfork(RFREND|RFFDG|RFPROC|RFENVG|RFNOTEG|RFNOWAIT) == 0){
995                 dup(fd, 0);
996                 close(fd);
997
998                 snprint(nam, sizeof nam, "/bin/%s", argv0);
999                 argv[0] = argv0;
1000                 argv[1] = "-w";
1001                 argv[2] = nil;
1002                 exec(nam, argv);
1003                 sysfatal("exec: %r");
1004         }
1005         close(fd);
1006 Out:
1007         qunlock(p);
1008         esetcursor(nil);
1009 }
1010
1011 void
1012 eresized(int new)
1013 {
1014         Page *p;
1015
1016         lockdisplay(display);
1017         if(new && getwindow(display, Refnone) == -1)
1018                 sysfatal("getwindow: %r");
1019         if(p = current){
1020                 if(canqlock(p)){
1021                         drawpage(p);
1022                         qunlock(p);
1023                 }
1024         }
1025         unlockdisplay(display);
1026 }
1027
1028 void killcohort(void)
1029 {
1030         int i;
1031         for(i=0;i!=3;i++){      /* It's a long way to the kitchen */
1032                 postnote(PNGROUP, getpid(), "kill");
1033                 sleep(1);
1034         }
1035 }
1036
1037 void drawerr(Display *, char *msg)
1038 {
1039         sysfatal("draw: %s", msg);
1040 }
1041
1042 char*
1043 shortname(char *s)
1044 {
1045         char *x;
1046         if(x = strrchr(s, '/'))
1047                 if(x[1] != 0)
1048                         return x+1;
1049         return s;
1050 }
1051
1052 void
1053 usage(void)
1054 {
1055         fprint(2, "usage: %s [ -iRw ] [ -p ppi ] [ file ... ]\n", argv0);
1056         exits("usage");
1057 }
1058
1059 void
1060 main(int argc, char *argv[])
1061 {
1062         enum { Eplumb = 4 };
1063         char jump[32];
1064         Plumbmsg *pm;
1065         Point o;
1066         Mouse m;
1067         Event e;
1068         char *s;
1069         int i;
1070
1071         ARGBEGIN {
1072         case 'a':
1073         case 'v':
1074         case 'V':
1075         case 'P':
1076                 break;
1077         case 'R':
1078                 newwin = -1;
1079                 break;
1080         case 'w':
1081                 newwin = 1;
1082                 break;
1083         case 'i':
1084                 imode = 1;
1085                 break;
1086         case 'p':
1087                 ppi = atoi(EARGF(usage()));
1088                 break;
1089         default:
1090                 usage();
1091         } ARGEND;
1092
1093         /*
1094          * so that we can stop all subprocesses with a note,
1095          * and to isolate rendezvous from other processes
1096          */
1097         rfork(RFNOTEG|RFNAMEG|RFREND);
1098         atexit(killcohort);
1099         atnotify(catchnote, 1);
1100         if(newwin > 0){
1101                 s = smprint("-pid %d", getpid());
1102                 if(newwindow(s) < 0)
1103                         sysfatal("newwindow: %r");
1104                 free(s);
1105         }
1106         initdraw(drawerr, nil, argv0);
1107         display->locking = 1;
1108         unlockdisplay(display);
1109         einit(Ekeyboard|Emouse);
1110         eplumb(Eplumb, "image");
1111         nullfd = open("/dev/null", ORDWR);
1112         current = root = addpage(nil, "root", nil, nil, -1);
1113
1114         if(*argv == nil && !imode)
1115                 addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
1116         for(; *argv; argv++)
1117                 addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
1118
1119         jump[0] = 0;
1120         for(;;){
1121                 i=event(&e);
1122                 switch(i){
1123                 case Emouse:
1124                         lockdisplay(display);
1125                         m = e.mouse;
1126                         if(m.buttons & 1){
1127                                 if(current == nil || !canqlock(current))
1128                                         goto Unlock;
1129                                 for(;;) {
1130                                         o = m.xy;
1131                                         m = emouse();
1132                                         if((m.buttons & 1) == 0)
1133                                                 break;
1134                                         translate(current, subpt(m.xy, o));
1135                                 }
1136                                 qunlock(current);
1137                                 goto Unlock;
1138                         }
1139                         if(m.buttons & 2){
1140                                 i = emenuhit(2, &m, &menu);
1141                                 if(i < 0 || i >= nelem(menuitems) || menuitems[i]==nil)
1142                                         goto Unlock;
1143                                 s = menuitems[i];
1144                                 if(strcmp(s, "orig size")==0){
1145                                         pos = ZP;
1146                                         zoom = 1;
1147                                         resize = ZP;
1148                                         rotate = 0;
1149                                 Unload:
1150                                         viewgen++;
1151                                         unlockdisplay(display);
1152                                         esetcursor(&reading);
1153                                         unloadpages(0);
1154                                         showpage(current);
1155                                         continue;
1156                                 }
1157                                 if(strncmp(s, "rotate ", 7)==0){
1158                                         rotate += atoi(s+7);
1159                                         rotate %= 360;
1160                                         goto Unload;
1161                                 }
1162                                 if(strcmp(s, "upside down")==0){
1163                                         rotate += 180;
1164                                         goto Unload;
1165                                 }
1166                                 if(strcmp(s, "fit width")==0){
1167                                         pos = ZP;
1168                                         zoom = 1;
1169                                         resize = subpt(screen->r.max, screen->r.min);
1170                                         resize.y = 0;
1171                                         goto Unload;
1172                                 }
1173                                 if(strcmp(s, "fit height")==0){
1174                                         pos = ZP;
1175                                         zoom = 1;
1176                                         resize = subpt(screen->r.max, screen->r.min);
1177                                         resize.x = 0;
1178                                         goto Unload;
1179                                 }
1180                                 if(strncmp(s, "zoom", 4)==0){
1181                                         if(current && canqlock(current)){
1182                                                 o = subpt(m.xy, screen->r.min);
1183                                                 if(strstr(s, "in")){
1184                                                         if(zoom < 0x40000000){
1185                                                                 zoom *= 2;
1186                                                                 pos =  addpt(mulpt(subpt(pos, o), 2), o);
1187                                                         }
1188                                                 }else{
1189                                                         if(zoom > 1){
1190                                                                 zoom /= 2;
1191                                                                 pos =  addpt(divpt(subpt(pos, o), 2), o);
1192                                                         }
1193                                                 }
1194                                                 drawpage(current);
1195                                                 qunlock(current);
1196                                         }
1197                                 }
1198                                 unlockdisplay(display);
1199                                 if(strcmp(s, "next")==0)
1200                                         showpage(nextpage(current));
1201                                 if(strcmp(s, "prev")==0)
1202                                         showpage(prevpage(current));
1203                                 if(strcmp(s, "zerox")==0)
1204                                         zerox(current);
1205                                 if(strcmp(s, "quit")==0)
1206                                         exits(0);
1207                                 continue;
1208                         }
1209                         if(m.buttons & 4){
1210                                 if(root->down == nil)
1211                                         goto Unlock;
1212                                 pagemenu.lasthit = pageindex(current);
1213                                 i = emenuhit(3, &m, &pagemenu);
1214                                 unlockdisplay(display);
1215                                 if(i != -1)
1216                                         showpage(pageat(i));
1217                                 continue;
1218                         }
1219                 Unlock:
1220                         unlockdisplay(display);
1221                         break;
1222                 case Ekeyboard:
1223                         switch(e.kbdc){
1224                         case 'q':
1225                         case Kdel:
1226                         case Keof:
1227                                 exits(0);
1228                         case Kup:
1229                                 if(current == nil || !canqlock(current))
1230                                         break;
1231                                 lockdisplay(display);
1232                                 if(pos.y < 0){
1233                                         translate(current, Pt(0, Dy(screen->r)/2));
1234                                         unlockdisplay(display);
1235                                         qunlock(current);
1236                                         continue;
1237                                 }
1238                                 unlockdisplay(display);
1239                                 qunlock(current);
1240                                 if(prevpage(current))
1241                                         pos.y = 0;
1242                         case '-':
1243                         case Kbs:
1244                         case Kleft:
1245                                 showpage(prevpage(current));
1246                                 break;
1247                         case Kdown:
1248                                 if(current == nil || !canqlock(current))
1249                                         break;
1250                                 o = addpt(pos, pagesize(current));
1251                                 lockdisplay(display);
1252                                 if(o.y > Dy(screen->r)){
1253                                         translate(current, Pt(0, -Dy(screen->r)/2));
1254                                         unlockdisplay(display);
1255                                         qunlock(current);
1256                                         continue;
1257                                 }
1258                                 unlockdisplay(display);
1259                                 qunlock(current);
1260                                 if(nextpage(current))
1261                                         pos.y = 0;
1262                         case '\n':
1263                                 if(jump[0]){
1264                                         showpage(findpage(jump));
1265                                         jump[0] = 0;
1266                                         break;
1267                                 }
1268                         case ' ':
1269                         case Kright:
1270                                 showpage(nextpage(current));
1271                                 break;
1272                         default:
1273                                 i = strlen(jump);
1274                                 if(i+1 < sizeof(jump)){
1275                                         jump[i] = e.kbdc;
1276                                         jump[i+1] = 0;
1277                                 }
1278                         }
1279                         break;
1280                 case Eplumb:
1281                         pm = e.v;
1282                         if(pm && pm->ndata > 0){
1283                                 int fd;
1284
1285                                 fd = -1;
1286                                 s = plumblookup(pm->attr, "action");
1287                                 if(s && strcmp(s, "quit")==0)
1288                                         exits(0);
1289                                 if(s && strcmp(s, "showdata")==0){
1290                                         static ulong plumbid;
1291
1292                                         if((fd = createtmp(plumbid++, "plumb")) < 0){
1293                                                 fprint(2, "plumb: createtmp: %r\n");
1294                                                 goto Plumbfree;
1295                                         }
1296                                         s = malloc(NPATH);
1297                                         if(fd2path(fd, s, NPATH) < 0){
1298                                                 close(fd);
1299                                                 goto Plumbfree;
1300                                         }
1301                                         write(fd, pm->data, pm->ndata);
1302                                 }else if(pm->data[0] == '/'){
1303                                         s = strdup(pm->data);
1304                                 }else{
1305                                         s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
1306                                         sprint(s, "%s/%s", pm->wdir, pm->data);
1307                                         cleanname(s);
1308                                 }
1309                                 showpage(addpage(root, shortname(s), popenfile, s, fd));
1310                         }
1311                 Plumbfree:
1312                         plumbfree(pm);
1313                         break;
1314                 }
1315         }
1316 }