Ejemplo n.º 1
0
Archivo: ps.c Proyecto: 00001/plan9port
static Image*
psdrawpage(Document *d, int page)
{
	PSInfo *ps = d->extra;
	Image *im;

	if(ps->clueless)
		return convert(&ps->gs.g);

	waitgs(&ps->gs);

	if(goodps)
		pswritepage(d, ps->gs.gsfd, page);
	else {
		pswritepage(d, ps->gs.gsfd, -1);
		pswritepage(d, ps->gs.gsfd, page);
		pswritepage(d, ps->gs.gsfd, d->npage);
	}
	/*
	 * If last line terminator is \r, gs will read ahead to check for \n
	 * so send one to avoid deadlock.
	 */
	write(ps->gs.gsfd, "\n", 1);
	im = convert(&ps->gs.g);
	if(im == nil) {
		fprint(2, "fatal: readimage error %r\n");
		wexits("readimage");
	}
	waitgs(&ps->gs);

	return im;
}
Ejemplo n.º 2
0
void
redraw(Image *screen)
{
	Rectangle r;

	if(im == nil)
		return;

	ulrange.max = screen->r.max;
	ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r)));

	ul = pclip(ul, ulrange);
	drawop(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min)), S);

	if(im->repl)
		return;

	/* fill in any outer edges */
	/* black border */
	r = rectaddpt(im->r, subpt(ul, im->r.min));
	border(screen, r, -2, display->black, ZP);
	r.min = subpt(r.min, Pt(2,2));
	r.max = addpt(r.max, Pt(2,2));

	/* gray for the rest */
	if(gray == nil) {
		gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF);
		if(gray == nil) {
			fprint(2, "g out of memory: %r\n");
			wexits("mem");
		}
	}
	border(screen, r, -4000, gray, ZP);
