1 /*
2  * the actual viewer that handles screen stuff
3  */
4 
5 #include <u.h>
6 #include <libc.h>
7 #include <9pclient.h>
8 #include <draw.h>
9 #include <cursor.h>
10 #include <mouse.h>
11 #include <keyboard.h>
12 #include <thread.h>
13 #include <bio.h>
14 #include <plumb.h>
15 #include <ctype.h>
16 #include "page.h"
17 
18 Document *doc;
19 Mousectl *mc;
20 Image *im;
21 Image *tofree;
22 int page;
23 int angle = 0;
24 int showbottom = 0;		/* on the next showpage, move the image so the bottom is visible. */
25 
26 Rectangle ulrange;	/* the upper left corner of the image must be in this rectangle */
27 Point ul;			/* the upper left corner of the image is at this point on the screen */
28 
29 Point pclip(Point, Rectangle);
30 Rectangle mkrange(Rectangle screenr, Rectangle imr);
31 void redraw(Image*);
32 void plumbproc(void*);
33 
34 Cursor reading={
35 	{-1, -1},
36 	{0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00,
37 	 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0,
38 	 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0,
39 	 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
40 	{0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00,
41 	 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0,
42 	 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40,
43 	 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
44 };
45 
46 Cursor query = {
47 	{-7,-7},
48 	{0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe,
49 	 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8,
50 	 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0,
51 	 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
52 	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c,
53 	 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0,
54 	 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80,
55 	 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
56 };
57 
58 enum {
59 	Left = 1,
60 	Middle = 2,
61 	Right = 4,
62 
63 	RMenu = 3,
64 };
65 
66 static void
delayfreeimage(Image * m)67 delayfreeimage(Image *m)
68 {
69 	if(m == tofree)
70 		return;
71 	if(tofree)
72 		freeimage(tofree);
73 	tofree = m;
74 }
75 
76 void
unhide(void)77 unhide(void)
78 {
79 	USED(nil);
80 }
81 
82 int
max(int a,int b)83 max(int a, int b)
84 {
85 	return a > b ? a : b;
86 }
87 
88 int
min(int a,int b)89 min(int a, int b)
90 {
91 	return a < b ? a : b;
92 }
93 
94 
95 char*
menugen(int n)96 menugen(int n)
97 {
98 	static char menustr[32];
99 	char *p;
100 	int len;
101 
102 	if(n == doc->npage)
103 		return "exit";
104 	if(n > doc->npage)
105 		return nil;
106 
107 	if(reverse)
108 		n = doc->npage-1-n;
109 
110 	p = doc->pagename(doc, n);
111 	len = (sizeof menustr)-2;
112 
113 	if(strlen(p) > len && strrchr(p, '/'))
114 		p = strrchr(p, '/')+1;
115 	if(strlen(p) > len)
116 		p = p+strlen(p)-len;
117 
118 	strcpy(menustr+1, p);
119 	if(page == n)
120 		menustr[0] = '>';
121 	else
122 		menustr[0] = ' ';
123 	return menustr;
124 }
125 
126 void
showpage(int page,Menu * m)127 showpage(int page, Menu *m)
128 {
129 	if(doc->fwdonly)
130 		m->lasthit = 0;	/* this page */
131 	else
132 		m->lasthit = reverse ? doc->npage-1-page : page;
133 
134 	setcursor(mc, &reading);
135 	delayfreeimage(nil);
136 	im = cachedpage(doc, angle, page);
137 	if(im == nil)
138 		wexits(0);
139 	if(resizing)
140 		resize(Dx(im->r), Dy(im->r));
141 
142 	setcursor(mc, nil);
143 	if(showbottom){
144 		ul.y = screen->r.max.y - Dy(im->r);
145 		showbottom = 0;
146 	}
147 
148 	if((doc->type == Tgfx) && fitwin)
149 		fit();
150 	else{
151 		redraw(screen);
152 	 	flushimage(display, 1);
153 	}
154 }
155 
156 char*
writebitmap(void)157 writebitmap(void)
158 {
159 	char basename[64];
160 	char name[64+30];
161 	static char result[200];
162 	char *p, *q;
163 	int fd = -1;
164 
165 	if(im == nil)
166 		return "no image";
167 
168 	memset(basename, 0, sizeof basename);
169 	if(doc->docname)
170 		strncpy(basename, doc->docname, sizeof(basename)-1);
171 	else if((p = menugen(page)) && p[0] != '\0')
172 		strncpy(basename, p+1, sizeof(basename)-1);
173 
174 	if(basename[0]) {
175 		if(q = strrchr(basename, '/'))
176 			q++;
177 		else
178 			q = basename;
179 		if(p = strchr(q, '.'))
180 			*p = 0;
181 
182 		memset(name, 0, sizeof name);
183 		snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1);
184 		if(access(name, 0) >= 0) {
185 			strcat(name, "XXXX");
186 			fd = mkstemp(name);
187 		}
188 		if(fd < 0)
189 			return "couldn't think of a name for bitmap";
190 	} else {
191 		strcpy(name, "bitXXXX");
192 		mkstemp(name);
193 		if(fd < 0)
194 			return "couldn't think of a name for bitmap";
195 	}
196 
197 	if(fd < 0) {
198 		snprint(result, sizeof result, "cannot create %s: %r", name);
199 		return result;
200 	}
201 
202 	if(writeimage(fd, im, 0) < 0) {
203 		snprint(result, sizeof result, "cannot writeimage: %r");
204 		close(fd);
205 		return result;
206 	}
207 	close(fd);
208 
209 	snprint(result, sizeof result, "wrote %s", name);
210 	return result;
211 }
212 
213 static void translate(Point);
214 
215 static int
showdata(Plumbmsg * msg)216 showdata(Plumbmsg *msg)
217 {
218 	char *s;
219 
220 	s = plumblookup(msg->attr, "action");
221 	return s && strcmp(s, "showdata")==0;
222 }
223 
224 /* correspond to entries in miditems[] below,
225  * changing one means you need to change
226  */
227 enum{
228 	Restore = 0,
229 	Zin,
230 	Fit,
231 	Rot,
232 	Upside,
233 	Empty1,
234 	Next,
235 	Prev,
236 	Zerox,
237 	Empty2,
238 	Reverse,
239 	Del,
240 	Write,
241 	Empty3,
242 	Exit,
243 };
244 
245 void
viewer(Document * dd)246 viewer(Document *dd)
247 {
248 	int i, fd, n, oldpage;
249 	int nxt, a;
250 	Channel *cp;
251 	Menu menu, midmenu;
252 	Mouse m;
253 	Keyboardctl *kc;
254 	Point dxy, oxy, xy0;
255 	Rune run;
256 	Rectangle r;
257 	int size[2];
258 	Image *tmp;
259 	PDFInfo *pdf;
260 	PSInfo *ps;
261 	static char *fwditems[] = { "this page", "next page", "exit", 0 };
262  	static char *miditems[] = {
263  		"orig size",
264  		"zoom in",
265  		"fit window",
266  		"rotate 90",
267  		"upside down",
268  		"",
269  		"next",
270  		"prev",
271 		"zerox",
272  		"",
273  		"reverse",
274  		"discard",
275  		"write",
276  		"",
277  		"quit",
278  		0
279  	};
280 	char *s;
281 	enum {
282 	    CMouse,
283 	    CResize,
284 	    CKeyboard,
285 	    CPlumb,
286 	    CN
287 	};
288 	Alt alts[CN+1];
289 	Plumbmsg *pm;
290 
291 	cp = chancreate(sizeof pm, 0);
292 	assert(cp);
293 
294 	doc = dd;    /* save global for menuhit */
295 	ul = screen->r.min;
296 	mc = initmouse(nil, screen);
297 	kc = initkeyboard(nil);
298 	alts[CMouse].c = mc->c;
299 	alts[CMouse].v = &m;
300 	alts[CMouse].op = CHANRCV;
301 	alts[CResize].c = mc->resizec;
302 	alts[CResize].v = &size;
303 	alts[CResize].op = CHANRCV;
304 	alts[CKeyboard].c = kc->c;
305 	alts[CKeyboard].v = &run;
306 	alts[CKeyboard].op = CHANRCV;
307 	alts[CPlumb].c = cp;
308 	alts[CPlumb].v = &pm;
309 	alts[CPlumb].op = CHANNOP;
310 	alts[CN].op = CHANEND;
311 
312 	/* XXX: Event */
313 	if(doc->addpage != nil) {
314 		alts[CPlumb].op = CHANRCV;
315 		proccreate(plumbproc, cp, 16384);
316 	}
317 
318 	setcursor(mc, &reading);
319 	r.min = ZP;
320 
321 	/*
322 	 * im is a global pointer to the current image.
323 	 * eventually, i think we will have a layer between
324 	 * the display routines and the ps/pdf/whatever routines
325 	 * to perhaps cache and handle images of different
326 	 * sizes, etc.
327 	 */
328 	im = 0;
329 	page = reverse ? doc->npage-1 : 0;
330 
331 	if(doc->fwdonly) {
332 		menu.item = fwditems;
333 		menu.gen = 0;
334 		menu.lasthit = 0;
335 	} else {
336 		menu.item = 0;
337 		menu.gen = menugen;
338 		menu.lasthit = 0;
339 	}
340 
341 	midmenu.item = miditems;
342 	midmenu.gen = 0;
343 	midmenu.lasthit = Next;
344 
345 	showpage(page, &menu);
346 	setcursor(mc, nil);
347 
348 	nxt = 0;
349 	for(;;) {
350 		/*
351 		 * throughout, if doc->fwdonly is set, we restrict the functionality
352 		 * a fair amount.  we don't care about doc->npage anymore, and
353 		 * all that can be done is select the next page.
354 		 */
355 		unlockdisplay(display);
356 		a = alt(alts);
357 		lockdisplay(display);
358 		switch(a) {
359 		case CKeyboard:
360 			if(run <= 0xFF && isdigit(run)) {
361 				nxt = nxt*10+run-'0';
362 				break;
363 			} else if(run != '\n')
364 				nxt = 0;
365 			switch(run) {
366 			case 'r':	/* reverse page order */
367 				if(doc->fwdonly)
368 					break;
369 				reverse = !reverse;
370 				menu.lasthit = doc->npage-1-menu.lasthit;
371 
372 				/*
373 				 * the theory is that if we are reversing the
374 				 * document order and are on the first or last
375 				 * page then we're just starting and really want
376 		 	 	 * to view the other end.  maybe the if
377 				 * should be dropped and this should happen always.
378 				 */
379 				if(page == 0 || page == doc->npage-1) {
380 					page = doc->npage-1-page;
381 					showpage(page, &menu);
382 				}
383 				break;
384 			case 'w':	/* write bitmap of current screen */
385 				setcursor(mc, &reading);
386 				s = writebitmap();
387 				if(s)
388 					string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
389 						display->defaultfont, s);
390 				setcursor(mc, nil);
391 				flushimage(display, 1);
392 				break;
393 			case 'd':	/* remove image from working set */
394 				if(doc->rmpage && page < doc->npage) {
395 					if(doc->rmpage(doc, page) >= 0) {
396 						if(doc->npage < 0)
397 							wexits(0);
398 						if(page >= doc->npage)
399 							page = doc->npage-1;
400 						showpage(page, &menu);
401 					}
402 				}
403 				break;
404 			case 'q':
405 			case 0x04: /* ctrl-d */
406 				wexits(0);
407 			case 'u':
408 				if(im==nil)
409 					break;
410 				setcursor(mc, &reading);
411 				rot180(im);
412 				setcursor(mc, nil);
413 				angle = (angle+180) % 360;
414 				redraw(screen);
415 				flushimage(display, 1);
416 				break;
417 			case '-':
418 			case '\b':
419 			case Kleft:
420 				if(page > 0 && !doc->fwdonly) {
421 					--page;
422 					showpage(page, &menu);
423 				}
424 				break;
425 			case '\n':
426 				if(nxt) {
427 					nxt--;
428 					if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly)
429 						showpage(page=nxt, &menu);
430 					nxt = 0;
431 					break;
432 				}
433 				goto Gotonext;
434 			case Kright:
435 			case ' ':
436 			Gotonext:
437 				if(doc->npage && ++page >= doc->npage && !doc->fwdonly)
438 					wexits(0);
439 				showpage(page, &menu);
440 				break;
441 
442 			/*
443 			 * The upper y coordinate of the image is at ul.y in screen->r.
444 			 * Panning up means moving the upper left corner down.  If the
445 			 * upper left corner is currently visible, we need to go back a page.
446 			 */
447 			case Kup:
448 				if(screen->r.min.y <= ul.y && ul.y < screen->r.max.y){
449 					if(page > 0 && !doc->fwdonly){
450 						--page;
451 						showbottom = 1;
452 						showpage(page, &menu);
453 					}
454 				} else {
455 					i = Dy(screen->r)/2;
456 					if(i > 10)
457 						i -= 10;
458 					if(i+ul.y > screen->r.min.y)
459 						i = screen->r.min.y - ul.y;
460 					translate(Pt(0, i));
461 				}
462 				break;
463 
464 			/*
465 			 * If the lower y coordinate is on the screen, we go to the next page.
466 			 * The lower y coordinate is at ul.y + Dy(im->r).
467 			 */
468 			case Kdown:
469 				i = ul.y + Dy(im->r);
470 				if(screen->r.min.y <= i && i <= screen->r.max.y){
471 					ul.y = screen->r.min.y;
472 					goto Gotonext;
473 				} else {
474 					i = -Dy(screen->r)/2;
475 					if(i < -10)
476 						i += 10;
477 					if(i+ul.y+Dy(im->r) <= screen->r.max.y)
478 						i = screen->r.max.y - Dy(im->r) - ul.y - 1;
479 					translate(Pt(0, i));
480 				}
481 				break;
482 			default:
483 				setcursor(mc, &query);
484 				sleep(1000);
485 				setcursor(mc, nil);
486 				break;
487 			}
488 			break;
489 
490 		case CMouse:
491 			switch(m.buttons){
492 			case Left:
493 				oxy = m.xy;
494 				xy0 = oxy;
495 				do {
496 					dxy = subpt(m.xy, oxy);
497 					oxy = m.xy;
498 					translate(dxy);
499 					recv(mc->c, &m);
500 				} while(m.buttons == Left);
501 				if(m.buttons) {
502 					dxy = subpt(xy0, oxy);
503 					translate(dxy);
504 				}
505 				break;
506 
507 			case Middle:
508 				if(doc->npage == 0)
509 					break;
510 
511 				n = menuhit(Middle, mc, &midmenu, nil);
512 				if(n == -1)
513 					break;
514 				switch(n){
515 				case Next: 	/* next */
516 					if(reverse)
517 						page--;
518 					else
519 						page++;
520 					if(page < 0) {
521 						if(reverse) return;
522 						else page = 0;
523 					}
524 
525 					if((page >= doc->npage) && !doc->fwdonly)
526 						return;
527 
528 					showpage(page, &menu);
529 					nxt = 0;
530 					break;
531 				case Prev:	/* prev */
532 					if(reverse)
533 						page++;
534 					else
535 						page--;
536 					if(page < 0) {
537 						if(reverse) return;
538 						else page = 0;
539 					}
540 
541 					if((page >= doc->npage) && !doc->fwdonly && !reverse)
542 						return;
543 
544 					showpage(page, &menu);
545 					nxt = 0;
546 					break;
547 				case Zerox:	/* prev */
548 					zerox();
549 					break;
550 				case Zin:	/* zoom in */
551 					if (dd->type == Tpdf){		/* pdf */
552 						pdf = (PDFInfo *) dd->extra;
553 						if (pdf != nil){
554 							ppi+= 50;
555 							setdim(&pdf->gs, Rect(0,0,0,0), ppi, 0);
556 							showpage(page, &menu);
557 						}
558 						break;
559 					}
560 					if (dd->type == Tps){		/* ps */
561 						ps = (PSInfo *) dd->extra;
562 						if (ps != nil){
563 							ppi+= 50;
564 							setdim(&ps->gs, Rect(0,0,0,0), ppi, 0);
565 							showpage(page, &menu);
566 						}
567 						break;
568 					}
569 					else{ 	/* image */
570 						double delta;
571 						Rectangle r;
572 
573 						r = getrect(Middle, mc);
574 						if((rectclip(&r, rectaddpt(im->r, ul)) == 0) ||
575 							Dx(r) == 0 || Dy(r) == 0)
576 							break;
577 						/* use the smaller side to expand */
578 						if(Dx(r) < Dy(r))
579 							delta = (double)Dx(im->r)/(double)Dx(r);
580 						else
581 							delta = (double)Dy(im->r)/(double)Dy(r);
582 
583 						setcursor(mc, &reading);
584 						tmp = xallocimage(display,
585 								Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta)),
586 								im->chan, 0, DBlack);
587 						if(tmp == nil) {
588 							fprint(2, "out of memory during zoom: %r\n");
589 							wexits("memory");
590 						}
591 						resample(im, tmp);
592 						im = tmp;
593 						delayfreeimage(tmp);
594 						setcursor(mc, nil);
595 						ul = screen->r.min;
596 						redraw(screen);
597 						flushimage(display, 1);
598 						break;
599 					}
600 				case Fit:	/* fit */
601 					/* no op if pdf or ps*/
602 					if (dd->type == Tgfx){
603 						fitwin = 1;
604 						fit();
605 					}
606 					break;
607 				case Rot:	/* rotate 90 */
608 					angle = (angle+90) % 360;
609 					showpage(page, &menu);
610 					break;
611 				case Upside: 	/* upside-down */
612 					angle = (angle+180) % 360;
613 					showpage(page, &menu);
614 					break;
615 				case Restore:	/* restore */
616 					if (dd->type == Tpdf){		/* pdf */
617 						pdf = (PDFInfo *) dd->extra;
618 						if (pdf != nil){
619 							ppi = 100;
620 							setdim(&pdf->gs, Rect(0,0,0,0), ppi, 0);
621 						}
622 						showpage(page, &menu);
623 						break;
624 					}
625 					if (dd->type == Tps){		/* ps */
626 						ps = (PSInfo *) dd->extra;
627 						if (ps != nil){
628 							ppi = 100;
629 							setdim(&ps->gs, Rect(0,0,0,0), ppi, 0);
630 						}
631 						showpage(page, &menu);
632 						break;
633 					}
634 					fitwin = 0;
635 					showpage(page, &menu);
636 					break;
637 				case Reverse:	/* reverse */
638 					if(doc->fwdonly)
639 						break;
640 					reverse = !reverse;
641 					menu.lasthit = doc->npage-1-menu.lasthit;
642 
643 					if(page == 0 || page == doc->npage-1) {
644 						page = doc->npage-1-page;
645 						showpage(page, &menu);
646 					}
647 					break;
648 				case Write: /* write */
649 					setcursor(mc, &reading);
650 					s = writebitmap();
651 					if(s)
652 						string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
653 							display->defaultfont, s);
654 					setcursor(mc, nil);
655 					flushimage(display, 1);
656 					break;
657 				case Del: /* delete */
658 					if(doc->rmpage && page < doc->npage) {
659 						if(doc->rmpage(doc, page) >= 0) {
660 							if(doc->npage < 0)
661 								wexits(0);
662 							if(page >= doc->npage)
663 								page = doc->npage-1;
664 							showpage(page, &menu);
665 						}
666 					}
667 					break;
668 				case Exit:	/* exit */
669 					return;
670 				case Empty1:
671 				case Empty2:
672 				case Empty3:
673 					break;
674 
675 				};
676 
677 
678 
679 			case Right:
680 				if(doc->npage == 0)
681 					break;
682 
683 				oldpage = page;
684 				n = menuhit(RMenu, mc, &menu, nil);
685 				if(n == -1)
686 					break;
687 
688 				if(doc->fwdonly) {
689 					switch(n){
690 					case 0:	/* this page */
691 						break;
692 					case 1:	/* next page */
693 						showpage(++page, &menu);
694 						break;
695 					case 2:	/* exit */
696 						return;
697 					}
698 					break;
699 				}
700 
701 				if(n == doc->npage)
702 					return;
703 				else
704 					page = reverse ? doc->npage-1-n : n;
705 
706 				if(oldpage != page)
707 					showpage(page, &menu);
708 				nxt = 0;
709 				break;
710 			}
711 			break;
712 		case CResize:
713 			r = screen->r;
714 			if(getwindow(display, Refnone) < 0)
715 				fprint(2,"can't reattach to window");
716 			ul = addpt(ul, subpt(screen->r.min, r.min));
717 			redraw(screen);
718 			flushimage(display, 1);
719 			break;
720 		case CPlumb:
721 			if(pm->ndata <= 0){
722 				plumbfree(pm);
723 				break;
724 			}
725 			if(showdata(pm)) {
726 				s = estrdup("/tmp/pageplumbXXXXXXX");
727 				fd = opentemp(s, ORDWR|ORCLOSE);
728 				write(fd, pm->data, pm->ndata);
729 				/* lose fd reference on purpose; the file is open ORCLOSE */
730 			} else if(pm->data[0] == '/') {
731 				s = estrdup(pm->data);
732 			} else {
733 				s = emalloc(strlen(pm->wdir)+1+pm->ndata+1);
734 				sprint(s, "%s/%s", pm->wdir, pm->data);
735 				cleanname(s);
736 			}
737 			if((i = doc->addpage(doc, s)) >= 0) {
738 				page = i;
739 				unhide();
740 				showpage(page, &menu);
741 			}
742 			free(s);
743 			plumbfree(pm);
744 			break;
745 		}
746 	}
747 }
748 
749 Image *gray;
750 
751 /*
752  * A draw operation that touches only the area contained in bot but not in top.
753  * mp and sp get aligned with bot.min.
754  */
755 static void
gendrawdiff(Image * dst,Rectangle bot,Rectangle top,Image * src,Point sp,Image * mask,Point mp,int op)756 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
757 	Image *src, Point sp, Image *mask, Point mp, int op)
758 {
759 	Rectangle r;
760 	Point origin;
761 	Point delta;
762 
763 	USED(op);
764 
765 	if(Dx(bot)*Dy(bot) == 0)
766 		return;
767 
768 	/* no points in bot - top */
769 	if(rectinrect(bot, top))
770 		return;
771 
772 	/* bot - top ≡ bot */
773 	if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
774 		gendrawop(dst, bot, src, sp, mask, mp, op);
775 		return;
776 	}
777 
778 	origin = bot.min;
779 	/* split bot into rectangles that don't intersect top */
780 	/* left side */
781 	if(bot.min.x < top.min.x){
782 		r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
783 		delta = subpt(r.min, origin);
784 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
785 		bot.min.x = top.min.x;
786 	}
787 
788 	/* right side */
789 	if(bot.max.x > top.max.x){
790 		r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
791 		delta = subpt(r.min, origin);
792 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
793 		bot.max.x = top.max.x;
794 	}
795 
796 	/* top */
797 	if(bot.min.y < top.min.y){
798 		r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
799 		delta = subpt(r.min, origin);
800 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
801 		bot.min.y = top.min.y;
802 	}
803 
804 	/* bottom */
805 	if(bot.max.y > top.max.y){
806 		r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
807 		delta = subpt(r.min, origin);
808 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
809 		bot.max.y = top.max.y;
810 	}
811 }
812 
813 static void
drawdiff(Image * dst,Rectangle bot,Rectangle top,Image * src,Image * mask,Point p,int op)814 drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p, int op)
815 {
816 	gendrawdiff(dst, bot, top, src, p, mask, p, op);
817 }
818 
819 /*
820  * Translate the image in the window by delta.
821  */
822 static void
translate(Point delta)823 translate(Point delta)
824 {
825 	Point u;
826 	Rectangle r, or;
827 
828 	if(im == nil)
829 		return;
830 
831 	u = pclip(addpt(ul, delta), ulrange);
832 	delta = subpt(u, ul);
833 	if(delta.x == 0 && delta.y == 0)
834 		return;
835 
836 	/*
837 	 * The upper left corner of the image is currently at ul.
838 	 * We want to move it to u.
839 	 */
840 	or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul);
841 	r = rectaddpt(or, delta);
842 
843 	drawop(screen, r, screen, nil, ul, S);
844 	ul = u;
845 
846 	/* fill in gray where image used to be but isn't. */
847 	drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP, S);
848 
849 	/* fill in black border */
850 	drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP, S);
851 
852 	/* fill in image where it used to be off the screen. */
853 	if(rectclip(&or, screen->r))
854 		drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min, S);
855 	else
856 		drawop(screen, r, im, nil, im->r.min, S);
857 	flushimage(display, 1);
858 }
859 
860 void
redraw(Image * screen)861 redraw(Image *screen)
862 {
863 	Rectangle r;
864 
865 	if(im == nil)
866 		return;
867 
868 	ulrange.max = screen->r.max;
869 	ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r)));
870 
871 	ul = pclip(ul, ulrange);
872 	drawop(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min)), S);
873 
874 	if(im->repl)
875 		return;
876 
877 	/* fill in any outer edges */
878 	/* black border */
879 	r = rectaddpt(im->r, subpt(ul, im->r.min));
880 	border(screen, r, -2, display->black, ZP);
881 	r.min = subpt(r.min, Pt(2,2));
882 	r.max = addpt(r.max, Pt(2,2));
883 
884 	/* gray for the rest */
885 	if(gray == nil) {
886 		gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF);
887 		if(gray == nil) {
888 			fprint(2, "g out of memory: %r\n");
889 			wexits("mem");
890 		}
891 	}
892 	border(screen, r, -4000, gray, ZP);
893 //	flushimage(display, 0);
894 }
895 
896 /* clip p to be in r */
897 Point
pclip(Point p,Rectangle r)898 pclip(Point p, Rectangle r)
899 {
900 	if(p.x < r.min.x)
901 		p.x = r.min.x;
902 	else if(p.x >= r.max.x)
903 		p.x = r.max.x-1;
904 
905 	if(p.y < r.min.y)
906 		p.y = r.min.y;
907 	else if(p.y >= r.max.y)
908 		p.y = r.max.y-1;
909 
910 	return p;
911 }
912 
913 /*
914  * resize is perhaps a misnomer.
915  * this really just grows the window to be at least dx across
916  * and dy high.  if the window hits the bottom or right edge,
917  * it is backed up until it hits the top or left edge.
918  */
919 void
resize(int dx,int dy)920 resize(int dx, int dy)
921 {
922 	static Rectangle sr;
923 	Rectangle r, or;
924 
925 	r = screen->r;
926 	if(Dx(sr)*Dy(sr) == 0) {
927 		sr = screenrect();
928 		/* Start with the size of the first image */
929 		r.max.x = r.min.x;
930 		r.max.y = r.min.y;
931 	}
932 
933 	if(Dx(r) >= dx && Dy(r) >= dy)
934 		return;
935 
936 	or = r;
937 
938 	r.max.x = max(r.min.x+dx, r.max.x);
939 	r.max.y = max(r.min.y+dy, r.max.y);
940 	if(r.max.x > sr.max.x){
941 		if(Dx(r) > Dx(sr)){
942 			r.min.x = 0;
943 			r.max.x = sr.max.x;
944 		}else
945 			r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0));
946 	}
947 	if(r.max.y > sr.max.y){
948 		if(Dy(r) > Dy(sr)){
949 			r.min.y = 0;
950 			r.max.y = sr.max.y;
951 		}else
952 			r = rectaddpt(r, Pt(0, sr.max.y-r.max.y));
953 	}
954 
955 	/*
956 	 * Sometimes we can't actually grow the window big enough,
957 	 * and resizing it to the same shape makes it flash.
958 	 */
959 	if(Dx(r) == Dx(or) && Dy(r) == Dy(or))
960 		return;
961 
962 	drawresizewindow(r);
963 }
964 
965 /*
966  * If we allocimage after a resize but before flushing the draw buffer,
967  * we won't have seen the reshape event, and we won't have called
968  * getwindow, and allocimage will fail.  So we flushimage before every alloc.
969  */
970 Image*
xallocimage(Display * d,Rectangle r,ulong chan,int repl,ulong val)971 xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val)
972 {
973 	flushimage(display, 0);
974 	return allocimage(d, r, chan, repl, val);
975 }
976 
977 void
plumbproc(void * c)978 plumbproc(void *c)
979 {
980 	Channel *cp;
981 	CFid *fd;
982 
983 	cp = c;
984 	fd = plumbopenfid("image", OREAD|OCEXEC);
985 	if(fd == nil) {
986 		fprint(2, "Cannot connect to the plumber");
987 		threadexits("plumber");
988 	}
989 	for(;;) {
990 		send(cp, plumbrecvfid(fd));
991 	}
992 }
993 
994 /* XXX: This function is ugly and hacky. There may be a better way... or not */
995 Rectangle
screenrect(void)996 screenrect(void)
997 {
998 	int fd[3], pfd[2];
999 	int n, w, h;
1000 	char buf[64];
1001 	char *p, *pr;
1002 
1003 	if(pipe(pfd) < 0)
1004 		wexits("pipe failed");
1005 
1006 	fd[0] = open("/dev/null", OREAD);
1007 	fd[1] = pfd[1];
1008 	fd[2] = dup(2, -1);
1009 	if(threadspawnl(fd, "rc", "rc", "-c", "xdpyinfo | grep 'dimensions:'", nil) == -1)
1010 		wexits("threadspawnl failed");
1011 
1012 	if((n = read(pfd[0], buf, 63)) <= 0)
1013 		wexits("read xdpyinfo failed");
1014 	close(fd[0]);
1015 
1016 	buf[n] = '\0';
1017 	for(p = buf; *p; p++)
1018 		if(*p >= '0' && *p <= '9') break;
1019 	if(*p == '\0')
1020 		wexits("xdpyinfo parse failed");
1021 
1022 	w = strtoul(p, &pr, 10);
1023 	if(p == pr || *pr == '\0' || *(++pr) == '\0')
1024 		wexits("xdpyinfo parse failed");
1025 	h = strtoul(pr, &p, 10);
1026 	if(p == pr)
1027 		wexits("xdpyinfo parse failed");
1028 
1029 	return Rect(0, 0, w, h);
1030 }
1031 
1032 void
zerox(void)1033 zerox(void)
1034 {
1035 	int pfd[2];
1036 	int fd[3];
1037 
1038 	pipe(pfd);
1039 	fd[0] = pfd[0];
1040 	fd[1] = dup(1, -1);
1041 	fd[2] = dup(2, -1);
1042 	threadspawnl(fd, "page", "page", "-R", nil);
1043 
1044 	writeimage(pfd[1], im, 0);
1045 	close(pfd[1]);
1046 }
1047 
1048 void
fit()1049 fit()
1050 {
1051 	double delta;
1052 	Rectangle r;
1053 	Image* tmp;
1054 
1055 	delta = (double)Dx(screen->r)/(double)Dx(im->r);
1056 	if((double)Dy(im->r)*delta > Dy(screen->r))
1057 		delta = (double)Dy(screen->r)/(double)Dy(im->r);
1058 	r = Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta));
1059 	setcursor(mc, &reading);
1060 	tmp = xallocimage(display, r, im->chan, 0, DBlack);
1061 	if(tmp == nil) {
1062 		fprint(2, "out of memory during fit: %r\n");
1063 		wexits("memory");
1064 	}
1065 	resample(im, tmp);
1066 	im = tmp;
1067 	delayfreeimage(tmp);
1068 	setcursor(mc, nil);
1069 	ul = screen->r.min;
1070 	redraw(screen);
1071 	flushimage(display, 1);
1072 }
1073