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