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