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