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