//	flushimage(display, 0);
}
Ejemplo n.º 3
0
void
showpage(int page, Menu *m)
{
	if(doc->fwdonly)
		m->lasthit = 0;	/* this page */
	else
		m->lasthit = reverse ? doc->npage-1-page : page;

	esetcursor(&reading);
	delayfreeimage(nil);
	im = cachedpage(doc, angle, page);
	if(im == nil)
		wexits(0);
	if(resizing)
		resize(Dx(im->r), Dy(im->r));

	esetcursor(nil);
	if(showbottom){
		ul.y = screen->r.max.y - Dy(im->r);
		showbottom = 0;
	}

	redraw(screen);
	flushimage(display, 1);
}
Ejemplo n.º 4
0
Archivo: util.c Proyecto: aahud/harvey
char*
estrdup(char *s)
{
	char *t;
	if((t = strdup(s)) == nil) {
		fprint(2, "out of memory in strdup(%.10s)\n", s);
		wexits("mem");
	}
	return t;
}
Ejemplo n.º 5
0
Archivo: util.c Proyecto: aahud/harvey
void*
erealloc(void *v, int sz)
{
	v = realloc(v, sz);
	if(v == nil) {
		fprint(2, "out of memory allocating %d\n", sz);
		wexits("mem");
	}
	return v;
}
Ejemplo n.º 6
0
Archivo: util.c Proyecto: aahud/harvey
void*
emalloc(int sz)
{
	void *v;
	v = malloc(sz);
	if(v == nil) {
		fprint(2, "out of memory allocating %d\n", sz);
		wexits("mem");
	}
	memset(v, 0, sz);
	return v;
}
Ejemplo n.º 7
0
Archivo: gs.c Proyecto: 99years/plan9
void
waitgs(GSInfo *gs)
{
	/* we figure out that gs is done by telling it to
	 * print something and waiting until it does.
	 */
	char *p;
	Biobuf *b = &gs->gsrd;
	uchar buf[1024];
	int n;

//	gscmd(gs, "(\\n**bstack\\n) print flush\n");
//	gscmd(gs, "stack flush\n");
//	gscmd(gs, "(**estack\\n) print flush\n");
	gscmd(gs, "(\\n//GO.SYSIN DD\\n) PAGE==\n");

	alarm(300*1000);
	for(;;) {
		p = Brdline(b, '\n');
		if(p == nil) {
			n = Bbuffered(b);
			if(n <= 0)
				break;
			if(n > sizeof buf)
				n = sizeof buf;
			Bread(b, buf, n);
			continue;
		}
		p[Blinelen(b)-1] = 0;
		if(chatty) fprint(2, "p: ");
		if(chatty) write(2, p, Blinelen(b)-1);
		if(chatty) fprint(2, "\n");
		if(strstr(p, "Error:")) {
			alarm(0);
			fprint(2, "ghostscript error: %s\n", p);
			wexits("gs error");
		}

		if(strstr(p, "//GO.SYSIN DD")) {
			break;
		}
	}
	alarm(0);
}
Ejemplo n.º 8
0
Archivo: gs.c Proyecto: 00001/plan9port
void
spawnreader(void *cp)
{
	int n, fd, pfd[2];
	char buf[1024];

	recv(cp, &fd);

	if(pipe(pfd)<0)
		wexits("pipe failed");

	send(cp, &pfd[1]);

	while((n=read(pfd[0], buf, sizeof buf)) > 0) {
		write(1, buf, n);
		write(fd, buf, n);
	}

	close(pfd[0]);
	threadexits(0);
}
Ejemplo n.º 9
0
Archivo: gs.c Proyecto: 99years/plan9
int
spawnreader(int fd)
{
	int n, pfd[2];
	char buf[1024];

	if(pipe(pfd)<0)
		return -1;
	switch(fork()){
	case -1:
		return -1;
	case 0:
		break;
	default:
		close(pfd[0]);
		return pfd[1];
	}

	close(pfd[1]);
	switch(fork()){
	case -1:
		wexits("fork failed");
	case 0:
		while((n=read(fd, buf, sizeof buf)) > 0) {
			write(1, buf, n);
			write(pfd[0], buf, n);
		}
		break;
	default:
		while((n=read(pfd[0], buf, sizeof buf)) > 0) {
			write(1, buf, n);
			write(fd, buf, n);
		}
		break;
	}
	postnote(PNGROUP, getpid(), "i'm die-ing");
	_exits(0);
	return -1;
}
Ejemplo n.º 10
0
static Image*
_cachedpage(Document *doc, int angle, int page, char *ra)
{
	int i;
	Cached *c, old;
	Image *im, *tmp;

	if((page < 0 || page >= doc->npage) && !doc->fwdonly)
		return nil;

Again:
	for(i=0; i<nelem(cache); i++){
		c = &cache[i];
		if(c->doc == doc && c->angle == angle && c->page == page){
			if(chatty) fprint(2, "cache%s hit %d\n", ra, page);
			goto Found;
		}
		if(c->doc == nil)
			break;
	}

	if(i >= nelem(cache))
		i = nelem(cache)-1;
	c = &cache[i];
	if(c->im)
		freeimage(c->im);
	c->im = nil;
	c->doc = nil;
	c->page = -1;

	if(chatty) fprint(2, "cache%s load %d\n", ra, page);
	im = doc->drawpage(doc, page);
	if(im == nil){
		if(doc->fwdonly)	/* end of file */
			wexits(0);
		im = questionmark();
		if(im == nil){
		Flush:
			if(i > 0){
				cacheflush();
				goto Again;
			}
			fprint(2, "out of memory: %r\n");
			wexits("memory");
		}
		return im;
	}

	if(im->r.min.x != 0 || im->r.min.y != 0){
		/* translate to 0,0 */
		tmp = xallocimage(display, Rect(0, 0, Dx(im->r), Dy(im->r)), im->chan, 0, DNofill);
		if(tmp == nil){
			freeimage(im);
			goto Flush;
		}
		drawop(tmp, tmp->r, im, nil, im->r.min, S);
		freeimage(im);
		im = tmp;
	}

	switch(angle){
	case 90:
		im = rot90(im);
		break;
	case 180:
		rot180(im);
		break;
	case 270:
		im = rot270(im);
		break;
	}
	if(im == nil)
		goto Flush;

	c->doc = doc;
	c->page = page;
	c->angle = angle;
	c->im = im;

Found:
	if(chatty) fprint(2, "cache%s mtf %d @%d:", ra, c->page, i);
	old = *c;
	memmove(cache+1, cache, (c-cache)*sizeof cache[0]);
	cache[0] = old;
	if(chatty){
		for(i=0; i<nelem(cache); i++)
			fprint(2, " %d", cache[i].page);
		fprint(2, "\n");
	}
	if(chatty) fprint(2, "cache%s return %d %p\n", ra, old.page, old.im);
	return old.im;
}
Ejemplo n.º 11
0
Archivo: ps.c Proyecto: 00001/plan9port
Document*
initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
{
	Document *d;
	PSInfo *ps;
	char *p;
	char *q, *r;
	char eol;
	char *nargv[1];
	char fdbuf[20];
	char tmp[32];
	int fd;
	int i;
	int incomments;
	int cantranslate;
	int trailer=0;
	int nesting=0;
	int dumb=0;
	int landscape=0;
	long psoff;
	long npage, mpage;
	Page *page;
	Rectangle bbox = Rect(0,0,0,0);

	if(argc > 1) {
		fprint(2, "can only view one ps file at a time\n");
		return nil;
	}

	fprint(2, "reading through postscript...\n");
	if(b == nil){	/* standard input; spool to disk (ouch) */
		fd = spooltodisk(buf, nbuf, nil);
		sprint(fdbuf, "/dev/fd/%d", fd);
		b = Bopen(fdbuf, OREAD);
		if(b == nil){
			fprint(2, "cannot open disk spool file\n");
			wexits("Bopen temp");
		}
		nargv[0] = fdbuf;
		argv = nargv;
	}

	/* find %!, perhaps after PCL nonsense */
	Bseek(b, 0, 0);
	psoff = 0;
	eol = 0;
	for(i=0; i<16; i++){
		psoff = Boffset(b);
		if(!(p = Brdline(b, eol='\n')) && !(p = Brdline(b, eol='\r'))) {
			fprint(2, "cannot find end of first line\n");
			wexits("initps");
		}
		if(p[0]=='\x1B')
			p++, psoff++;
		if(p[0] == '%' && p[1] == '!')
			break;
	}
	if(i == 16){
		werrstr("not ps");
		return nil;
	}

	/* page counting */
	npage = 0;
	mpage = 16;
	page = emalloc(mpage*sizeof(*page));
	memset(page, 0, mpage*sizeof(*page));

	cantranslate = goodps;
	incomments = 1;
Keepreading:
	while(p = Brdline(b, eol)) {
		if(p[0] == '%')
			if(chatty) fprint(2, "ps %.*s\n", utfnlen(p, Blinelen(b)-1), p);
		if(npage == mpage) {
			mpage *= 2;
			page = erealloc(page, mpage*sizeof(*page));
			memset(&page[npage], 0, npage*sizeof(*page));
		}

		if(p[0] != '%' || p[1] != '%')
			continue;

		if(prefix(p, "%%BeginDocument")) {
			nesting++;
			continue;
		}
		if(nesting > 0 && prefix(p, "%%EndDocument")) {
			nesting--;
			continue;
		}
		if(nesting)
			continue;

		if(prefix(p, "%%EndComment")) {
			incomments = 0;
			continue;
		}
		if(reverse == -1 && prefix(p, "%%PageOrder")) {
			/* glean whether we should reverse the viewing order */
			p[Blinelen(b)-1] = 0;
			if(strstr(p, "Ascend"))
				reverse = 0;
			else if(strstr(p, "Descend"))
				reverse = 1;
			else if(strstr(p, "Special"))
				dumb = 1;
			p[Blinelen(b)-1] = '\n';
			continue;
		} else if(prefix(p, "%%Trailer")) {
			incomments = 1;
			page[npage].offset = Boffset(b)-Blinelen(b);
			trailer = 1;
			continue;
		} else if(incomments && prefix(p, "%%Orientation")) {
			if(strstr(p, "Landscape"))
				landscape = 1;
		} else if(incomments && Dx(bbox)==0 && prefix(p, q="%%BoundingBox")) {
			bbox = rdbbox(p+strlen(q)+1);
			if(chatty)
				/* can't use %R because haven't initdraw() */
				fprint(2, "document bbox [%d %d %d %d]\n",
					RECT(bbox));
			continue;
		}

		/*
		 * If they use the initgraphics command, we can't play our translation tricks.
		 */
		p[Blinelen(b)-1] = 0;
		if((q=strstr(p, "initgraphics")) && ((r=strchr(p, '%'))==nil || r > q))
			cantranslate = 0;
		p[Blinelen(b)-1] = eol;

		if(!prefix(p, "%%Page:"))
			continue;

		/* 
		 * figure out of the %%Page: line contains a page number
		 * or some other page description to use in the menu bar.
		 * 
		 * lines look like %%Page: x y or %%Page: x
		 * we prefer just x, and will generate our
		 * own if necessary.
		 */
		p[Blinelen(b)-1] = 0;
		if(chatty) fprint(2, "page %s\n", p);
		r = p+7;
		while(*r == ' ' || *r == '\t')
			r++;
		q = r;
		while(*q && *q != ' ' && *q != '\t')
			q++;
		free(page[npage].name);
		if(*r) {
			if(*r == '"' && *q == '"')
				r++, q--;
			if(*q)
				*q = 0;
			page[npage].name = estrdup(r);
			*q = 'x';
		} else {
			snprint(tmp, sizeof tmp, "p %ld", npage+1);
			page[npage].name = estrdup(tmp);
		}

		/*
		 * store the offset info for later viewing
		 */
		trailer = 0;
		p[Blinelen(b)-1] = eol;
		page[npage++].offset = Boffset(b)-Blinelen(b);
	}
	if(Blinelen(b) > 0){
		fprint(2, "page: linelen %d\n", Blinelen(b));
		Bseek(b, Blinelen(b), 1);
		goto Keepreading;
	}

	if(Dx(bbox) == 0 || Dy(bbox) == 0)
		bbox = Rect(0,0,612,792);	/* 8½×11 */
	/*
	 * if we didn't find any pages, assume the document
	 * is one big page
	 */
	if(npage == 0) {
		dumb = 1;
		if(chatty) fprint(2, "don't know where pages are\n");
		reverse = 0;
		goodps = 0;
		trailer = 0;
		page[npage].name = "p 1";
		page[npage++].offset = 0;
	}

	if(npage+2 > mpage) {
		mpage += 2;
		page = erealloc(page, mpage*sizeof(*page));
		memset(&page[mpage-2], 0, 2*sizeof(*page));
	}

	if(!trailer)
		page[npage].offset = Boffset(b);

	Bseek(b, 0, 2); /* EOF */
	page[npage+1].offset = Boffset(b);

	d = emalloc(sizeof(*d));
	ps = emalloc(sizeof(*ps));
	ps->page = page;
	ps->npage = npage;
	ps->bbox = bbox;
	ps->psoff = psoff;

	d->extra = ps;
	d->npage = ps->npage;
	d->b = b;
	d->drawpage = psdrawpage;
	d->pagename = pspagename;

	d->fwdonly = ps->clueless = dumb;
	d->docname = argv[0];
	/*
	 * "tag" the doc as an image for now since there still is the "blank page"
	 * problem for ps files.
	 */
	d->type = Tgfx;

	if(spawngs(&ps->gs, "-dSAFER") < 0)
		return nil;

	if(!cantranslate)
		bbox.min = ZP;
	setdim(&ps->gs, bbox, ppi, landscape);

	if(goodps){
		/*
		 * We want to only send the page (i.e. not header and trailer) information
	 	 * for each page, so initialize the device by sending the header now.
		 */
		pswritepage(d, ps->gs.gsfd, -1);
		waitgs(&ps->gs);
	}

	if(dumb) {
		fprint(ps->gs.gsfd, "(%s) run\n", argv[0]);
		fprint(ps->gs.gsfd, "(/dev/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n");
	}

	ps->bbox = bbox;

	return d;
}
Ejemplo n.º 12
0
Archivo: gs.c Proyecto: 99years/plan9
int 
spawngs(GSInfo *g, char *safer)
{
	char *args[16];
	char tb[32], gb[32];
	int i, nargs;
	int devnull;
	int stdinout[2];
	int dataout[2];
	int errout[2];

	/*
	 * spawn gs
	 *
 	 * gs's standard input is fed from stdinout.
	 * gs output written to fd-2 (i.e. output we generate intentionally) is fed to stdinout.
	 * gs output written to fd 1 (i.e. ouptut gs generates on error) is fed to errout.
	 * gs data output is written to fd 3, which is dataout.
	 */
	if(pipe(stdinout) < 0 || pipe(dataout)<0 || pipe(errout)<0)
		return -1;

	nargs = 0;
	args[nargs++] = "gs";
	args[nargs++] = "-dNOPAUSE";
	args[nargs++] = safer;
	args[nargs++] = "-sDEVICE=plan9";
	args[nargs++] = "-sOutputFile=/fd/3";
	args[nargs++] = "-dQUIET";
	args[nargs++] = "-r100";
	sprint(tb, "-dTextAlphaBits=%d", textbits);
	sprint(gb, "-dGraphicsAlphaBits=%d", gfxbits);
	if(textbits)
		args[nargs++] = tb;
	if(gfxbits)
		args[nargs++] = gb;
	args[nargs++] = "-";
	args[nargs] = nil;

	gspid = fork();
	if(gspid == 0) {
		close(stdinout[1]);
		close(dataout[1]);
		close(errout[1]);

		/*
		 * Horrible problem: we want to dup fd's 0-4 below,
		 * but some of the source fd's might have those small numbers.
		 * So we need to reallocate those.  In order to not step on
		 * anything else, we'll dup the fd's to higher ones using
		 * dup(x, -1), but we need to use up the lower ones first.
		 */
		while((devnull = open("/dev/null", ORDWR)) < 5)
			;

		stdinout[0] = dup(stdinout[0], -1);
		errout[0] = dup(errout[0], -1);
		dataout[0] = dup(dataout[0], -1);

		dup(stdinout[0], 0);
		dup(errout[0], 1);
		dup(devnull, 2);	/* never anything useful */
		dup(dataout[0], 3);
		dup(stdinout[0], 4);
		for(i=5; i<20; i++)
			close(i);
		exec("/bin/gs", args);
		wexits("exec");
	}
	close(stdinout[0]);
	close(errout[0]);
	close(dataout[0]);
	atexit(killgs);

	if(teegs)
		stdinout[1] = spawnreader(stdinout[1]);

	gsfd = g->gsfd = stdinout[1];
	g->gsdfd = dataout[1];
	g->gspid = gspid;

	spawnmonitor(errout[1]);
	Binit(&g->gsrd, g->gsfd, OREAD);

	gscmd(g, "/PAGEOUT (/fd/4) (w) file def\n");
	gscmd(g, "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n");
	waitgs(g);

	return 0;
}
Ejemplo n.º 13
0
void
viewer(Document *dd)
{
	int i, fd, n, oldpage;
	int nxt;
	Menu menu, midmenu;
	Mouse m;
	Event e;
	Point dxy, oxy, xy0;
	Image *tmp;

	static char *fwditems[] = { "this page", "next page", "exit", 0 };
 	static char *miditems[] = {
 		"orig size",
 		"zoom in",
 		"fit window",
 		"rotate 90",
 		"upside down",
 		"",
 		"next",
 		"prev",
		"zerox",
 		"",
 		"reverse",
 		"discard",
 		"write",
 		"",
 		"quit",
 		0
 	};
	char *s;
	enum { Eplumb = 4 };
	Plumbmsg *pm;

	doc = dd;    /* save global for menuhit */
	ul = screen->r.min;
	einit(Emouse|Ekeyboard);
	if(doc->addpage != nil)
		eplumb(Eplumb, "image");

	esetcursor(&reading);

	/*
	 * im is a global pointer to the current image.
	 * eventually, i think we will have a layer between
	 * the display routines and the ps/pdf/whatever routines
	 * to perhaps cache and handle images of different
	 * sizes, etc.
	 */
	im = 0;
	page = reverse ? doc->npage-1 : 0;

	if(doc->fwdonly) {
		menu.item = fwditems;
		menu.gen = 0;
		menu.lasthit = 0;
	} else {
		menu.item = 0;
		menu.gen = menugen;
		menu.lasthit = 0;
	}

	midmenu.item = miditems;
	midmenu.gen = 0;
	midmenu.lasthit = Next;

	if(doc->docname != nil)
		setlabel(doc->docname);
	showpage(page, &menu);
	esetcursor(nil);

	nxt = 0;
	for(;;) {
		/*
		 * throughout, if doc->fwdonly is set, we restrict the functionality
		 * a fair amount.  we don't care about doc->npage anymore, and
		 * all that can be done is select the next page.
		 */
		unlockdisplay(display);
		i = eread(Emouse|Ekeyboard|Eplumb, &e);
		lockdisplay(display);
		switch(i){
		case Ekeyboard:
			if(e.kbdc <= 0xFF && isdigit(e.kbdc)) {
				nxt = nxt*10+e.kbdc-'0';
				break;
			} else if(e.kbdc != '\n')
				nxt = 0;
			switch(e.kbdc) {
			case 'r':	/* reverse page order */
				if(doc->fwdonly)
					break;
				reverse = !reverse;
				menu.lasthit = doc->npage-1-menu.lasthit;

				/*
				 * the theory is that if we are reversing the
				 * document order and are on the first or last
				 * page then we're just starting and really want
		 	 	 * to view the other end.  maybe the if
				 * should be dropped and this should happen always.
				 */
				if(page == 0 || page == doc->npage-1) {
					page = doc->npage-1-page;
					showpage(page, &menu);
				}
				break;
			case 'w':	/* write bitmap of current screen */
				esetcursor(&reading);
				s = writebitmap();
				if(s)
					string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
						display->defaultfont, s);
				esetcursor(nil);
				flushimage(display, 1);
				break;
			case 'd':	/* remove image from working set */
				if(doc->rmpage && page < doc->npage) {
					if(doc->rmpage(doc, page) >= 0) {
						if(doc->npage < 0)
							wexits(0);
						if(page >= doc->npage)
							page = doc->npage-1;
						showpage(page, &menu);
					}
				}
				break;
			case 'q':
			case 0x04: /* ctrl-d */
				wexits(0);
			case 'u':
				if(im==nil)
					break;
				angle = (angle+180) % 360;
				showpage(page, &menu);
				break;
			case '-':
			case '\b':
			case Kleft:
				if(page > 0 && !doc->fwdonly) {
					--page;
					showpage(page, &menu);
				}
				break;
			case '\n':
				if(nxt) {
					nxt--;
					if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly)
						showpage(page=nxt, &menu);
					nxt = 0;
					break;
				}
				goto Gotonext;
			case Kright:
			case ' ':
			Gotonext:
				if(doc->npage && ++page >= doc->npage && !doc->fwdonly)
					wexits(0);
				showpage(page, &menu);
				break;

			/*
			 * The upper y coordinate of the image is at ul.y in screen->r.
			 * Panning up means moving the upper left corner down.  If the
			 * upper left corner is currently visible, we need to go back a page.
			 */
			case Kup:
				if(screen->r.min.y <= ul.y && ul.y < screen->r.max.y){
					if(page > 0 && !doc->fwdonly){
						--page;
						showbottom = 1;
						showpage(page, &menu);
					}
				} else {
					i = Dy(screen->r)/2;
					if(i > 10)
						i -= 10;
					if(i+ul.y > screen->r.min.y)
						i = screen->r.min.y - ul.y;
					translate(Pt(0, i));
				}
				break;

			/*
			 * If the lower y coordinate is on the screen, we go to the next page.
			 * The lower y coordinate is at ul.y + Dy(im->r).
			 */
			case Kdown:
				i = ul.y + Dy(im->r);
				if(screen->r.min.y <= i && i <= screen->r.max.y){
					ul.y = screen->r.min.y;
					goto Gotonext;
				} else {
					i = -Dy(screen->r)/2;
					if(i < -10)
						i += 10;
					if(i+ul.y+Dy(im->r) <= screen->r.max.y)
						i = screen->r.max.y - Dy(im->r) - ul.y - 1;
					translate(Pt(0, i));
				}
				break;
			default:
				esetcursor(&query);
				sleep(1000);
				esetcursor(nil);
				break;
			}
			break;

		case Emouse:
			m = e.mouse;
			switch(m.buttons){
			case Left:
				oxy = m.xy;
				xy0 = oxy;
				do {
					dxy = subpt(m.xy, oxy);
					oxy = m.xy;
					translate(dxy);
					unlockdisplay(display);
					m = emouse();
					lockdisplay(display);
				} while(m.buttons == Left);
				if(m.buttons) {
					dxy = subpt(xy0, oxy);
					translate(dxy);
				}
				break;

			case Middle:
				if(doc->npage == 0)
					break;

				unlockdisplay(display);
				n = emenuhit(Middle, &m, &midmenu);
				lockdisplay(display);
				if(n == -1)
					break;
				switch(n){
				case Next: 	/* next */
					if(reverse)
						page--;
					else
						page++;
					if(page < 0) {
						if(reverse) return;
						else page = 0;
					}

					if((page >= doc->npage) && !doc->fwdonly)
						return;

					showpage(page, &menu);
					nxt = 0;
					break;
				case Prev:	/* prev */
					if(reverse)
						page++;
					else
						page--;
					if(page < 0) {
						if(reverse) return;
						else page = 0;
					}

					if((page >= doc->npage) && !doc->fwdonly && !reverse)
						return;

					showpage(page, &menu);
					nxt = 0;
					break;
				case Zerox:	/* prev */
					zerox();
					break;
				case Zin:	/* zoom in */
					{
						double delta;
						Rectangle r;

						r = egetrect(Middle, &m);
						if((rectclip(&r, rectaddpt(im->r, ul)) == 0) ||
							Dx(r) == 0 || Dy(r) == 0)
							break;
						/* use the smaller side to expand */
						if(Dx(r) < Dy(r))
							delta = (double)Dx(im->r)/(double)Dx(r);
						else
							delta = (double)Dy(im->r)/(double)Dy(r);

						esetcursor(&reading);
						tmp = xallocimage(display,
								Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta)),
								im->chan, 0, DBlack);
						if(tmp == nil) {
							fprint(2, "out of memory during zoom: %r\n");
							wexits("memory");
						}
						resample(im, tmp);
						im = tmp;
						delayfreeimage(tmp);
						esetcursor(nil);
						ul = screen->r.min;
						redraw(screen);
						flushimage(display, 1);
						break;
					}
				case Fit:	/* fit */
					{
						double delta;
						Rectangle r;

						delta = (double)Dx(screen->r)/(double)Dx(im->r);
						if((double)Dy(im->r)*delta > Dy(screen->r))
							delta = (double)Dy(screen->r)/(double)Dy(im->r);

						r = Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta));
						esetcursor(&reading);
						tmp = xallocimage(display, r, im->chan, 0, DBlack);
						if(tmp == nil) {
							fprint(2, "out of memory during fit: %r\n");
							wexits("memory");
						}
						resample(im, tmp);
						im = tmp;
						delayfreeimage(tmp);
						esetcursor(nil);
						ul = screen->r.min;
						redraw(screen);
						flushimage(display, 1);
						break;
					}
				case Rot:	/* rotate 90 */
					angle = (angle+90) % 360;
					showpage(page, &menu);
					break;
				case Upside: 	/* upside-down */
					angle = (angle+180) % 360;
					showpage(page, &menu);
					break;
				case Restore:	/* restore */
					showpage(page, &menu);
					break;
				case Reverse:	/* reverse */
					if(doc->fwdonly)
						break;
					reverse = !reverse;
					menu.lasthit = doc->npage-1-menu.lasthit;

					if(page == 0 || page == doc->npage-1) {
						page = doc->npage-1-page;
						showpage(page, &menu);
					}
					break;
				case Write: /* write */
					esetcursor(&reading);
					s = writebitmap();
					if(s)
						string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
							display->defaultfont, s);
					esetcursor(nil);
					flushimage(display, 1);
					break;
				case Del: /* delete */
					if(doc->rmpage && page < doc->npage) {
						if(doc->rmpage(doc, page) >= 0) {
							if(doc->npage < 0)
								wexits(0);
							if(page >= doc->npage)
								page = doc->npage-1;
							showpage(page, &menu);
						}
					}
					break;
				case Exit:	/* exit */
					return;
				case Empty1:
				case Empty2:
				case Empty3:
					break;

				};



			case Right:
				if(doc->npage == 0)
					break;

				oldpage = page;
				unlockdisplay(display);
				n = emenuhit(RMenu, &m, &menu);
				lockdisplay(display);
				if(n == -1)
					break;

				if(doc->fwdonly) {
					switch(n){
					case 0:	/* this page */
						break;
					case 1:	/* next page */
						showpage(++page, &menu);
						break;
					case 2:	/* exit */
						return;
					}
					break;
				}

				if(n == doc->npage)
					return;
				else
					page = reverse ? doc->npage-1-n : n;

				if(oldpage != page)
					showpage(page, &menu);
				nxt = 0;
				break;
			}
			break;

		case Eplumb:
			pm = e.v;
			if(pm->ndata <= 0){
				plumbfree(pm);
				break;
			}
			if(plumbquit(pm))
				exits(nil);
			if(showdata(pm)) {
				s = estrdup("/tmp/pageplumbXXXXXXX");
				fd = opentemp(s);
				write(fd, pm->data, pm->ndata);
				/* lose fd reference on purpose; the file is open ORCLOSE */
			} else if(pm->data[0] == '/') {
				s = estrdup(pm->data);
			} else {
				s = emalloc(strlen(pm->wdir)+1+pm->ndata+1);
				sprint(s, "%s/%s", pm->wdir, pm->data);
				cleanname(s);
			}
			if((i = doc->addpage(doc, s)) >= 0) {
				page = i;
				unhide();
				showpage(page, &menu);
			}
			free(s);
			plumbfree(pm);
			break;
		}
	}
}