shithub: riscv

Download patch

ref: d9657f82748a397eed451a4cfd0bbc6f19551488
parent: d02a0b77666d94bc97bbf0412b3cbefeff12c462
author: cinap_lenrek <cinap_lenrek@centraldogma>
date: Thu Sep 15 16:59:31 EDT 2011

replaceing page with npage

--- a/sys/man/1/page
+++ b/sys/man/1/page
@@ -80,27 +80,6 @@
 .B -r
 option reverses the order in which pages are displayed.
 .PP
-When viewing a document,
-.I page
-will try to guess the true bounding box, usually rounding up from
-the file's bounding box to
-8½×11 or A4 size.
-The 
-.B -b
-option causes it to respect the bounding box given in the file.
-As a more general problem,
-some PostScript files claim to conform to Adobe's
-Document Structuring Conventions but do not.
-The 
-.B -P
-option enables a slightly slower and slightly more
-skeptical version of the PostScript processing code.
-Unfortunately, there are PostScript documents
-that can only be viewed with the
-.B -P
-option, and there are PostScript documents that
-can only be viewed without it.
-.PP
 When viewing images with 
 .IR page ,
 it listens to the 
@@ -117,21 +96,6 @@
 from standard input but rather to listen
 for ones to load from the plumbing channel.
 .PP
-The 
-.B -v
-option turns on extra debugging output, and
-the
-.B -V
-option turns on even more debugging output.
-The 
-.B -a
-option causes 
-.I page
-to call
-.IR abort (2)
-rather than exit cleanly on errors,
-to facilitate debugging.
-.PP
 Pressing and holding button 1 permits panning about the page.
 .PP
 Button 2 raises a menu of operations on the current image or the
@@ -145,10 +109,9 @@
 Restores the image to the original. All modifications are lost.
 .TP
 .B Zoom
-Prompts the user to sweep a rectangle on the image which is 
-expanded proportionally to the rectangle.
+controls magnification.
 .TP
-.B Fit window
+.B Fit
 Resizes the image so that it fits in the current window.
 .TP
 .B Rotate 90
@@ -166,12 +129,6 @@
 .B Zerox
 Displays the current image in a new page window. 
 Useful for selecting important pages from large documents.
-.TP
-.B Reverse
-Reverses the order in which pages are displayed.
-.TP
-.B Write
-Writes the image to file.
 .PD
 .PP
 Button 3 raises a menu of the
@@ -182,23 +139,6 @@
 .B q
 or
 control-D exits the program.
-Typing a
-.B u
-toggles whether images are displayed upside-down.
-(This is useful in the common case of mistransmitted upside-down faxes).
-Typing a
-.B r
-reverses the order in which pages are displayed.
-Typing a 
-.B w
-will write the currently viewed page to a new file as a compressed
-.IR image (6)
-file.
-When possible, the filename is of the form
-.IR basename . pagenum . bit .
-Typing a 
-.B d
-removes an image from the working set.
 .PP
 To go to a specific page, one can type its number followed by enter.
 Typing left arrow, backspace, or minus displays the previous page.
@@ -238,34 +178,7 @@
 .IR troff (1)
 .SH SOURCE
 .B /sys/src/cmd/page
-.SH DIAGNOSTICS
-The mouse cursor changes to an arrow and ellipsis
-when
-.I page
-is reading or writing a file.
 .SH BUGS
-.I Page
-supports reading of only one document
-file at a time, and
-the user interface is clumsy when viewing very large documents.
-.PP
-When viewing multipage PostScript files that do not contain
-.RB `` %%Page ''
-comments, the button 3 menu only contains
-``this page'' and ``next page'':
-correctly determining 
-page boundaries in Postscript code is not computable
-in the general case.
-.PP
-If
-.I page
-has trouble viewing a Postscript file,
-it might not be exactly conforming: try viewing it with the 
-.B -P
-option.
-.PP
 The interface to the plumber is unsatisfactory.  In particular,
 document references cannot be sent 
 via plumbing messages.
-.PP
-There are too many keyboard commands.
--- a/sys/src/cmd/npage.c
+++ /dev/null
@@ -1,1246 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <cursor.h>
-#include <keyboard.h>
-#include <plumb.h>
-
-typedef struct Page Page;
-struct Page {
-	char	*label;
-
-	QLock;
-	void	*data;
-	int	(*open)(Page *);
-
-	char	*text;
-	Image	*image;
-	int	fd;
-	int	gen;
-
-	Page	*up;
-	Page	*next;
-	Page	*down;
-	Page	*tail;
-};
-
-int zoom = 1;
-int ppi = 100;
-int imode;
-int newwin;
-int rotate;
-int viewgen;
-int pagegen;
-Point resize, pos;
-Page *root, *current;
-QLock pagelock;
-int nullfd;
-
-char pagespool[] = "/tmp/pagespool.";
-
-enum {
-	NPROC = 4,
-	NAHEAD = 2,
-	NBUF = 8*1024,
-	NPATH = 1024,
-};
-
-char *pagemenugen(int i);
-
-char *menuitems[] = {
-	"orig size",
-	"rotate 90",
-	"upside down",
-	"",
-	"fit width",
-	"fit height",
-	"",
-	"zoom in",
-	"zoom out",
-	"",
-	"next",
-	"prev",
-	"zerox",
-	"",
-	"quit",
-	nil
-};
-
-Menu pagemenu = {
-	nil,
-	pagemenugen,
-	-1,
-};
-
-Menu menu = {
-	menuitems,
-	nil,
-	-1,
-};
-
-Cursor reading = {
-	{-1, -1},
-	{0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, 
-	 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, 
-	 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, 
-	 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
-	{0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, 
-	 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, 
-	 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, 
-	 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
-};
-
-void showpage(Page *);
-void drawpage(Page *);
-Point pagesize(Page *);
-
-Page*
-addpage(Page *up, char *label, int (*popen)(Page *), void *pdata, int fd)
-{
-	Page *p;
-
-	p = mallocz(sizeof(*p), 1);
-	p->label = strdup(label);
-	p->gen = pagegen;
-	p->text = nil;
-	p->image = nil;
-	p->data = pdata;
-	p->open = popen;
-	p->fd = fd;
-
-	p->down = nil;
-	p->tail = nil;
-	p->next = nil;
-
-	qlock(&pagelock);
-	if(p->up = up){
-		if(up->tail == nil)
-			up->down = up->tail = p;
-		else {
-			up->tail->next = p;
-			up->tail = p;
-		}
-	}
-	qunlock(&pagelock);
-
-	if(up && current == up)
-		showpage(p);
-	return p;
-}
-
-void
-resizewin(Point size)
-{
-	int wctl;
-
-	if((wctl = open("/dev/wctl", OWRITE)) < 0)
-		return;
-	/* add rio border */
-	size = addpt(size, Pt(Borderwidth*2, Borderwidth*2));
-	fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y);
-	close(wctl);
-}
-
-int
-createtmp(ulong id, char *pfx)
-{
-	char nam[64];
-
-	snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id ^ 0xcafebabe);
-	return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
-}
-
-int
-catchnote(void *, char *msg)
-{
-	if(strstr(msg, "sys: write on closed pipe"))
-		return 1;
-	if(strstr(msg, "hangup"))
-		return 1;
-	if(strstr(msg, "alarm"))
-		return 1;
-	return 0;
-}
-
-void
-pipeline(int fd, char *fmt, ...)
-{
-	char buf[128], *argv[4];
-	va_list arg;
-	int pfd[2];
-
-	if(pipe(pfd) < 0){
-	Err:
-		dup(nullfd, fd);
-		return;
-	}
-	switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
-	case -1:
-		close(pfd[0]);
-		close(pfd[1]);
-		goto Err;
-	case 0:
-		if(dup(fd, 0)<0)
-			exits("dup");
-		if(dup(pfd[1], 1)<0)
-			exits("dup");
-		close(fd);
-		close(pfd[1]);
-		close(pfd[0]);
-		va_start(arg, fmt);
-		vsnprint(buf, sizeof buf, fmt, arg);
-		va_end(arg);
-
-		argv[0] = "rc";
-		argv[1] = "-c";
-		argv[2] = buf;
-		argv[3] = nil;
-		exec("/bin/rc", argv);
-		sysfatal("exec: %r");
-	}
-	close(pfd[1]);
-	dup(pfd[0], fd);
-	close(pfd[0]);
-}
-
-int
-popenfile(Page*);
-
-int
-popenconv(Page *p)
-{
-	char nam[NPATH];
-	int fd;
-
-	if((fd = dup(p->fd, -1)) < 0){
-		close(p->fd);
-		p->fd = -1;
-		return -1;
-	}
-
-	seek(fd, 0, 0);
-	if(p->data)
-		pipeline(fd, "%s", (char*)p->data);
-
-	/*
-	 * dont keep the file descriptor arround if it can simply
-	 * be reopened.
-	 */
-	fd2path(p->fd, nam, sizeof(nam));
-	if(strncmp(nam, pagespool, strlen(pagespool))){
-		close(p->fd);
-		p->fd = -1;
-		p->data = strdup(nam);
-		p->open = popenfile;
-	}
-
-	return fd;
-}
-
-typedef struct Ghost Ghost;
-struct Ghost
-{
-	QLock;
-
-	int	pin;
-	int	pout;
-	int	pdat;
-};
-
-int
-popenpdf(Page *p)
-{
-	char buf[NBUF];
-	int n, pfd[2];
-	Ghost *gs;
-
-	if(pipe(pfd) < 0)
-		return -1;
-	switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
-	case -1:
-		close(pfd[0]);
-		close(pfd[1]);
-		return -1;
-	case 0:
-		close(pfd[0]);
-		gs = p->data;
-		qlock(gs);
-		fprint(gs->pin, "%s DoPDFPage\n"
-			"(/fd/3) (w) file "
-			"dup flushfile "
-			"dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
-			"flushfile\n", p->label);
-		while((n = read(gs->pdat, buf, sizeof buf)) > 0){
-			if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
-				break;
-			if(pfd[1] < 0)
-				continue;
-			if(write(pfd[1], buf, n) != n){
-				close(pfd[1]);
-				pfd[1]=-1;
-			}
-		}
-		qunlock(gs);
-		exits(nil);
-	}
-	close(pfd[1]);
-	return pfd[0];
-}
-
-int
-infernobithdr(char *buf, int n)
-{
-	if(n >= 11){
-		if(memcmp(buf, "compressed\n", 11) == 0)
-			return 1;
-		if(strtochan((char*)buf))
-			return 1;
-		if(memcmp(buf, "          ", 10) == 0 && 
-			'0' <= buf[10] && buf[10] <= '9' &&
-			buf[11] == ' ')
-			return 1;
-	}
-	return 0;
-}
-
-int
-popengs(Page *p)
-{
-	int n, i, pdf, ifd, ofd, pin[2], pout[2], pdat[2];
-	char buf[NBUF], nam[32], *argv[16];
-
-	pdf = 0;
-	ifd = p->fd;
-	p->fd = -1;
-	seek(ifd, 0, 0);
-	if(read(ifd, buf, 5) != 5)
-		goto Err0;
-	seek(ifd, 0, 0);
-	if(memcmp(buf, "%PDF-", 5) == 0)
-		pdf = 1;
-	p->text = strdup(p->label);
-	if(pipe(pin) < 0){
-	Err0:
-		close(ifd);
-		return -1;
-	}
-	if(pipe(pout) < 0){
-	Err1:
-		close(pin[0]);
-		close(pin[1]);
-		goto Err0;
-	}
-	if(pipe(pdat) < 0){
-	Err2:
-		close(pdat[0]);
-		close(pdat[1]);
-		goto Err1;
-	}
-
-	switch(rfork(RFREND|RFPROC|RFFDG|RFNOWAIT)){
-	case -1:
-		goto Err2;
-	case 0:
-		if(pdf){
-			if(dup(pin[1], 0)<0)
-				exits("dup");
-			if(dup(pout[1], 1)<0)
-				exits("dup");
-		} else {
-			if(dup(nullfd, 0)<0)
-				exits("dup");
-			if(dup(nullfd, 1)<0)
-				exits("dup");
-		}
-		if(dup(nullfd, 2)<0)
-			exits("dup");
-		if(dup(pdat[1], 3)<0)
-			exits("dup");
-		if(dup(ifd, 4)<0)
-			exits("dup");
-
-		close(pin[0]);
-		close(pin[1]);
-		close(pout[0]);
-		close(pout[1]);
-		close(pdat[0]);
-		close(pdat[1]);
-		close(ifd);
-
-		if(p->data)
-			pipeline(4, "%s", (char*)p->data);
-
-		argv[0] = "gs";
-		argv[1] = "-q";
-		argv[2] = "-sDEVICE=plan9";
-		argv[3] = "-sOutputFile=/fd/3";
-		argv[4] = "-dBATCH";
-		argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
-		argv[6] = "-dQUIET";
-		argv[7] = "-dTextAlphaBits=4";
-		argv[8] = "-dGraphicsAlphaBits=4";
-		snprint(buf, sizeof buf, "-r%d", ppi);
-		argv[9] = buf;
-		argv[10] = "-dDOINTERPOLATE";
-		argv[11] = pdf ? "-" : "/fd/4";
-		argv[12] = nil;
-		exec("/bin/gs", argv);
-		sysfatal("exec: %r");
-	}
-
-	close(pin[1]);
-	close(pout[1]);
-	close(pdat[1]);
-	close(ifd);
-
-	if(pdf){
-		Ghost *gs;
-		char *prolog =
-			"/PAGEOUT (/fd/1) (w) file def\n"
-			"/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
-			"\n"
-			"/Page null def\n"
-			"/Page# 0 def\n"
-			"/PDFSave null def\n"
-			"/DSCPageCount 0 def\n"
-			"/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
-			"\n"
-			"GS_PDF_ProcSet begin\n"
-			"pdfdict begin\n"
-			"(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
-			"\n"
-			"pdfpagecount PAGE==\n";
-
-		n = strlen(prolog);
-		if(write(pin[0], prolog, n) != n)
-			goto Out;
-		if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
-			goto Out;
-		buf[n] = 0;
-		n = atoi(buf);
-		if(n <= 0){
-			werrstr("no pages");
-			goto Out;
-		}
-		gs = mallocz(sizeof(*gs), 1);
-		gs->pin = pin[0];
-		gs->pout = pout[0];
-		gs->pdat = pdat[0];
-		for(i=1; i<=n; i++){
-			snprint(nam, sizeof nam, "%d", i);
-			addpage(p, nam, popenpdf, gs, -1);
-		}
-
-		/* keep ghostscript arround */
-		return -1;
-	} else {
-		i = 0;
-		ofd = -1;
-		while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
-			if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
-				snprint(nam, sizeof nam, "%d", i);
-				addpage(p, nam, popenconv, nil, ofd);
-				ofd = -1;
-			}
-			if(n <= 0)
-				break;
-			if(ofd < 0){
-				snprint(nam, sizeof nam, "%.4d", ++i);
-				if((ofd = createtmp((ulong)p, nam)) < 0)
-					ofd = dup(nullfd, -1);
-			}
-			if(write(ofd, buf, n) != n)
-				break;
-		}
-		if(ofd >= 0)
-			close(ofd);
-	}
-Out:
-	close(pin[0]);
-	close(pout[0]);
-	close(pdat[0]);
-	return -1;
-}
-
-int
-popenfile(Page *p)
-{
-	char buf[NBUF], *file;
-	int i, n, fd, tfd;
-	Dir *d;
-
-	fd = p->fd;
-	p->fd = -1;
-	file = p->data;
-	if(fd < 0){
-		if((fd = open(file, OREAD)) < 0){
-		Err0:
-			p->data = nil;
-			free(file);
-			return -1;
-		}
-	}
-	seek(fd, 0, 0);
-	if((d = dirfstat(fd)) == nil){
-	Err1:
-		close(fd);
-		goto Err0;
-	}
-	if(d->mode & DMDIR){
-		free(d);
-		d = nil;
-		if((n = dirreadall(fd, &d)) < 0)
-			goto Err1;
-		for(i = 0; i<n; i++)
-			addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
-		free(d);
-		p->text = strdup(p->label);
-		goto Err1;
-	}
-	free(d);
-
-	memset(buf, 0, 32+1);
-	if((n = read(fd, buf, 32)) <= 0)
-		goto Err1;
-
-	p->fd = fd;
-	p->data = nil;
-	p->open = popenconv;
-	if(memcmp(buf, "%PDF-", 5) == 0 || strstr(buf, "%!"))
-		p->open = popengs;
-	else if(memcmp(buf, "x T ", 4) == 0){
-		p->data = "lp -dstdout";
-		p->open = popengs;
-	}
-	else if(memcmp(buf, "\xF7\x02\x01\x83\x92\xC0\x1C;", 8) == 0){
-		p->data = "dvips -Pps -r0 -q1 -f1";
-		p->open = popengs;
-	}
-	else if(memcmp(buf, "\x1F\x8B", 2) == 0){
-		p->data = "gunzip";
-		p->open = popengs;
-	}
-	else if(memcmp(buf, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 8) == 0){
-		p->data = "doc2ps";
-		p->open = popengs;
-	}
-	else if(memcmp(buf, "GIF", 3) == 0)
-		p->data = "gif -t9";
-	else if(memcmp(buf, "\111\111\052\000", 4) == 0) 
-		p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
-	else if(memcmp(buf, "\115\115\000\052", 4) == 0)
-		p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
-	else if(memcmp(buf, "\377\330\377", 3) == 0)
-		p->data = "jpg -t9";
-	else if(memcmp(buf, "\211PNG\r\n\032\n", 3) == 0)
-		p->data = "png -t9";
-	else if(memcmp(buf, "\0PC Research, Inc", 17) == 0)
-		p->data = "aux/g3p9bit -g";
-	else if(memcmp(buf, "TYPE=ccitt-g31", 14) == 0)
-		p->data = "aux/g3p9bit -g";
-	else if(memcmp(buf, "II*", 3) == 0)
-		p->data = "aux/g3p9bit -g";
-	else if(memcmp(buf, "TYPE=", 5) == 0)
-		p->data = "fb/3to1 rgbv |fb/pcp -tplan9";
-	else if(buf[0] == 'P' && '0' <= buf[1] && buf[1] <= '9')
-		p->data = "ppm -t9";
-	else if(memcmp(buf, "BM", 2) == 0)
-		p->data = "bmp -t9";
-	else if(infernobithdr(buf, n))
-		p->data = nil;
-	else {
-		werrstr("unknown image format");
-		goto Err1;
-	}
-
-	if(seek(fd, 0, 0) < 0)
-		goto Noseek;
-	if((i = read(fd, buf+n, n)) < 0)
-		goto Err1;
-	if(i != n || memcmp(buf, buf+n, i)){
-		n += i;
-	Noseek:
-		if((tfd = createtmp((ulong)p, "file")) < 0)
-			goto Err1;
-		while(n > 0){
-			if(write(tfd, buf, n) != n)
-				goto Err2;
-			if((n = read(fd, buf, sizeof(buf))) < 0)
-				goto Err2;
-		}
-		if(dup(tfd, fd) < 0){
-		Err2:
-			close(tfd);
-			goto Err1;
-		}
-		close(tfd);
-	}
-	free(file);
-	return p->open(p);
-}
-
-Page*
-nextpage(Page *p)
-{
-	if(p){
-		if(p->down)
-			return p->down;
-		if(p->next)
-			return p->next;
-		if(p->up)
-			return p->up->next;
-	}
-	return nil;
-}
-
-Page*
-prevpage(Page *x)
-{
-	Page *p, *t;
-
-	if(x){
-		for(p = root->down; p; p = t)
-			if((t = nextpage(p)) == x)
-				return p;
-	}
-	return nil;
-}
-
-int
-openpage(Page *p)
-{
-	int fd;
-
-	fd = -1;
-	if(p->open == nil || (fd = p->open(p)) < 0)
-		p->open = nil;
-	else {
-		if(rotate)
-			pipeline(fd, "rotate -r %d", rotate);
-		if(resize.x)
-			pipeline(fd, "resize -x %d", resize.x);
-		else if(resize.y)
-			pipeline(fd, "resize -y %d", resize.y);
-	}
-	return fd;
-}
-
-void
-loadpage(Page *p)
-{
-	int fd;
-
-	if(p->open && p->image == nil && p->text == nil){
-		if((fd = openpage(p)) >= 0){
-			pagegen++;
-			p->image = readimage(display, fd, 1);
-			close(fd);
-		}
-		if(p->image == nil && p->text == nil)
-			p->text = smprint("%s: %r", p->label);
-	}
-	p->gen = pagegen;
-}
-
-void
-unloadpage(Page *p)
-{
-	if(p->open){
-		if(p->text)
-			free(p->text);
-		p->text = nil;
-		if(p->image){
-			lockdisplay(display);
-			freeimage(p->image);
-			unlockdisplay(display);
-		}
-		p->image = nil;
-	}
-}
-
-void
-unloadpages(int age)
-{
-	Page *p;
-
-	for(p = root->down; p; p = nextpage(p)){
-		if(age == 0)	/* synchronous flush */
-			qlock(p);
-		else if(!canqlock(p))
-			continue;
-		if((pagegen - p->gen) >= age)
-			unloadpage(p);
-		qunlock(p);
-	}
-}
-
-void
-loadpages(Page *p, int ahead, int oviewgen)
-{
-	int i;
-
-	ahead++;	/* load at least one */
-	unloadpages(ahead*2);
-	for(i = 0; i < ahead && p; p = nextpage(p), i++){
-		if(viewgen != oviewgen)
-			break;
-		if(canqlock(p)){
-			loadpage(p);
-			if(viewgen != oviewgen){
-				unloadpage(p);
-				qunlock(p);
-				break;
-			}
-			if(p == current){
-				Point size;
-
-				esetcursor(nil);
-				size = pagesize(p);
-				if(size.x && size.y && newwin){
-					newwin = 0;
-					resizewin(size);
-				}
-				lockdisplay(display);
-				drawpage(p);
-				unlockdisplay(display);
-			}
-			qunlock(p);
-		}
-	}
-}
-
-/*
- * A draw operation that touches only the area contained in bot but not in top.
- * mp and sp get aligned with bot.min.
- */
-static void
-gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
-	Image *src, Point sp, Image *mask, Point mp, int op)
-{
-	Rectangle r;
-	Point origin;
-	Point delta;
-
-	USED(op);
-
-	if(Dx(bot)*Dy(bot) == 0)
-		return;
-
-	/* no points in bot - top */
-	if(rectinrect(bot, top))
-		return;
-
-	/* bot - top ≡ bot */
-	if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
-		gendrawop(dst, bot, src, sp, mask, mp, op);
-		return;
-	}
-
-	origin = bot.min;
-	/* split bot into rectangles that don't intersect top */
-	/* left side */
-	if(bot.min.x < top.min.x){
-		r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
-		delta = subpt(r.min, origin);
-		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
-		bot.min.x = top.min.x;
-	}
-
-	/* right side */
-	if(bot.max.x > top.max.x){
-		r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
-		delta = subpt(r.min, origin);
-		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
-		bot.max.x = top.max.x;
-	}
-
-	/* top */
-	if(bot.min.y < top.min.y){
-		r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
-		delta = subpt(r.min, origin);
-		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
-		bot.min.y = top.min.y;
-	}
-
-	/* bottom */
-	if(bot.max.y > top.max.y){
-		r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
-		delta = subpt(r.min, origin);
-		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
-		bot.max.y = top.max.y;
-	}
-}
-
-void
-zoomdraw(Image *d, Rectangle r, Rectangle top, Image *s, Point sp, int f)
-{
-	int w, x, y;
-	Image *t;
-	Point a;
-
-	if(f <= 1){
-		gendrawdiff(d, r, top, s, sp, nil, ZP, S);
-		return;
-	}
-	a = ZP;
-	if(r.min.x < d->r.min.x){
-		sp.x += (d->r.min.x - r.min.x)/f;
-		a.x = (d->r.min.x - r.min.x)%f;
-		r.min.x = d->r.min.x;
-	}
-	if(r.min.y < d->r.min.y){
-		sp.y += (d->r.min.y - r.min.y)/f;
-		a.y = (d->r.min.y - r.min.y)%f;
-		r.min.y = d->r.min.y;
-	}
-	rectclip(&r, d->r);
-	w = s->r.max.x - sp.x;
-	if(w > Dx(r))
-		w = Dx(r);
-	t = allocimage(display, Rect(r.min.x, r.min.y, r.min.x+w, r.max.y), s->chan, 0, DNofill);
-	if(t == nil)
-		return;
-	for(y=r.min.y; y<r.max.y; y++){
-		draw(t, Rect(r.min.x, y, r.min.x+w, y+1), s, nil, sp);
-		if(++a.y == zoom){
-			a.y = 0;
-			sp.y++;
-		}
-	}
-	sp = r.min;
-	for(x=r.min.x; x<r.max.x; x++){
-		gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, t, sp, nil, ZP, S);
-		if(++a.x == f){
-			a.x = 0;
-			sp.x++;
-		}
-	}
-	freeimage(t);
-}
-
-Point
-pagesize(Page *p)
-{
-	return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
-}
-
-void
-drawpage(Page *p)
-{
-	Rectangle r;
-	Image *i;
-
-	if((i = p->image) == nil){
-		char *s;
-
-		if((s = p->text) == nil)
-			s = "...";
-		r.min = ZP;
-		r.max = stringsize(font, p->text);
-		r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2), divpt(r.max, 2)),
-			screen->r.min));
-		draw(screen, r, display->white, nil, ZP);
-		string(screen, r.min, display->black, ZP, font, s);
-	} else {
-		r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
-		zoomdraw(screen, r, ZR, i, i->r.min, zoom);
-	}
-	gendrawdiff(screen, screen->r, r, display->white, ZP, nil, ZP, S);
-	border(screen, r, -Borderwidth, display->black, ZP);
-	flushimage(display, 1);
-}
-
-void
-translate(Page *p, Point d)
-{
-	Rectangle r, or, nr;
-	Image *i;
-
-	i = p->image;
-	if((i==0) || (d.x==0 && d.y==0))
-		return;
-	r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
-	pos = addpt(pos, d);
-	nr = rectaddpt(r, d);
-	or = r;
-	rectclip(&or, screen->r);
-	draw(screen, rectaddpt(or, d), screen, nil, or.min);
-	zoomdraw(screen, nr, rectaddpt(or, d), i, i->r.min, zoom);
-	gendrawdiff(screen, screen->r, nr, display->white, ZP, nil, ZP, S);
-	border(screen, nr, -Borderwidth, display->black, ZP);
-	flushimage(display, 1);
-}
-
-Page*
-pageat(int i)
-{
-	Page *p;
-
-	for(p = root->down; i > 0 && p; p = nextpage(p))
-		i--;
-	return i ? nil : p;
-}
-
-int
-pageindex(Page *x)
-{
-	Page *p;
-	int i;
-
-	for(i = 0, p = root->down; p && p != x; p = nextpage(p))
-		i++;
-	return (p == x) ? i : -1;
-}
-
-char*
-pagemenugen(int i)
-{
-	Page *p;
-	if(p = pageat(i))
-		return p->label;
-	return nil;
-}
-
-void
-showpage(Page *p)
-{
-	static int nproc;
-	int oviewgen;
-
-	if(p == nil)
-		return;
-	esetcursor(&reading);
-	current = p;
-	oviewgen = viewgen;
-	if(++nproc > NPROC)
-		if(waitpid() > 0)
-			nproc--;
-	switch(rfork(RFPROC|RFMEM)){
-	case -1:
-		sysfatal("rfork: %r");
-	case 0:
-		loadpages(p, NAHEAD, oviewgen);
-		exits(nil);
-	}
-}
-
-void
-zerox(Page *p)
-{
-	char nam[64], *argv[4];
-	int fd;
-
-	if(p == nil)
-		return;
-	esetcursor(&reading);
-	qlock(p);
-	if((fd = openpage(p)) < 0)
-		goto Out;
-	if(rfork(RFREND|RFFDG|RFPROC|RFENVG|RFNOTEG|RFNOWAIT) == 0){
-		dup(fd, 0);
-		close(fd);
-
-		snprint(nam, sizeof nam, "/bin/%s", argv0);
-		argv[0] = argv0;
-		argv[1] = "-w";
-		argv[2] = nil;
-		exec(nam, argv);
-		sysfatal("exec: %r");
-	}
-	close(fd);
-Out:
-	qunlock(p);
-	esetcursor(nil);
-}
-
-void
-eresized(int new)
-{
-	Page *p;
-
-	lockdisplay(display);
-	if(new && getwindow(display, Refnone) == -1)
-		sysfatal("getwindow: %r");
-	if(p = current){
-		if(canqlock(p)){
-			drawpage(p);
-			qunlock(p);
-		}
-	}
-	unlockdisplay(display);
-}
-
-void killcohort(void)
-{
-	int i;
-	for(i=0;i!=3;i++){	/* It's a long way to the kitchen */
-		postnote(PNGROUP, getpid(), "kill");
-		sleep(1);
-	}
-}
-
-void drawerr(Display *, char *msg)
-{
-	sysfatal("draw: %s", msg);
-}
-
-char*
-shortname(char *s)
-{
-	char *x;
-	if(x = strrchr(s, '/'))
-		if(x[1] != 0)
-			return x+1;
-	return s;
-}
-
-void
-usage(void)
-{
-	fprint(2, "usage: %s [ -iRw ] [ -p ppi ] [ file ... ]\n", argv0);
-	exits("usage");
-}
-
-void
-main(int argc, char *argv[])
-{
-	enum { Eplumb = 4 };
-	Plumbmsg *pm;
-	Point o;
-	Mouse m;
-	Event e;
-	char *s;
-	int i;
-
-	ARGBEGIN {
-	case 'a':
-	case 'v':
-	case 'V':
-	case 'P':
-		break;
-	case 'R':
-		newwin = -1;
-		break;
-	case 'w':
-		newwin = 1;
-		break;
-	case 'i':
-		imode = 1;
-		break;
-	case 'p':
-		ppi = atoi(EARGF(usage()));
-		break;
-	default:
-		usage();
-	} ARGEND;
-
-	/*
-	 * so that we can stop all subprocesses with a note,
-	 * and to isolate rendezvous from other processes
-	 */
-	rfork(RFNOTEG|RFNAMEG|RFREND);
-	atexit(killcohort);
-	atnotify(catchnote, 1);
-	if(newwin > 0){
-		s = smprint("-pid %d", getpid());
-		if(newwindow(s) < 0)
-			sysfatal("newwindow: %r");
-		free(s);
-	}
-	initdraw(drawerr, nil, argv0);
-	display->locking = 1;
-	unlockdisplay(display);
-	einit(Ekeyboard|Emouse);
-	eplumb(Eplumb, "image");
-	nullfd = open("/dev/null", ORDWR);
-	current = root = addpage(nil, "root", nil, nil, -1);
-
-	if(*argv == nil && !imode)
-		addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
-	for(; *argv; argv++)
-		addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
-
-	for(;;){
-		i=event(&e);
-		switch(i){
-		case Emouse:
-			lockdisplay(display);
-			m = e.mouse;
-			if(m.buttons & 1){
-				if(current == nil || !canqlock(current))
-					goto Unlock;
-				for(;;) {
-					o = m.xy;
-					m = emouse();
-					if((m.buttons & 1) == 0)
-						break;
-					translate(current, subpt(m.xy, o));
-				}
-				qunlock(current);
-				goto Unlock;
-			}
-			if(m.buttons & 2){
-				i = emenuhit(2, &m, &menu);
-				if(i < 0 || i >= nelem(menuitems) || menuitems[i]==nil)
-					goto Unlock;
-				s = menuitems[i];
-				if(strcmp(s, "orig size")==0){
-					pos = ZP;
-					zoom = 1;
-					resize = ZP;
-					rotate = 0;
-				Unload:
-					viewgen++;
-					unlockdisplay(display);
-					esetcursor(&reading);
-					unloadpages(0);
-					showpage(current);
-					continue;
-				}
-				if(strncmp(s, "rotate ", 7)==0){
-					rotate += atoi(s+7);
-					rotate %= 360;
-					goto Unload;
-				}
-				if(strcmp(s, "upside down")==0){
-					rotate += 180;
-					goto Unload;
-				}
-				if(strcmp(s, "fit width")==0){
-					pos = ZP;
-					zoom = 1;
-					resize = subpt(screen->r.max, screen->r.min);
-					resize.y = 0;
-					goto Unload;
-				}
-				if(strcmp(s, "fit height")==0){
-					pos = ZP;
-					zoom = 1;
-					resize = subpt(screen->r.max, screen->r.min);
-					resize.x = 0;
-					goto Unload;
-				}
-				if(strncmp(s, "zoom", 4)==0){
-					if(current && canqlock(current)){
-						o = subpt(m.xy, screen->r.min);
-						if(strstr(s, "in")){
-							if(zoom < 0x40000000){
-								zoom *= 2;
-								pos =  addpt(mulpt(subpt(pos, o), 2), o);
-							}
-						}else{
-							if(zoom > 1){
-								zoom /= 2;
-								pos =  addpt(divpt(subpt(pos, o), 2), o);
-							}
-						}
-						drawpage(current);
-						qunlock(current);
-					}
-				}
-				unlockdisplay(display);
-				if(strcmp(s, "next")==0)
-					showpage(nextpage(current));
-				if(strcmp(s, "prev")==0)
-					showpage(prevpage(current));
-				if(strcmp(s, "zerox")==0)
-					zerox(current);
-				if(strcmp(s, "quit")==0)
-					exits(0);
-				continue;
-			}
-			if(m.buttons & 4){
-				if(root->down == nil)
-					goto Unlock;
-				pagemenu.lasthit = pageindex(current);
-				i = emenuhit(3, &m, &pagemenu);
-				unlockdisplay(display);
-				if(i != -1)
-					showpage(pageat(i));
-				continue;
-			}
-		Unlock:
-			unlockdisplay(display);
-			break;
-		case Ekeyboard:
-			switch(e.kbdc){
-			case 'q':
-			case Kdel:
-			case Keof:
-				exits(0);
-			case Kup:
-				if(current == nil || !canqlock(current))
-					break;
-				lockdisplay(display);
-				if(pos.y < 0){
-					translate(current, Pt(0, Dy(screen->r)/2));
-					unlockdisplay(display);
-					qunlock(current);
-					continue;
-				}
-				unlockdisplay(display);
-				qunlock(current);
-				if(prevpage(current))
-					pos.y = 0;
-			case Kleft:
-				showpage(prevpage(current));
-				break;
-			case Kdown:
-				if(current == nil || !canqlock(current))
-					break;
-				o = addpt(pos, pagesize(current));
-				lockdisplay(display);
-				if(o.y > Dy(screen->r)){
-					translate(current, Pt(0, -Dy(screen->r)/2));
-					unlockdisplay(display);
-					qunlock(current);
-					continue;
-				}
-				unlockdisplay(display);
-				qunlock(current);
-				if(nextpage(current))
-					pos.y = 0;
-			case ' ':
-			case Kright:
-				showpage(nextpage(current));
-				break;
-			}
-			break;
-		case Eplumb:
-			pm = e.v;
-			if(pm && pm->ndata > 0){
-				int fd;
-
-				fd = -1;
-				s = plumblookup(pm->attr, "action");
-				if(s && strcmp(s, "quit")==0)
-					exits(0);
-				if(s && strcmp(s, "showdata")==0){
-					static ulong plumbid;
-
-					if((fd = createtmp(plumbid++, "plumb")) < 0){
-						fprint(2, "plumb: createtmp: %r\n");
-						goto Plumbfree;
-					}
-					s = malloc(NPATH);
-					if(fd2path(fd, s, NPATH) < 0){
-						close(fd);
-						goto Plumbfree;
-					}
-					write(fd, pm->data, pm->ndata);
-				}else if(pm->data[0] == '/'){
-					s = strdup(pm->data);
-				}else{
-					s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
-					sprint(s, "%s/%s", pm->wdir, pm->data);
-					cleanname(s);
-				}
-				showpage(addpage(root, shortname(s), popenfile, s, fd));
-			}
-		Plumbfree:
-			plumbfree(pm);
-			break;
-		}
-	}
-}
--- /dev/null
+++ b/sys/src/cmd/page.c
@@ -1,0 +1,1282 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <plumb.h>
+
+typedef struct Page Page;
+struct Page {
+	char	*label;
+
+	QLock;
+	void	*data;
+	int	(*open)(Page *);
+
+	char	*text;
+	Image	*image;
+	int	fd;
+	int	gen;
+
+	Page	*up;
+	Page	*next;
+	Page	*down;
+	Page	*tail;
+};
+
+int zoom = 1;
+int ppi = 100;
+int imode;
+int newwin;
+int rotate;
+int viewgen;
+int pagegen;
+Point resize, pos;
+Page *root, *current;
+QLock pagelock;
+int nullfd;
+
+char pagespool[] = "/tmp/pagespool.";
+
+enum {
+	NPROC = 4,
+	NAHEAD = 2,
+	NBUF = 8*1024,
+	NPATH = 1024,
+};
+
+char *pagemenugen(int i);
+
+char *menuitems[] = {
+	"orig size",
+	"rotate 90",
+	"upside down",
+	"",
+	"fit width",
+	"fit height",
+	"",
+	"zoom in",
+	"zoom out",
+	"",
+	"next",
+	"prev",
+	"zerox",
+	"",
+	"quit",
+	nil
+};
+
+Menu pagemenu = {
+	nil,
+	pagemenugen,
+	-1,
+};
+
+Menu menu = {
+	menuitems,
+	nil,
+	-1,
+};
+
+Cursor reading = {
+	{-1, -1},
+	{0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, 
+	 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, 
+	 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, 
+	 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
+	{0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, 
+	 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, 
+	 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, 
+	 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
+};
+
+void showpage(Page *);
+void drawpage(Page *);
+Point pagesize(Page *);
+
+Page*
+addpage(Page *up, char *label, int (*popen)(Page *), void *pdata, int fd)
+{
+	Page *p;
+
+	p = mallocz(sizeof(*p), 1);
+	p->label = strdup(label);
+	p->gen = pagegen;
+	p->text = nil;
+	p->image = nil;
+	p->data = pdata;
+	p->open = popen;
+	p->fd = fd;
+
+	p->down = nil;
+	p->tail = nil;
+	p->next = nil;
+
+	qlock(&pagelock);
+	if(p->up = up){
+		if(up->tail == nil)
+			up->down = up->tail = p;
+		else {
+			up->tail->next = p;
+			up->tail = p;
+		}
+	}
+	qunlock(&pagelock);
+
+	if(up && current == up)
+		showpage(p);
+	return p;
+}
+
+void
+resizewin(Point size)
+{
+	int wctl;
+
+	if((wctl = open("/dev/wctl", OWRITE)) < 0)
+		return;
+	/* add rio border */
+	size = addpt(size, Pt(Borderwidth*2, Borderwidth*2));
+	fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y);
+	close(wctl);
+}
+
+int
+createtmp(ulong id, char *pfx)
+{
+	char nam[64];
+
+	snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id ^ 0xcafebabe);
+	return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
+}
+
+int
+catchnote(void *, char *msg)
+{
+	if(strstr(msg, "sys: write on closed pipe"))
+		return 1;
+	if(strstr(msg, "hangup"))
+		return 1;
+	if(strstr(msg, "alarm"))
+		return 1;
+	return 0;
+}
+
+void
+pipeline(int fd, char *fmt, ...)
+{
+	char buf[128], *argv[4];
+	va_list arg;
+	int pfd[2];
+
+	if(pipe(pfd) < 0){
+	Err:
+		dup(nullfd, fd);
+		return;
+	}
+	switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
+	case -1:
+		close(pfd[0]);
+		close(pfd[1]);
+		goto Err;
+	case 0:
+		if(dup(fd, 0)<0)
+			exits("dup");
+		if(dup(pfd[1], 1)<0)
+			exits("dup");
+		close(fd);
+		close(pfd[1]);
+		close(pfd[0]);
+		va_start(arg, fmt);
+		vsnprint(buf, sizeof buf, fmt, arg);
+		va_end(arg);
+
+		argv[0] = "rc";
+		argv[1] = "-c";
+		argv[2] = buf;
+		argv[3] = nil;
+		exec("/bin/rc", argv);
+		sysfatal("exec: %r");
+	}
+	close(pfd[1]);
+	dup(pfd[0], fd);
+	close(pfd[0]);
+}
+
+int
+popenfile(Page*);
+
+int
+popenconv(Page *p)
+{
+	char nam[NPATH];
+	int fd;
+
+	if((fd = dup(p->fd, -1)) < 0){
+		close(p->fd);
+		p->fd = -1;
+		return -1;
+	}
+
+	seek(fd, 0, 0);
+	if(p->data)
+		pipeline(fd, "%s", (char*)p->data);
+
+	/*
+	 * dont keep the file descriptor arround if it can simply
+	 * be reopened.
+	 */
+	fd2path(p->fd, nam, sizeof(nam));
+	if(strncmp(nam, pagespool, strlen(pagespool))){
+		close(p->fd);
+		p->fd = -1;
+		p->data = strdup(nam);
+		p->open = popenfile;
+	}
+
+	return fd;
+}
+
+typedef struct Ghost Ghost;
+struct Ghost
+{
+	QLock;
+
+	int	pin;
+	int	pout;
+	int	pdat;
+};
+
+int
+popenpdf(Page *p)
+{
+	char buf[NBUF];
+	int n, pfd[2];
+	Ghost *gs;
+
+	if(pipe(pfd) < 0)
+		return -1;
+	switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
+	case -1:
+		close(pfd[0]);
+		close(pfd[1]);
+		return -1;
+	case 0:
+		close(pfd[0]);
+		gs = p->data;
+		qlock(gs);
+		fprint(gs->pin, "%s DoPDFPage\n"
+			"(/fd/3) (w) file "
+			"dup flushfile "
+			"dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
+			"flushfile\n", p->label);
+		while((n = read(gs->pdat, buf, sizeof buf)) > 0){
+			if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
+				break;
+			if(pfd[1] < 0)
+				continue;
+			if(write(pfd[1], buf, n) != n){
+				close(pfd[1]);
+				pfd[1]=-1;
+			}
+		}
+		qunlock(gs);
+		exits(nil);
+	}
+	close(pfd[1]);
+	return pfd[0];
+}
+
+int
+infernobithdr(char *buf, int n)
+{
+	if(n >= 11){
+		if(memcmp(buf, "compressed\n", 11) == 0)
+			return 1;
+		if(strtochan((char*)buf))
+			return 1;
+		if(memcmp(buf, "          ", 10) == 0 && 
+			'0' <= buf[10] && buf[10] <= '9' &&
+			buf[11] == ' ')
+			return 1;
+	}
+	return 0;
+}
+
+int
+popengs(Page *p)
+{
+	int n, i, pdf, ifd, ofd, pin[2], pout[2], pdat[2];
+	char buf[NBUF], nam[32], *argv[16];
+
+	pdf = 0;
+	ifd = p->fd;
+	p->fd = -1;
+	seek(ifd, 0, 0);
+	if(read(ifd, buf, 5) != 5)
+		goto Err0;
+	seek(ifd, 0, 0);
+	if(memcmp(buf, "%PDF-", 5) == 0)
+		pdf = 1;
+	p->text = strdup(p->label);
+	if(pipe(pin) < 0){
+	Err0:
+		close(ifd);
+		return -1;
+	}
+	if(pipe(pout) < 0){
+	Err1:
+		close(pin[0]);
+		close(pin[1]);
+		goto Err0;
+	}
+	if(pipe(pdat) < 0){
+	Err2:
+		close(pdat[0]);
+		close(pdat[1]);
+		goto Err1;
+	}
+
+	switch(rfork(RFREND|RFPROC|RFFDG|RFNOWAIT)){
+	case -1:
+		goto Err2;
+	case 0:
+		if(pdf){
+			if(dup(pin[1], 0)<0)
+				exits("dup");
+			if(dup(pout[1], 1)<0)
+				exits("dup");
+		} else {
+			if(dup(nullfd, 0)<0)
+				exits("dup");
+			if(dup(nullfd, 1)<0)
+				exits("dup");
+		}
+		if(dup(nullfd, 2)<0)
+			exits("dup");
+		if(dup(pdat[1], 3)<0)
+			exits("dup");
+		if(dup(ifd, 4)<0)
+			exits("dup");
+
+		close(pin[0]);
+		close(pin[1]);
+		close(pout[0]);
+		close(pout[1]);
+		close(pdat[0]);
+		close(pdat[1]);
+		close(ifd);
+
+		if(p->data)
+			pipeline(4, "%s", (char*)p->data);
+
+		argv[0] = "gs";
+		argv[1] = "-q";
+		argv[2] = "-sDEVICE=plan9";
+		argv[3] = "-sOutputFile=/fd/3";
+		argv[4] = "-dBATCH";
+		argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
+		argv[6] = "-dQUIET";
+		argv[7] = "-dTextAlphaBits=4";
+		argv[8] = "-dGraphicsAlphaBits=4";
+		snprint(buf, sizeof buf, "-r%d", ppi);
+		argv[9] = buf;
+		argv[10] = "-dDOINTERPOLATE";
+		argv[11] = pdf ? "-" : "/fd/4";
+		argv[12] = nil;
+		exec("/bin/gs", argv);
+		sysfatal("exec: %r");
+	}
+
+	close(pin[1]);
+	close(pout[1]);
+	close(pdat[1]);
+	close(ifd);
+
+	if(pdf){
+		Ghost *gs;
+		char *prolog =
+			"/PAGEOUT (/fd/1) (w) file def\n"
+			"/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
+			"\n"
+			"/Page null def\n"
+			"/Page# 0 def\n"
+			"/PDFSave null def\n"
+			"/DSCPageCount 0 def\n"
+			"/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
+			"\n"
+			"GS_PDF_ProcSet begin\n"
+			"pdfdict begin\n"
+			"(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
+			"\n"
+			"pdfpagecount PAGE==\n";
+
+		n = strlen(prolog);
+		if(write(pin[0], prolog, n) != n)
+			goto Out;
+		if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
+			goto Out;
+		buf[n] = 0;
+		n = atoi(buf);
+		if(n <= 0){
+			werrstr("no pages");
+			goto Out;
+		}
+		gs = mallocz(sizeof(*gs), 1);
+		gs->pin = pin[0];
+		gs->pout = pout[0];
+		gs->pdat = pdat[0];
+		for(i=1; i<=n; i++){
+			snprint(nam, sizeof nam, "%d", i);
+			addpage(p, nam, popenpdf, gs, -1);
+		}
+
+		/* keep ghostscript arround */
+		return -1;
+	} else {
+		i = 0;
+		ofd = -1;
+		while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
+			if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
+				snprint(nam, sizeof nam, "%d", i);
+				addpage(p, nam, popenconv, nil, ofd);
+				ofd = -1;
+			}
+			if(n <= 0)
+				break;
+			if(ofd < 0){
+				snprint(nam, sizeof nam, "%.4d", ++i);
+				if((ofd = createtmp((ulong)p, nam)) < 0)
+					ofd = dup(nullfd, -1);
+			}
+			if(write(ofd, buf, n) != n)
+				break;
+		}
+		if(ofd >= 0)
+			close(ofd);
+	}
+Out:
+	close(pin[0]);
+	close(pout[0]);
+	close(pdat[0]);
+	return -1;
+}
+
+int
+popenfile(Page *p)
+{
+	char buf[NBUF], *file;
+	int i, n, fd, tfd;
+	Dir *d;
+
+	fd = p->fd;
+	p->fd = -1;
+	file = p->data;
+	if(fd < 0){
+		if((fd = open(file, OREAD)) < 0){
+		Err0:
+			p->data = nil;
+			free(file);
+			return -1;
+		}
+	}
+	seek(fd, 0, 0);
+	if((d = dirfstat(fd)) == nil){
+	Err1:
+		close(fd);
+		goto Err0;
+	}
+	if(d->mode & DMDIR){
+		free(d);
+		d = nil;
+		if((n = dirreadall(fd, &d)) < 0)
+			goto Err1;
+		for(i = 0; i<n; i++)
+			addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
+		free(d);
+		p->text = strdup(p->label);
+		goto Err1;
+	}
+	free(d);
+
+	memset(buf, 0, 32+1);
+	if((n = read(fd, buf, 32)) <= 0)
+		goto Err1;
+
+	p->fd = fd;
+	p->data = nil;
+	p->open = popenconv;
+	if(memcmp(buf, "%PDF-", 5) == 0 || strstr(buf, "%!"))
+		p->open = popengs;
+	else if(memcmp(buf, "x T ", 4) == 0){
+		p->data = "lp -dstdout";
+		p->open = popengs;
+	}
+	else if(memcmp(buf, "\xF7\x02\x01\x83\x92\xC0\x1C;", 8) == 0){
+		p->data = "dvips -Pps -r0 -q1 -f1";
+		p->open = popengs;
+	}
+	else if(memcmp(buf, "\x1F\x8B", 2) == 0){
+		p->data = "gunzip";
+		p->open = popengs;
+	}
+	else if(memcmp(buf, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 8) == 0){
+		p->data = "doc2ps";
+		p->open = popengs;
+	}
+	else if(memcmp(buf, "GIF", 3) == 0)
+		p->data = "gif -t9";
+	else if(memcmp(buf, "\111\111\052\000", 4) == 0) 
+		p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
+	else if(memcmp(buf, "\115\115\000\052", 4) == 0)
+		p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
+	else if(memcmp(buf, "\377\330\377", 3) == 0)
+		p->data = "jpg -t9";
+	else if(memcmp(buf, "\211PNG\r\n\032\n", 3) == 0)
+		p->data = "png -t9";
+	else if(memcmp(buf, "\0PC Research, Inc", 17) == 0)
+		p->data = "aux/g3p9bit -g";
+	else if(memcmp(buf, "TYPE=ccitt-g31", 14) == 0)
+		p->data = "aux/g3p9bit -g";
+	else if(memcmp(buf, "II*", 3) == 0)
+		p->data = "aux/g3p9bit -g";
+	else if(memcmp(buf, "TYPE=", 5) == 0)
+		p->data = "fb/3to1 rgbv |fb/pcp -tplan9";
+	else if(buf[0] == 'P' && '0' <= buf[1] && buf[1] <= '9')
+		p->data = "ppm -t9";
+	else if(memcmp(buf, "BM", 2) == 0)
+		p->data = "bmp -t9";
+	else if(infernobithdr(buf, n))
+		p->data = nil;
+	else {
+		werrstr("unknown image format");
+		goto Err1;
+	}
+
+	if(seek(fd, 0, 0) < 0)
+		goto Noseek;
+	if((i = read(fd, buf+n, n)) < 0)
+		goto Err1;
+	if(i != n || memcmp(buf, buf+n, i)){
+		n += i;
+	Noseek:
+		if((tfd = createtmp((ulong)p, "file")) < 0)
+			goto Err1;
+		while(n > 0){
+			if(write(tfd, buf, n) != n)
+				goto Err2;
+			if((n = read(fd, buf, sizeof(buf))) < 0)
+				goto Err2;
+		}
+		if(dup(tfd, fd) < 0){
+		Err2:
+			close(tfd);
+			goto Err1;
+		}
+		close(tfd);
+	}
+	free(file);
+	return p->open(p);
+}
+
+Page*
+nextpage(Page *p)
+{
+	if(p){
+		if(p->down)
+			return p->down;
+		if(p->next)
+			return p->next;
+		if(p->up)
+			return p->up->next;
+	}
+	return nil;
+}
+
+Page*
+prevpage(Page *x)
+{
+	Page *p, *t;
+
+	if(x){
+		for(p = root->down; p; p = t)
+			if((t = nextpage(p)) == x)
+				return p;
+	}
+	return nil;
+}
+
+int
+openpage(Page *p)
+{
+	int fd;
+
+	fd = -1;
+	if(p->open == nil || (fd = p->open(p)) < 0)
+		p->open = nil;
+	else {
+		if(rotate)
+			pipeline(fd, "rotate -r %d", rotate);
+		if(resize.x)
+			pipeline(fd, "resize -x %d", resize.x);
+		else if(resize.y)
+			pipeline(fd, "resize -y %d", resize.y);
+	}
+	return fd;
+}
+
+void
+loadpage(Page *p)
+{
+	int fd;
+
+	if(p->open && p->image == nil && p->text == nil){
+		if((fd = openpage(p)) >= 0){
+			pagegen++;
+			p->image = readimage(display, fd, 1);
+			close(fd);
+		}
+		if(p->image == nil && p->text == nil)
+			p->text = smprint("%s: %r", p->label);
+	}
+	p->gen = pagegen;
+}
+
+void
+unloadpage(Page *p)
+{
+	if(p->open){
+		if(p->text)
+			free(p->text);
+		p->text = nil;
+		if(p->image){
+			lockdisplay(display);
+			freeimage(p->image);
+			unlockdisplay(display);
+		}
+		p->image = nil;
+	}
+}
+
+void
+unloadpages(int age)
+{
+	Page *p;
+
+	for(p = root->down; p; p = nextpage(p)){
+		if(age == 0)	/* synchronous flush */
+			qlock(p);
+		else if(!canqlock(p))
+			continue;
+		if((pagegen - p->gen) >= age)
+			unloadpage(p);
+		qunlock(p);
+	}
+}
+
+void
+loadpages(Page *p, int ahead, int oviewgen)
+{
+	int i;
+
+	ahead++;	/* load at least one */
+	unloadpages(ahead*2);
+	for(i = 0; i < ahead && p; p = nextpage(p), i++){
+		if(viewgen != oviewgen)
+			break;
+		if(canqlock(p)){
+			loadpage(p);
+			if(viewgen != oviewgen){
+				unloadpage(p);
+				qunlock(p);
+				break;
+			}
+			if(p == current){
+				Point size;
+
+				esetcursor(nil);
+				size = pagesize(p);
+				if(size.x && size.y && newwin){
+					newwin = 0;
+					resizewin(size);
+				}
+				lockdisplay(display);
+				drawpage(p);
+				unlockdisplay(display);
+			}
+			qunlock(p);
+		}
+	}
+}
+
+/*
+ * A draw operation that touches only the area contained in bot but not in top.
+ * mp and sp get aligned with bot.min.
+ */
+static void
+gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
+	Image *src, Point sp, Image *mask, Point mp, int op)
+{
+	Rectangle r;
+	Point origin;
+	Point delta;
+
+	USED(op);
+
+	if(Dx(bot)*Dy(bot) == 0)
+		return;
+
+	/* no points in bot - top */
+	if(rectinrect(bot, top))
+		return;
+
+	/* bot - top ≡ bot */
+	if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
+		gendrawop(dst, bot, src, sp, mask, mp, op);
+		return;
+	}
+
+	origin = bot.min;
+	/* split bot into rectangles that don't intersect top */
+	/* left side */
+	if(bot.min.x < top.min.x){
+		r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
+		delta = subpt(r.min, origin);
+		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+		bot.min.x = top.min.x;
+	}
+
+	/* right side */
+	if(bot.max.x > top.max.x){
+		r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
+		delta = subpt(r.min, origin);
+		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+		bot.max.x = top.max.x;
+	}
+
+	/* top */
+	if(bot.min.y < top.min.y){
+		r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
+		delta = subpt(r.min, origin);
+		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+		bot.min.y = top.min.y;
+	}
+
+	/* bottom */
+	if(bot.max.y > top.max.y){
+		r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
+		delta = subpt(r.min, origin);
+		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+		bot.max.y = top.max.y;
+	}
+}
+
+void
+zoomdraw(Image *d, Rectangle r, Rectangle top, Image *s, Point sp, int f)
+{
+	int w, x, y;
+	Image *t;
+	Point a;
+
+	if(f <= 1){
+		gendrawdiff(d, r, top, s, sp, nil, ZP, S);
+		return;
+	}
+	a = ZP;
+	if(r.min.x < d->r.min.x){
+		sp.x += (d->r.min.x - r.min.x)/f;
+		a.x = (d->r.min.x - r.min.x)%f;
+		r.min.x = d->r.min.x;
+	}
+	if(r.min.y < d->r.min.y){
+		sp.y += (d->r.min.y - r.min.y)/f;
+		a.y = (d->r.min.y - r.min.y)%f;
+		r.min.y = d->r.min.y;
+	}
+	rectclip(&r, d->r);
+	w = s->r.max.x - sp.x;
+	if(w > Dx(r))
+		w = Dx(r);
+	t = allocimage(display, Rect(r.min.x, r.min.y, r.min.x+w, r.max.y), s->chan, 0, DNofill);
+	if(t == nil)
+		return;
+	for(y=r.min.y; y<r.max.y; y++){
+		draw(t, Rect(r.min.x, y, r.min.x+w, y+1), s, nil, sp);
+		if(++a.y == zoom){
+			a.y = 0;
+			sp.y++;
+		}
+	}
+	sp = r.min;
+	for(x=r.min.x; x<r.max.x; x++){
+		gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, t, sp, nil, ZP, S);
+		if(++a.x == f){
+			a.x = 0;
+			sp.x++;
+		}
+	}
+	freeimage(t);
+}
+
+Point
+pagesize(Page *p)
+{
+	return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
+}
+
+void
+drawpage(Page *p)
+{
+	Rectangle r;
+	Image *i;
+
+	if((i = p->image) == nil){
+		char *s;
+
+		if((s = p->text) == nil)
+			s = "...";
+		r.min = ZP;
+		r.max = stringsize(font, p->text);
+		r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2), divpt(r.max, 2)),
+			screen->r.min));
+		draw(screen, r, display->white, nil, ZP);
+		string(screen, r.min, display->black, ZP, font, s);
+	} else {
+		r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
+		zoomdraw(screen, r, ZR, i, i->r.min, zoom);
+	}
+	gendrawdiff(screen, screen->r, r, display->white, ZP, nil, ZP, S);
+	border(screen, r, -Borderwidth, display->black, ZP);
+	flushimage(display, 1);
+}
+
+void
+translate(Page *p, Point d)
+{
+	Rectangle r, or, nr;
+	Image *i;
+
+	i = p->image;
+	if((i==0) || (d.x==0 && d.y==0))
+		return;
+	r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
+	pos = addpt(pos, d);
+	nr = rectaddpt(r, d);
+	or = r;
+	rectclip(&or, screen->r);
+	draw(screen, rectaddpt(or, d), screen, nil, or.min);
+	zoomdraw(screen, nr, rectaddpt(or, d), i, i->r.min, zoom);
+	gendrawdiff(screen, screen->r, nr, display->white, ZP, nil, ZP, S);
+	border(screen, nr, -Borderwidth, display->black, ZP);
+	flushimage(display, 1);
+}
+
+Page*
+findpage(char *name)
+{
+	Page *p;
+	int n;
+
+	n = strlen(name);
+	/* look in current document first */
+	if(current && current->up){
+		for(p = current->up->down; p; p = p->next)
+			if(cistrncmp(p->label, name, n) == 0)
+				return p;
+	}
+	/* look everywhere */
+	for(p = root->down; p; p = nextpage(p))
+		if(cistrncmp(p->label, name, n) == 0)
+			return p;
+	return nil;
+}
+
+Page*
+pageat(int i)
+{
+	Page *p;
+
+	for(p = root->down; i > 0 && p; p = nextpage(p))
+		i--;
+	return i ? nil : p;
+}
+
+int
+pageindex(Page *x)
+{
+	Page *p;
+	int i;
+
+	for(i = 0, p = root->down; p && p != x; p = nextpage(p))
+		i++;
+	return (p == x) ? i : -1;
+}
+
+char*
+pagemenugen(int i)
+{
+	Page *p;
+	if(p = pageat(i))
+		return p->label;
+	return nil;
+}
+
+void
+showpage(Page *p)
+{
+	static int nproc;
+	int oviewgen;
+
+	if(p == nil)
+		return;
+	esetcursor(&reading);
+	current = p;
+	oviewgen = viewgen;
+	if(++nproc > NPROC)
+		if(waitpid() > 0)
+			nproc--;
+	switch(rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		loadpages(p, NAHEAD, oviewgen);
+		exits(nil);
+	}
+}
+
+void
+zerox(Page *p)
+{
+	char nam[64], *argv[4];
+	int fd;
+
+	if(p == nil)
+		return;
+	esetcursor(&reading);
+	qlock(p);
+	if((fd = openpage(p)) < 0)
+		goto Out;
+	if(rfork(RFREND|RFFDG|RFPROC|RFENVG|RFNOTEG|RFNOWAIT) == 0){
+		dup(fd, 0);
+		close(fd);
+
+		snprint(nam, sizeof nam, "/bin/%s", argv0);
+		argv[0] = argv0;
+		argv[1] = "-w";
+		argv[2] = nil;
+		exec(nam, argv);
+		sysfatal("exec: %r");
+	}
+	close(fd);
+Out:
+	qunlock(p);
+	esetcursor(nil);
+}
+
+void
+eresized(int new)
+{
+	Page *p;
+
+	lockdisplay(display);
+	if(new && getwindow(display, Refnone) == -1)
+		sysfatal("getwindow: %r");
+	if(p = current){
+		if(canqlock(p)){
+			drawpage(p);
+			qunlock(p);
+		}
+	}
+	unlockdisplay(display);
+}
+
+void killcohort(void)
+{
+	int i;
+	for(i=0;i!=3;i++){	/* It's a long way to the kitchen */
+		postnote(PNGROUP, getpid(), "kill");
+		sleep(1);
+	}
+}
+
+void drawerr(Display *, char *msg)
+{
+	sysfatal("draw: %s", msg);
+}
+
+char*
+shortname(char *s)
+{
+	char *x;
+	if(x = strrchr(s, '/'))
+		if(x[1] != 0)
+			return x+1;
+	return s;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [ -iRw ] [ -p ppi ] [ file ... ]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	enum { Eplumb = 4 };
+	char jump[32];
+	Plumbmsg *pm;
+	Point o;
+	Mouse m;
+	Event e;
+	char *s;
+	int i;
+
+	ARGBEGIN {
+	case 'a':
+	case 'v':
+	case 'V':
+	case 'P':
+		break;
+	case 'R':
+		newwin = -1;
+		break;
+	case 'w':
+		newwin = 1;
+		break;
+	case 'i':
+		imode = 1;
+		break;
+	case 'p':
+		ppi = atoi(EARGF(usage()));
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	/*
+	 * so that we can stop all subprocesses with a note,
+	 * and to isolate rendezvous from other processes
+	 */
+	rfork(RFNOTEG|RFNAMEG|RFREND);
+	atexit(killcohort);
+	atnotify(catchnote, 1);
+	if(newwin > 0){
+		s = smprint("-pid %d", getpid());
+		if(newwindow(s) < 0)
+			sysfatal("newwindow: %r");
+		free(s);
+	}
+	initdraw(drawerr, nil, argv0);
+	display->locking = 1;
+	unlockdisplay(display);
+	einit(Ekeyboard|Emouse);
+	eplumb(Eplumb, "image");
+	nullfd = open("/dev/null", ORDWR);
+	current = root = addpage(nil, "root", nil, nil, -1);
+
+	if(*argv == nil && !imode)
+		addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
+	for(; *argv; argv++)
+		addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
+
+	jump[0] = 0;
+	for(;;){
+		i=event(&e);
+		switch(i){
+		case Emouse:
+			lockdisplay(display);
+			m = e.mouse;
+			if(m.buttons & 1){
+				if(current == nil || !canqlock(current))
+					goto Unlock;
+				for(;;) {
+					o = m.xy;
+					m = emouse();
+					if((m.buttons & 1) == 0)
+						break;
+					translate(current, subpt(m.xy, o));
+				}
+				qunlock(current);
+				goto Unlock;
+			}
+			if(m.buttons & 2){
+				i = emenuhit(2, &m, &menu);
+				if(i < 0 || i >= nelem(menuitems) || menuitems[i]==nil)
+					goto Unlock;
+				s = menuitems[i];
+				if(strcmp(s, "orig size")==0){
+					pos = ZP;
+					zoom = 1;
+					resize = ZP;
+					rotate = 0;
+				Unload:
+					viewgen++;
+					unlockdisplay(display);
+					esetcursor(&reading);
+					unloadpages(0);
+					showpage(current);
+					continue;
+				}
+				if(strncmp(s, "rotate ", 7)==0){
+					rotate += atoi(s+7);
+					rotate %= 360;
+					goto Unload;
+				}
+				if(strcmp(s, "upside down")==0){
+					rotate += 180;
+					goto Unload;
+				}
+				if(strcmp(s, "fit width")==0){
+					pos = ZP;
+					zoom = 1;
+					resize = subpt(screen->r.max, screen->r.min);
+					resize.y = 0;
+					goto Unload;
+				}
+				if(strcmp(s, "fit height")==0){
+					pos = ZP;
+					zoom = 1;
+					resize = subpt(screen->r.max, screen->r.min);
+					resize.x = 0;
+					goto Unload;
+				}
+				if(strncmp(s, "zoom", 4)==0){
+					if(current && canqlock(current)){
+						o = subpt(m.xy, screen->r.min);
+						if(strstr(s, "in")){
+							if(zoom < 0x40000000){
+								zoom *= 2;
+								pos =  addpt(mulpt(subpt(pos, o), 2), o);
+							}
+						}else{
+							if(zoom > 1){
+								zoom /= 2;
+								pos =  addpt(divpt(subpt(pos, o), 2), o);
+							}
+						}
+						drawpage(current);
+						qunlock(current);
+					}
+				}
+				unlockdisplay(display);
+				if(strcmp(s, "next")==0)
+					showpage(nextpage(current));
+				if(strcmp(s, "prev")==0)
+					showpage(prevpage(current));
+				if(strcmp(s, "zerox")==0)
+					zerox(current);
+				if(strcmp(s, "quit")==0)
+					exits(0);
+				continue;
+			}
+			if(m.buttons & 4){
+				if(root->down == nil)
+					goto Unlock;
+				pagemenu.lasthit = pageindex(current);
+				i = emenuhit(3, &m, &pagemenu);
+				unlockdisplay(display);
+				if(i != -1)
+					showpage(pageat(i));
+				continue;
+			}
+		Unlock:
+			unlockdisplay(display);
+			break;
+		case Ekeyboard:
+			switch(e.kbdc){
+			case 'q':
+			case Kdel:
+			case Keof:
+				exits(0);
+			case Kup:
+				if(current == nil || !canqlock(current))
+					break;
+				lockdisplay(display);
+				if(pos.y < 0){
+					translate(current, Pt(0, Dy(screen->r)/2));
+					unlockdisplay(display);
+					qunlock(current);
+					continue;
+				}
+				unlockdisplay(display);
+				qunlock(current);
+				if(prevpage(current))
+					pos.y = 0;
+			case '-':
+			case Kbs:
+			case Kleft:
+				showpage(prevpage(current));
+				break;
+			case Kdown:
+				if(current == nil || !canqlock(current))
+					break;
+				o = addpt(pos, pagesize(current));
+				lockdisplay(display);
+				if(o.y > Dy(screen->r)){
+					translate(current, Pt(0, -Dy(screen->r)/2));
+					unlockdisplay(display);
+					qunlock(current);
+					continue;
+				}
+				unlockdisplay(display);
+				qunlock(current);
+				if(nextpage(current))
+					pos.y = 0;
+			case '\n':
+				if(jump[0]){
+					showpage(findpage(jump));
+					jump[0] = 0;
+					break;
+				}
+			case ' ':
+			case Kright:
+				showpage(nextpage(current));
+				break;
+			default:
+				i = strlen(jump);
+				if(i+1 < sizeof(jump)){
+					jump[i] = e.kbdc;
+					jump[i+1] = 0;
+				}
+			}
+			break;
+		case Eplumb:
+			pm = e.v;
+			if(pm && pm->ndata > 0){
+				int fd;
+
+				fd = -1;
+				s = plumblookup(pm->attr, "action");
+				if(s && strcmp(s, "quit")==0)
+					exits(0);
+				if(s && strcmp(s, "showdata")==0){
+					static ulong plumbid;
+
+					if((fd = createtmp(plumbid++, "plumb")) < 0){
+						fprint(2, "plumb: createtmp: %r\n");
+						goto Plumbfree;
+					}
+					s = malloc(NPATH);
+					if(fd2path(fd, s, NPATH) < 0){
+						close(fd);
+						goto Plumbfree;
+					}
+					write(fd, pm->data, pm->ndata);
+				}else if(pm->data[0] == '/'){
+					s = strdup(pm->data);
+				}else{
+					s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
+					sprint(s, "%s/%s", pm->wdir, pm->data);
+					cleanname(s);
+				}
+				showpage(addpage(root, shortname(s), popenfile, s, fd));
+			}
+		Plumbfree:
+			plumbfree(pm);
+			break;
+		}
+	}
+}
--- a/sys/src/cmd/page/cache.c
+++ /dev/null
@@ -1,187 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <cursor.h>
-#include <event.h>
-#include <bio.h>
-#include <plumb.h>
-#include <ctype.h>
-#include <keyboard.h>
-#include "page.h"
-
-typedef struct Cached Cached;
-struct Cached
-{
-	Document *doc;
-	int page;
-	int angle;
-	Image *im;
-};
-
-static Cached cache[5];
-
-static Image*
-questionmark(void)
-{
-	static Image *im;
-
-	if(im)
-		return im;	
-	im = xallocimage(display, Rect(0,0,50,50), GREY1, 1, DBlack);
-	if(im == nil)
-		return nil;
-	string(im, ZP, display->white, ZP, display->defaultfont, "?");
-	return im;
-}
-
-void
-cacheflush(void)
-{
-	int i;
-	Cached *c;
-
-	for(i=0; i<nelem(cache); i++){
-		c = &cache[i];
-		if(c->im)
-			freeimage(c->im);
-		c->im = nil;
-		c->doc = nil;
-	}
-}
-
-static Image*
-_cachedpage(Document *doc, int angle, int page, char *ra)
-{
-	int i;
-	Cached *c, old;
-	Image *im, *tmp;
-	static int lastpage = -1;
-
-	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;
-}
-
-Image*
-cachedpage(Document *doc, int angle, int page)
-{
-	static int lastpage = -1;
-	static int rabusy;
-	Image *im;
-	int ra;
-	
-	if(doc->npage < 1)
-		return display->white;
-
-	im = _cachedpage(doc, angle, page, "");
-	if(im == nil)
-		return nil;
-
-	/* readahead */
-	ra = -1;
-	if(!rabusy){
-		if(page == lastpage+1)
-			ra = page+1;
-		else if(page == lastpage-1)
-			ra = page-1;
-	}
-	lastpage = page;
-	if(ra >= 0){
-		rabusy = 1;
-		switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
-		case -1:
-			rabusy = 0;
-			break;
-		case 0:
-			lockdisplay(display);
-			_cachedpage(doc, angle, ra, "-ra");
-			rabusy = 0;
-			unlockdisplay(display);
-			_exits(nil);
-		default:
-			break;
-		}
-	}
-	return im;
-}
--- a/sys/src/cmd/page/filter.c
+++ /dev/null
@@ -1,107 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <bio.h>
-#include "page.h"
-
-Document*
-initfilt(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf, char *type, char *cmd, int docopy)
-{
-	int ofd;
-	int p[2];
-	char xbuf[8192];
-	int n;
-
-	if(argc > 1) {
-		fprint(2, "can only view one %s file at a time\n", type);
-		return nil;
-	}
-
-	fprint(2, "converting from %s to postscript...\n", type);
-
-	if(docopy){
-		if(pipe(p) < 0){
-			fprint(2, "pipe fails: %r\n");
-			exits("Epipe");
-		}
-	}else{
-		p[0] = open("/dev/null", ORDWR);
-		p[1] = open("/dev/null", ORDWR);
-	}
-
-	ofd = opentemp("/tmp/pagecvtXXXXXXXXX");
-	switch(fork()){
-	case -1:
-		fprint(2, "fork fails: %r\n");
-		exits("Efork");
-	default:
-		close(p[1]);
-		if(docopy){
-			write(p[0], buf, nbuf);
-			if(b)
-				while((n = Bread(b, xbuf, sizeof xbuf)) > 0)
-					write(p[0], xbuf, n);
-			else
-				while((n = read(stdinfd, xbuf, sizeof xbuf)) > 0)
-					write(p[0], xbuf, n);
-		}
-		close(p[0]);
-		waitpid();
-		break;
-	case 0:
-		close(p[0]);
-		dup(p[1], 0);
-		dup(ofd, 1);
-		/* stderr shines through */
-		execl("/bin/rc", "rc", "-c", cmd, nil);
-		break;
-	}
-
-	if(b)
-		Bterm(b);
-	seek(ofd, 0, 0);
-	b = emalloc(sizeof(Biobuf));
-	Binit(b, ofd, OREAD);
-
-	return initps(b, argc, argv, nil, 0);
-}
-
-Document*
-initdvi(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
-{
-	int fd;
-	char *name;
-	char cmd[256];
-	char fdbuf[20];
-
-	/*
-	 * Stupid DVIPS won't take standard input.
-	 */
-	if(b == nil){	/* standard input; spool to disk (ouch) */
-		fd = spooltodisk(buf, nbuf, &name);
-		sprint(fdbuf, "/fd/%d", fd);
-		b = Bopen(fdbuf, OREAD);
-		if(b == nil){
-			fprint(2, "cannot open disk spool file\n");
-			wexits("Bopen temp");
-		}
-		argv = &name;
-		argc = 1;
-	}
-
-	snprint(cmd, sizeof cmd, "dvips -Pps -r0 -q1 -f1 '%s'", argv[0]);
-	return initfilt(b, argc, argv, buf, nbuf, "dvi", cmd, 0);
-}
-
-Document*
-inittroff(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
-{
-	return initfilt(b, argc, argv, buf, nbuf, "troff", "lp -dstdout", 1);
-}
-
-Document*
-initmsdoc(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
-{
-	return initfilt(b, argc, argv, buf, nbuf, "microsoft office", "doc2ps", 1);
-}
--- a/sys/src/cmd/page/gfx.c
+++ /dev/null
@@ -1,332 +1,0 @@
-/*
- * graphics file reading for page
- */
-
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <bio.h>
-#include "page.h"
-
-typedef struct Convert	Convert;
-typedef struct GfxInfo	GfxInfo;
-typedef struct Graphic	Graphic;
-
-struct Convert {
-	char *name;
-	char *cmd;
-	char *truecmd;	/* cmd for true color */
-};
-
-struct GfxInfo {
-	Graphic *g;
-};
-
-struct Graphic {
-	int type;
-	char *name;
-	uchar *buf;	/* if stdin */
-	int nbuf;
-};
-
-enum {
-	Ipic,
-	Itiff,
-	Ijpeg,
-	Igif,
-	Iinferno,
-	Ifax,
-	Icvt2pic,
-	Iplan9bm,
-	Iccittg4,
-	Ippm,
-	Ipng,
-	Iyuv,
-	Ibmp,
-};
-
-/*
- * N.B. These commands need to read stdin if %a is replaced
- * with an empty string.
- */
-Convert cvt[] = {
-[Ipic]		{ "plan9",	"fb/3to1 rgbv %a |fb/pcp -tplan9" },
-[Itiff]		{ "tiff",	"fb/tiff2pic %a | fb/3to1 rgbv | fb/pcp -tplan9" },
-[Iplan9bm]	{ "plan9bm",	nil },
-[Ijpeg]		{ "jpeg",	"jpg -9 %a", "jpg -t9 %a" },
-[Igif]		{ "gif",	"gif -9 %a", "gif -t9 %a" },
-[Iinferno]	{ "inferno",	nil },
-[Ifax]		{ "fax",	"aux/g3p9bit -g %a" },
-[Icvt2pic]	{ "unknown",	"fb/cvt2pic %a |fb/3to1 rgbv" },
-[Ippm]		{ "ppm",	"ppm -9 %a", "ppm -t9 %a" },
-/* ``temporary'' hack for hobby */
-[Iccittg4]	{ "ccitt-g4",	"cat %a|rx nslocum /usr/lib/ocr/bin/bcp -M|fb/pcp -tcompressed -l0" },
-[Ipng]		{ "png",	"png -9 %a", "png -t9 %a" },
-[Iyuv]		{ "yuv",	"yuv -9 %a", "yuv -t9 %a"  },
-[Ibmp]		{ "bmp",	"bmp -9 %a", "bmp -t9 %a"  },
-};
-
-static Image*	convert(Graphic*);
-static Image*	gfxdrawpage(Document *d, int page);
-static char*	gfxpagename(Document*, int);
-static int	spawnrc(char*, uchar*, int);
-static void	waitrc(void);
-static int	spawnpost(int);
-static int	addpage(Document*, char*);
-static int	rmpage(Document*, int);
-static int	genaddpage(Document*, char*, uchar*, int);
-
-static char*
-gfxpagename(Document *doc, int page)
-{
-	GfxInfo *gfx = doc->extra;
-	return gfx->g[page].name;
-}
-
-static Image*
-gfxdrawpage(Document *doc, int page)
-{
-	GfxInfo *gfx = doc->extra;
-	return convert(gfx->g+page);
-}
-
-Document*
-initgfx(Biobuf*, int argc, char **argv, uchar *buf, int nbuf)
-{
-	GfxInfo *gfx;
-	Document *doc;
-	int i;
-
-	doc = emalloc(sizeof(*doc));
-	gfx = emalloc(sizeof(*gfx));
-	gfx->g = nil;
-	
-	doc->npage = 0;
-	doc->drawpage = gfxdrawpage;
-	doc->pagename = gfxpagename;
-	doc->addpage = addpage;
-	doc->rmpage = rmpage;
-	doc->extra = gfx;
-	doc->fwdonly = 0;
-
-	fprint(2, "reading through graphics...\n");
-	if(argc==0 && buf)
-		genaddpage(doc, nil, buf, nbuf);
-	else{
-		for(i=0; i<argc; i++)
-			if(addpage(doc, argv[i]) < 0)
-				fprint(2, "warning: not including %s: %r\n", argv[i]);
-	}
-
-	return doc;
-}
-
-static int
-genaddpage(Document *doc, char *name, uchar *buf, int nbuf)
-{
-	Graphic *g;
-	GfxInfo *gfx;
-	Biobuf *b;
-	uchar xbuf[32];
-	int i, l;
-
-	l = 0;
-	gfx = doc->extra;
-
-	assert((name == nil) ^ (buf == nil));
-	assert(name != nil || doc->npage == 0);
-
-	for(i=0; i<doc->npage; i++)
-		if(strcmp(gfx->g[i].name, name) == 0)
-			return i;
-
-	if(name){
-		l = strlen(name);
-		if((b = Bopen(name, OREAD)) == nil) {
-			werrstr("Bopen: %r");
-			return -1;
-		}
-
-		if(Bread(b, xbuf, sizeof xbuf) != sizeof xbuf) {
-			werrstr("short read: %r");
-			return -1;
-		}
-		Bterm(b);
-		buf = xbuf;
-		nbuf = sizeof xbuf;
-	}
-
-
-	gfx->g = erealloc(gfx->g, (doc->npage+1)*(sizeof(*gfx->g)));
-	g = &gfx->g[doc->npage];
-
-	memset(g, 0, sizeof *g);
-	if(memcmp(buf, "GIF", 3) == 0)
-		g->type = Igif;
-	else if(memcmp(buf, "\111\111\052\000", 4) == 0) 
-		g->type = Itiff;
-	else if(memcmp(buf, "\115\115\000\052", 4) == 0)
-		g->type = Itiff;
-	else if(memcmp(buf, "\377\330\377", 3) == 0)
-		g->type = Ijpeg;
-	else if(memcmp(buf, "\211PNG\r\n\032\n", 3) == 0)
-		g->type = Ipng;
-	else if(memcmp(buf, "compressed\n", 11) == 0)
-		g->type = Iinferno;
-	else if(memcmp(buf, "\0PC Research, Inc", 17) == 0)
-		g->type = Ifax;
-	else if(memcmp(buf, "TYPE=ccitt-g31", 14) == 0)
-		g->type = Ifax;
-	else if(memcmp(buf, "II*", 3) == 0)
-		g->type = Ifax;
-	else if(memcmp(buf, "TYPE=ccitt-g4", 13) == 0)
-		g->type = Iccittg4;
-	else if(memcmp(buf, "TYPE=", 5) == 0)
-		g->type = Ipic;
-	else if(buf[0] == 'P' && '0' <= buf[1] && buf[1] <= '9')
-		g->type = Ippm;
-	else if(memcmp(buf, "BM", 2) == 0)
-		g->type = Ibmp;
-	else if(memcmp(buf, "          ", 10) == 0 &&
-		'0' <= buf[10] && buf[10] <= '9' &&
-		buf[11] == ' ')
-		g->type = Iplan9bm;
-	else if(strtochan((char*)buf) != 0)
-		g->type = Iplan9bm;
-	else if (l > 4 && strcmp(name + l -4, ".yuv") == 0)
-		g->type = Iyuv;
-	else
-		g->type = Icvt2pic;
-
-	if(name)
-		g->name = estrdup(name);
-	else{
-		g->name = estrdup("stdin");	/* so it can be freed */
-		g->buf = buf;
-		g->nbuf = nbuf;
-	}
-
-	if(chatty) fprint(2, "classified \"%s\" as \"%s\"\n", g->name, cvt[g->type].name);
-	return doc->npage++;
-}
-
-static int 
-addpage(Document *doc, char *name)
-{
-	return genaddpage(doc, name, nil, 0);
-}
-
-static int
-rmpage(Document *doc, int n)
-{
-	int i;
-	GfxInfo *gfx;
-
-	if(n < 0 || n >= doc->npage)
-		return -1;
-
-	gfx = doc->extra;
-	doc->npage--;
-	free(gfx->g[n].name);
-
-	for(i=n; i<doc->npage; i++)
-		gfx->g[i] = gfx->g[i+1];
-
-	if(n < doc->npage)
-		return n;
-	if(n == 0)
-		return 0;
-	return n-1;
-}
-
-
-static Image*
-convert(Graphic *g)
-{
-	int fd;
-	Convert c;
-	char *cmd;
-	char *name, buf[1000];
-	Image *im;
-	int rcspawned = 0;
-	Waitmsg *w;
-
-	c = cvt[g->type];
-	if(c.cmd == nil) {
-		if(chatty) fprint(2, "no conversion for bitmap \"%s\"...\n", g->name);
-		if(g->buf == nil){	/* not stdin */
-			fd = open(g->name, OREAD);
-			if(fd < 0) {
-				fprint(2, "cannot open file: %r\n");
-				wexits("open");
-			}
-		}else
-			fd = stdinpipe(g->buf, g->nbuf);	
-	} else {
-		cmd = c.cmd;
-		if(truecolor && c.truecmd)
-			cmd = c.truecmd;
-
-		if(g->buf != nil)	/* is stdin */
-			name = "";
-		else
-			name = g->name;
-		if(strlen(cmd)+strlen(name) > sizeof buf) {
-			fprint(2, "command too long\n");
-			wexits("convert");
-		}
-		snprint(buf, sizeof buf, cmd, name);
-		if(chatty) fprint(2, "using \"%s\" to convert \"%s\"...\n", buf, g->name);
-		fd = spawnrc(buf, g->buf, g->nbuf);
-		rcspawned++;
-		if(fd < 0) {
-			fprint(2, "cannot spawn converter: %r\n");
-			wexits("convert");
-		}	
-	}
-
-	im = readimage(display, fd, 0);
-	if(im == nil) {
-		fprint(2, "warning: couldn't read image: %r\n");
-	}
-	close(fd);
-
-	/* for some reason rx doesn't work well with wait */
-	/* for some reason 3to1 exits on success with a non-null status of |3to1 */
-	if(rcspawned && g->type != Iccittg4) {
-		if((w=wait())!=nil && w->msg[0] && !strstr(w->msg, "3to1"))
-			fprint(2, "slave wait error: %s\n", w->msg);
-		free(w);
-	}
-	return im;
-}
-
-static int
-spawnrc(char *cmd, uchar *stdinbuf, int nstdinbuf)
-{
-	int pfd[2];
-	int pid;
-
-	if(chatty) fprint(2, "spawning(%s)...", cmd);
-
-	if(pipe(pfd) < 0)
-		return -1;
-	if((pid = fork()) < 0)
-		return -1;
-
-	if(pid == 0) {
-		close(pfd[1]);
-		if(stdinbuf)
-			dup(stdinpipe(stdinbuf, nstdinbuf), 0);
-		else
-			dup(open("/dev/null", OREAD), 0);
-		dup(pfd[0], 1);
-		//dup(pfd[0], 2);
-		execl("/bin/rc", "rc", "-c", cmd, nil);
-		wexits("exec");
-	}
-	close(pfd[0]);
-	return pfd[1];
-}
-
--- a/sys/src/cmd/page/gs.c
+++ /dev/null
@@ -1,342 +1,0 @@
-/*
- * gs interface for page.
- * ps.c and pdf.c both use these routines.
- * a caveat: if you run more than one gs, only the last 
- * one gets killed by killgs 
- */
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <bio.h>
-#include "page.h"
-
-static int gspid;	/* globals for atexit */
-static int gsfd;
-static void	killgs(void);
-
-static void
-killgs(void)
-{
-	char tmpfile[100];
-
-	close(gsfd);
-	postnote(PNGROUP, getpid(), "die");
-
-	/*
-	 * from ghostscript's use.txt:
-	 * ``Ghostscript currently doesn't do a very good job of deleting temporary
-	 * files when it exits; you may have to delete them manually from time to
-	 * time.''
-	 */
-	sprint(tmpfile, "/tmp/gs_%.5da", (gspid+300000)%100000);
-	if(chatty) fprint(2, "remove %s...\n", tmpfile);
-	remove(tmpfile);
-	sleep(100);
-	postnote(PNPROC, gspid, "die yankee pig dog");
-}
-
-int
-spawnwriter(GSInfo *g, Biobuf *b)
-{
-	char buf[4096];
-	int n;
-	int fd;
-
-	switch(fork()){
-	case -1:	return -1;
-	case 0:	break;
-	default:	return 0;
-	}
-
-	Bseek(b, 0, 0);
-	fd = g->gsfd;
-	while((n = Bread(b, buf, sizeof buf)) > 0)
-		write(fd, buf, n);
-	fprint(fd, "(/fd/3) (w) file dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring flushfile\n");
-	_exits(0);
-	return -1;
-}
-
-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;
-}
-
-void
-spawnmonitor(int fd)
-{
-	char buf[4096];
-	char *xbuf;
-	int n;
-	int out;
-	int first;
-
-	switch(rfork(RFFDG|RFNOTEG|RFPROC)){
-	case -1:
-	default:
-		return;
-
-	case 0:
-		break;
-	}
-
-	out = open("/dev/cons", OWRITE);
-	if(out < 0)
-		out = 2;
-
-	xbuf = buf;	/* for ease of acid */
-	first = 1;
-	while((n = read(fd, xbuf, sizeof buf)) > 0){
-		if(first){
-			first = 0;
-			fprint(2, "Ghostscript Error:\n");
-		}
-		write(out, xbuf, n);
-		alarm(500);
-	}
-	_exits(0);
-}
-
-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;
-}
-
-int
-gscmd(GSInfo *gs, char *fmt, ...)
-{
-	char buf[1024];
-	int n;
-
-	va_list v;
-	va_start(v, fmt);
-	n = vseprint(buf, buf+sizeof buf, fmt, v) - buf;
-	if(n <= 0)
-		return n;
-
-	if(chatty) {
-		fprint(2, "cmd: ");
-		write(2, buf, n);
-	}
-
-	if(write(gs->gsfd, buf, n) != 0)
-		return -1;
-
-	return n;
-}
-
-/*
- * set the dimensions of the bitmap we expect to get back from GS.
- */
-void
-setdim(GSInfo *gs, Rectangle bbox, int ppi, int landscape)
-{
-	Rectangle pbox;
-
-	if(chatty)
-		fprint(2, "setdim: bbox=%R\n", bbox);
-
-	if(ppi)
-		gs->ppi = ppi;
-
-	gscmd(gs, "mark\n");
-	if(ppi)
-		gscmd(gs, "/HWResolution [%d %d]\n", ppi, ppi);
-
-	if(!Dx(bbox))
-		bbox = Rect(0, 0, 612, 792);	/* 8½×11 */
-
-	switch(landscape){
-	case 0:
-		pbox = bbox;
-		break;
-	case 1:
-		pbox = Rect(bbox.min.y, bbox.min.x, bbox.max.y, bbox.max.x);
-		break;
-	}
-	gscmd(gs, "/PageSize [%d %d]\n", Dx(pbox), Dy(pbox));
-	gscmd(gs, "/Margins [%d %d]\n", -pbox.min.x, -pbox.min.y);
-	gscmd(gs, "currentdevice putdeviceprops pop\n");
-	gscmd(gs, "/#copies 1 store\n");
-
-	if(!eqpt(bbox.min, ZP))
-		gscmd(gs, "%d %d translate\n", -bbox.min.x, -bbox.min.y);
-
-	switch(landscape){
-	case 0:
-		break;
-	case 1:
-		gscmd(gs, "%d 0 translate\n", Dy(bbox));
-		gscmd(gs, "90 rotate\n");
-		break;
-	}
-
-	waitgs(gs);
-}
-
-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);
-}
--- a/sys/src/cmd/page/mkfile
+++ /dev/null
@@ -1,36 +1,0 @@
-</$objtype/mkfile
-
-TARG=page
-
-HFILES=page.h
-OFILES=\
-	cache.$O\
-	filter.$O\
-	gfx.$O\
-	gs.$O\
-	page.$O\
-	pdf.$O\
-	ps.$O\
-	rotate.$O\
-	util.$O\
-	view.$O\
-
-LIB=/$objtype/lib/libdraw.a
-
-UPDATE=\
-	mkfile\
-	${OFILES:%.$O=%.c}\
-	pdfprolog.ps\
-	$HFILES\
-	/sys/man/1/page\
-	/386/bin/page\
-
-</sys/src/cmd/mkone
-
-BIN=/$objtype/bin
-
-pdfprolog.c: pdfprolog.ps
-	cat pdfprolog.ps | sed 's/.*/"&\\n"/g' >pdfprolog.c
-
-pdf.$O: pdfprolog.c
-
--- a/sys/src/cmd/page/nrotate.c
+++ /dev/null
@@ -1,277 +1,0 @@
-/*
- * Rotate an image 180° in O(log Dx + log Dy)
- * draw calls, using an extra buffer the same size
- * as the image.
- *
- * The basic concept is that you can invert an array by
- * inverting the top half, inverting the bottom half, and
- * then swapping them.
- * 
- * This is usually overkill, but it speeds up slow remote
- * connections quite a bit.
- */
-
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <draw.h>
-#include <event.h>
-#include "page.h"
-
-int ndraw = 0;
-
-enum {
-	Xaxis,
-	Yaxis,
-};
-
-static void reverse(Image*, Image*, int);
-static void shuffle(Image*, Image*, int, int, Image*, int, int);
-static void writefile(char *name, Image *im, int gran);
-static void halvemaskdim(Image*);
-static void swapranges(Image*, Image*, int, int, int, int);
-
-/*
- * Rotate the image 180° by reflecting first
- * along the X axis, and then along the Y axis.
- */
-void
-rot180(Image *img)
-{
-	Image *tmp;
-
-	tmp = xallocimage(display, img->r, img->chan, 0, DNofill);
-	if(tmp == nil)
-		return;
-
-	reverse(img, tmp, Xaxis);
-	reverse(img, tmp, Yaxis);
-
-	freeimage(tmp);
-}
-
-Image *mtmp;
-
-static void
-reverse(Image *img, Image *tmp, int axis)
-{
-	Image *mask;
-	Rectangle r;
-	int i, d;
-
-	/*
-	 * We start by swapping large chunks at a time.
-	 * The chunk size should be the largest power of
-	 * two that fits in the dimension.
-	 */
-	d = axis==Xaxis ? Dx(img) : Dy(img);
-	for(i = 1; i*2 <= d; i *= 2)
-		;
-
-	r = axis==Xaxis ? Rect(0,0, i,100) : Rect(0,0, 100,i);
-	mask = xallocimage(display, r, GREY1, 1, DTransparent);
-	mtmp = xallocimage(display, r, GREY1, 1, DTransparent);
-
-	/*
-	 * Now color the bottom (or left) half of the mask opaque.
-	 */
-	if(axis==Xaxis)
-		r.max.x /= 2;
-	else
-		r.max.y /= 2;
-
-	draw(mask, r, display->opaque, nil, ZP);
-	writefile("mask", mask, i);
-
-	/*
-	 * Shuffle will recur, shuffling the pieces as necessary
-	 * and making the mask a finer and finer grating.
-	 */
-	shuffle(img, tmp, axis, d, mask, i, 0);
-
-	freeimage(mask);
-}
-
-/*
- * Shuffle the image by swapping pieces of size maskdim.
- */
-static void
-shuffle(Image *img, Image *tmp, int axis, int imgdim, Image *mask, int maskdim)
-{
-	int slop;
-
-	if(maskdim == 0)
-		return;
-
-	/*
-	 * Figure out how much will be left over that needs to be
-	 * shifted specially to the bottom.
-	 */
-	slop = imgdim % maskdim;
-
-	/*
-	 * Swap adjacent grating lines as per mask.
-	 */
-	swapadjacent(img, tmp, axis, imgdim - slop, mask, maskdim);
-
-	/*
-	 * Calculate the mask with gratings half as wide and recur.
-	 */
-	halvemaskdim(mask, maskdim, axis);
-	writefile("mask", mask, maskdim/2);
-
-	shuffle(img, tmp, axis, imgdim, mask, maskdim/2);
-
-	/*
-	 * Move the slop down to the bottom of the image.
-	 */
-	swapranges(img, tmp, 0, imgdim-slop, imgdim, axis);
-	moveup(im, tmp, lastnn, nn, n, axis);
-}
-
-/*
- * Halve the grating period in the mask.
- * The grating currently looks like 
- * ####____####____####____####____
- * where #### is opacity.
- *
- * We want
- * ##__##__##__##__##__##__##__##__
- * which is achieved by shifting the mask
- * and drawing on itself through itself.
- * Draw doesn't actually allow this, so 
- * we have to copy it first.
- *
- *     ####____####____####____####____ (dst)
- * +   ____####____####____####____#### (src)
- * in  __####____####____####____####__ (mask)
- * ===========================================
- *     ##__##__##__##__##__##__##__##__
- */
-static void
-halvemaskdim(Image *m, int maskdim, int axis)
-{
-	Point δ;
-
-	δ = axis==Xaxis ? Pt(maskdim,0) : Pt(0,maskdim);
-	draw(mtmp, mtmp->r, mask, nil, mask->r.min);
-	gendraw(mask, mask->r, mtmp, δ, mtmp, divpt(δ,2));
-	writefile("mask", mask, maskdim/2);
-}
-
-/*
- * Swap the regions [a,b] and [b,c]
- */
-static void
-swapranges(Image *img, Image *tmp, int a, int b, int c, int axis)
-{
-	Rectangle r;
-	Point δ;
-
-	if(a == b || b == c)
-		return;
-
-	writefile("swap", img, 0);
-	draw(tmp, tmp->r, im, nil, im->r.min);
-
-	/* [a,a+(c-b)] gets [b,c] */
-	r = img->r;
-	if(axis==Xaxis){
-		δ = Pt(1,0);
-		r.min.x = img->r.min.x + a;
-		r.max.x = img->r.min.x + a + (c-b);
-	}else{
-		δ = Pt(0,1);
-		r.min.y = img->r.min.y + a;
-		r.max.y = img->r.min.y + a + (c-b);
-	}
-	draw(img, r, tmp, nil, addpt(tmp->r.min, mulpt(δ, b)));
-
-	/* [a+(c-b), c] gets [a,b] */
-	r = img->r;
-	if(axis==Xaxis){
-		r.min.x = img->r.min.x + a + (c-b);
-		r.max.x = img->r.min.x + c;
-	}else{
-		r.min.y = img->r.min.y + a + (c-b);
-		r.max.y = img->r.min.y + c;
-	}
-	draw(img, r, tmp, nil, addpt(tmp->r.min, mulpt(δ, a)));
-	writefile("swap", img, 1);
-}
-
-/*
- * Swap adjacent regions as specified by the grating.
- * We do this by copying the image through the mask twice,
- * once aligned with the grading and once 180° out of phase.
- */
-static void
-swapadjacent(Image *img, Image *tmp, int axis, int imgdim, Image *mask, int maskdim)
-{
-	Point δ;
-	Rectangle r0, r1;
-
-	δ = axis==Xaxis ? Pt(1,0) : Pt(0,1);
-
-	r0 = img->r;
-	r1 = img->r;
-	switch(axis){
-	case Xaxis:
-		r0.max.x = imgdim;
-		r1.min.x = imgdim;
-		break;
-	case Yaxis:
-		r0.max.y = imgdim;
-		r1.min.y = imgdim;
-	}
-
-	/*
-	 * r0 is the lower rectangle, while r1 is the upper one.
-	 */
-	draw(tmp, tmp->r, img, nil, 
-}
-
-void
-interlace(Image *im, Image *tmp, int axis, int n, Image *mask, int gran)
-{
-	Point p0, p1;
-	Rectangle r0, r1;
-
-	r0 = im->r;
-	r1 = im->r;
-	switch(axis) {
-	case Xaxis:
-		r0.max.x = n;
-		r1.min.x = n;
-		p0 = (Point){gran, 0};
-		p1 = (Point){-gran, 0};
-		break;
-	case Yaxis:
-		r0.max.y = n;
-		r1.min.y = n;
-		p0 = (Point){0, gran};
-		p1 = (Point){0, -gran};
-		break;
-	}
-
-	draw(tmp, im->r, im, display->black, im->r.min);
-	gendraw(im, r0, tmp, p0, mask, mask->r.min);
-	gendraw(im, r0, tmp, p1, mask, p1);
-}
-
-
-static void
-writefile(char *name, Image *im, int gran)
-{
-	static int c = 100;
-	int fd;
-	char buf[200];
-
-	snprint(buf, sizeof buf, "%d%s%d", c++, name, gran);
-	fd = create(buf, OWRITE, 0666);
-	if(fd < 0)
-		return;	
-	writeimage(fd, im, 0);
-	close(fd);
-}
-
--- a/sys/src/cmd/page/page.c
+++ /dev/null
@@ -1,238 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <bio.h>
-#include "page.h"
-
-int resizing;
-int mknewwindow;
-int doabort;
-int chatty;
-int reverse = -1;
-int goodps = 1;
-int ppi = 100;
-int teegs = 0;
-int truetoboundingbox;
-int textbits=4, gfxbits=4;
-int wctlfd = -1;
-int stdinfd;
-int truecolor;
-int imagemode;
-int notewatcher;
-int notegp;
-
-int
-watcher(void*, char *x)
-{
-	if(strcmp(x, "die") != 0)
-		postnote(PNGROUP, notegp, x);
-	_exits(0);
-	return 0;
-}
-
-int
-bell(void *u, char *x)
-{
-	if(x && strcmp(x, "hangup") == 0)
-		_exits(0);
-
-	if(x && strstr(x, "die") == nil)
-		fprint(2, "postnote %d: %s\n", getpid(), x);
-
-	/* alarms come from the gs monitor */
-	if(x && strstr(x, "alarm")){
-		postnote(PNGROUP, getpid(), "die (gs error)");
-		postnote(PNPROC, notewatcher, "die (gs error)");
-	}
-
-	/* function mentions u so that it's in the stack trace */
-	if((u == nil || u != x) && doabort)
-		abort();
-
-/*	fprint(2, "exiting %d\n", getpid()); */
-	wexits("note");
-	return 0;
-}
-
-static int
-afmt(Fmt *fmt)
-{
-	char *s;
-
-	s = va_arg(fmt->args, char*);
-	if(s == nil || s[0] == '\0')
-		return fmtstrcpy(fmt, "");
-	else
-		return fmtprint(fmt, "%#q", s);
-}
-
-void
-usage(void)
-{
-	fprint(2, "usage: page [-biRrw] [-p ppi] file...\n");
-	exits("usage");
-}
-
-void
-main(int argc, char **argv)
-{
-	Document *doc;
-	Biobuf *b;
-	enum { Ninput = 16 };
-	uchar buf[Ninput+1];
-	int readstdin;
-
-	ARGBEGIN{
-	/* "temporary" debugging options */
-	case 'P':
-		goodps = 0;
-		break;
-	case 'v':
-		chatty++;
-		break;
-	case 'V':
-		teegs++;
-		break;
-	case 'a':
-		doabort++;
-		break;
-	case 'T':
-		textbits = atoi(EARGF(usage()));
-		gfxbits = atoi(EARGF(usage()));
-		break;
-
-	/* real options */
-	case 'R':
-		resizing = 1;
-		break;
-	case 'r':
-		reverse = 1;
-		break;
-	case 'p':
-		ppi = atoi(EARGF(usage()));
-		break;
-	case 'b':
-		truetoboundingbox = 1;
-		break;
-	case 'w':
-		mknewwindow = 1;
-		resizing = 1;
-		break;
-	case 'i':
-		imagemode = 1;
-		break;
-	default:
-		usage();
-	}ARGEND;
-
-	notegp = getpid();
-
-	switch(notewatcher = fork()){
-	case -1:
-		sysfatal("fork");
-		exits(0);
-	default:
-		break;
-	case 0:
-		atnotify(watcher, 1);
-		for(;;)
-			sleep(1000);
-		/* not reached */
-	}
-
-	rfork(RFNOTEG);
-	atnotify(bell, 1);
-
-	readstdin = 0;
-	if(imagemode == 0 && argc == 0){
-		readstdin = 1;
-		stdinfd = dup(0, -1);
-		close(0);
-		open("/dev/cons", OREAD);
-	}
-
-	quotefmtinstall();
-	fmtinstall('a', afmt);
-
-	fmtinstall('R', Rfmt);
-	fmtinstall('P', Pfmt);
-	if(mknewwindow)
-		newwin();
-
-	if(readstdin){
-		b = nil;
-		if(readn(stdinfd, buf, Ninput) != Ninput){
-			fprint(2, "page: short read reading %s\n", argv[0]);
-			wexits("read");
-		}
-	}else if(argc != 0){
-		if(!(b = Bopen(argv[0], OREAD))) {
-			fprint(2, "page: cannot open \"%s\"\n", argv[0]);
-			wexits("open");
-		}	
-
-		if(Bread(b, buf, Ninput) != Ninput) {
-			fprint(2, "page: short read reading %s\n", argv[0]);
-			wexits("read");
-		}
-	}else
-		b = nil;
-
-	buf[Ninput] = '\0';
-	if(imagemode)
-		doc = initgfx(nil, 0, nil, nil, 0);
-	else if(strncmp((char*)buf, "%PDF-", 5) == 0)
-		doc = initpdf(b, argc, argv, buf, Ninput);
-	else if(strncmp((char*)buf, "\x04%!", 2) == 0)
-		doc = initps(b, argc, argv, buf, Ninput);
-	else if(buf[0] == '\x1B' && strstr((char*)buf, "@PJL"))
-		doc = initps(b, argc, argv, buf, Ninput);
-	else if(strncmp((char*)buf, "%!", 2) == 0)
-		doc = initps(b, argc, argv, buf, Ninput);
-	else if(strcmp((char*)buf, "\xF7\x02\x01\x83\x92\xC0\x1C;") == 0)
-		doc = initdvi(b, argc, argv, buf, Ninput);
-	else if(strncmp((char*)buf, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 8) == 0)
-		doc = initmsdoc(b, argc, argv, buf, Ninput);
-	else if(strncmp((char*)buf, "x T ", 4) == 0)
-		doc = inittroff(b, argc, argv, buf, Ninput);
-	else {
-		if(ppi != 100) {
-			fprint(2, "page: you can't specify -p with graphic files\n");
-			wexits("-p and graphics");
-		}
-		doc = initgfx(b, argc, argv, buf, Ninput);
-	}
-
-	if(doc == nil) {
-		fprint(2, "page: error reading file: %r\n");
-		wexits("document init");
-	}
-
-	if(doc->npage < 1 && !imagemode) {
-		fprint(2, "page: no pages found?\n");
-		wexits("pagecount");
-	}
-
-	if(reverse == -1) /* neither cmdline nor ps reader set it */
-		reverse = 0;
-
-	if(initdraw(0, 0, "page") < 0){
-		fprint(2, "page: initdraw failed: %r\n");
-		wexits("initdraw");
-	}
-	display->locking = 1;
-
-	truecolor = screen->depth > 8;
-	viewer(doc);
-	wexits(0);
-}
-
-void
-wexits(char *s)
-{
-	if(s && *s && strcmp(s, "note") != 0 && mknewwindow)
-		sleep(10*1000);
-	postnote(PNPROC, notewatcher, "die");
-	exits(s);
-}
--- a/sys/src/cmd/page/page.h
+++ /dev/null
@@ -1,85 +1,0 @@
-typedef struct Document Document;
-
-struct Document {
-	char *docname;
-	int npage;
-	int fwdonly;
-	char* (*pagename)(Document*, int);
-	Image* (*drawpage)(Document*, int);
-	int	(*addpage)(Document*, char*);
-	int	(*rmpage)(Document*, int);
-	Biobuf *b;
-	void *extra;
-};
-
-void *emalloc(int);
-void *erealloc(void*, int);
-char *estrdup(char*);
-int spawncmd(char*, char **, int, int, int);
-
-int spooltodisk(uchar*, int, char**);
-int stdinpipe(uchar*, int);
-Document *initps(Biobuf*, int, char**, uchar*, int);
-Document *initpdf(Biobuf*, int, char**, uchar*, int);
-Document *initgfx(Biobuf*, int, char**, uchar*, int);
-Document *inittroff(Biobuf*, int, char**, uchar*, int);
-Document *initdvi(Biobuf*, int, char**, uchar*, int);
-Document *initmsdoc(Biobuf*, int, char**, uchar*, int);
-
-void viewer(Document*);
-extern Cursor reading;
-extern int chatty;
-extern int goodps;
-extern int textbits, gfxbits;
-extern int reverse;
-extern int clean;
-extern int ppi;
-extern int teegs;
-extern int truetoboundingbox;
-extern int wctlfd;
-extern int resizing;
-extern int mknewwindow;
-
-void rot180(Image*);
-Image *rot90(Image*);
-Image *rot270(Image*);
-Image *resample(Image*, Image*);
-
-/* ghostscript interface shared by ps, pdf */
-typedef struct GSInfo	GSInfo;
-struct GSInfo {
-	int gsfd;
-	Biobuf gsrd;
-	int gspid;
-	int gsdfd;
-	int ppi;
-};
-void	waitgs(GSInfo*);
-int	gscmd(GSInfo*, char*, ...);
-int	spawngs(GSInfo*, char*);
-void	setdim(GSInfo*, Rectangle, int, int);
-int	spawnwriter(GSInfo*, Biobuf*);
-Rectangle	screenrect(void);
-void	newwin(void);
-void	zerox(void);
-Rectangle winrect(void);
-void	resize(int, int);
-int	max(int, int);
-int	min(int, int);
-void	wexits(char*);
-Image*	xallocimage(Display*, Rectangle, ulong, int, ulong);
-int	bell(void*, char*);
-int	opentemp(char *template);
-Image*	cachedpage(Document*, int, int);
-void	cacheflush(void);
-
-extern int stdinfd;
-extern int truecolor;
-
-/* BUG BUG BUG BUG BUG: cannot use new draw operations in drawterm,
- * or in vncs, and there is a bug in the kernel for copying images
- * from cpu memory -> video memory (memmove is not being used).
- * until all that is settled, ignore the draw operators.
- */
-#define drawop(a,b,c,d,e,f) draw(a,b,c,d,e)
-#define gendrawop(a,b,c,d,e,f,g) gendraw(a,b,c,d,e,f)
--- a/sys/src/cmd/page/pdf.c
+++ /dev/null
@@ -1,153 +1,0 @@
-/*
- * pdf.c
- * 
- * pdf file support for page
- */
-
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <bio.h>
-#include "page.h"
-
-typedef struct PDFInfo	PDFInfo;
-struct PDFInfo {
-	GSInfo;
-	Rectangle *pagebbox;
-};
-
-static Image*	pdfdrawpage(Document *d, int page);
-static char*	pdfpagename(Document*, int);
-
-char *pdfprolog = 
-#include "pdfprolog.c"
-	;
-
-Rectangle
-pdfbbox(GSInfo *gs)
-{
-	char *p;
-	char *f[4];
-	Rectangle r;
-	
-	r = Rect(0,0,0,0);
-	waitgs(gs);
-	gscmd(gs, "/CropBox knownoget {} {[0 0 0 0]} ifelse PAGE==\n");
-	p = Brdline(&gs->gsrd, '\n');
-	p[Blinelen(&gs->gsrd)-1] ='\0';
-	if(p[0] != '[')
-		return r;
-	if(tokenize(p+1, f, 4) != 4)
-		return r;
-	r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
-	waitgs(gs);
-	return r;
-}
-
-Document*
-initpdf(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
-{
-	Document *d;
-	PDFInfo *pdf;
-	char *p;
-	char *fn;
-	char fdbuf[20];
-	int fd;
-	int i, npage;
-	Rectangle bbox;
-
-	if(argc > 1) {
-		fprint(2, "can only view one pdf file at a time\n");
-		return nil;
-	}
-
-	fprint(2, "reading through pdf...\n");
-	if(b == nil){	/* standard input; spool to disk (ouch) */
-		fd = spooltodisk(buf, nbuf, &fn);
-		sprint(fdbuf, "/fd/%d", fd);
-		b = Bopen(fdbuf, OREAD);
-		if(b == nil){
-			fprint(2, "cannot open disk spool file\n");
-			wexits("Bopen temp");
-		}
-	}else
-		fn = argv[0];
-
-	/* sanity check */
-	Bseek(b, 0, 0);
-	if(!(p = Brdline(b, '\n')) && !(p = Brdline(b, '\r'))) {
-		fprint(2, "cannot find end of first line\n");
-		wexits("initps");
-	}
-	if(strncmp(p, "%PDF-", 5) != 0) {
-		werrstr("not pdf");
-		return nil;
-	}
-
-	/* setup structures so one free suffices */
-	p = emalloc(sizeof(*d) + sizeof(*pdf));
-	d = (Document*) p;
-	p += sizeof(*d);
-	pdf = (PDFInfo*) p;
-
-	d->extra = pdf;
-	d->b = b;
-	d->drawpage = pdfdrawpage;
-	d->pagename = pdfpagename;
-	d->fwdonly = 0;
-
-	if(spawngs(pdf, "-dDELAYSAFER") < 0)
-		return nil;
-
-	gscmd(pdf, "%s", pdfprolog);
-	waitgs(pdf);
-
-	setdim(pdf, Rect(0,0,0,0), ppi, 0);
-	gscmd(pdf, "(%s) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n", fn);
-	gscmd(pdf, "pdfpagecount PAGE==\n");
-	p = Brdline(&pdf->gsrd, '\n');
-	npage = atoi(p);
-	if(npage < 1) {
-		fprint(2, "no pages?\n");
-		return nil;
-	}
-	d->npage = npage;
-	d->docname = argv[0];
-
-	gscmd(pdf, "Trailer\n");
-	bbox = pdfbbox(pdf);
-
-	pdf->pagebbox = emalloc(sizeof(Rectangle)*npage);
-	for(i=0; i<npage; i++) {
-		gscmd(pdf, "%d pdfgetpage\n", i+1);
-		pdf->pagebbox[i] = pdfbbox(pdf);
-		if(Dx(pdf->pagebbox[i]) <= 0)
-			pdf->pagebbox[i] = bbox;
-	}
-	return d;
-}
-
-static Image*
-pdfdrawpage(Document *doc, int page)
-{
-	PDFInfo *pdf = doc->extra;
-	Image *im;
-
-	gscmd(pdf, "%d DoPDFPage\n", page+1);
-	im = readimage(display, pdf->gsdfd, 0);
-	if(im == nil) {
-		fprint(2, "fatal: readimage error %r\n");
-		wexits("readimage");
-	}
-	waitgs(pdf);
-	return im;
-}
-
-static char*
-pdfpagename(Document*, int page)
-{
-	static char str[15];
-	sprint(str, "p %d", page+1);
-	return str;
-}
--- a/sys/src/cmd/page/pdfprolog.ps
+++ /dev/null
@@ -1,20 +1,0 @@
-/Page null def
-/Page# 0 def
-/PDFSave null def
-/DSCPageCount 0 def
-/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def
-
-/pdfshowpage_mysetpage {	% <pagedict> pdfshowpage_mysetpage <pagedict>
-  dup /CropBox pget {
-      boxrect
-      2 array astore /PageSize exch 4 2 roll
-      4 index /Rotate pget {
-        dup 0 lt {360 add} if 90 idiv {exch neg} repeat
-      } if
-      exch neg exch 2 array astore /PageOffset exch
-      << 5 1 roll >> setpagedevice
-  } if
-} bind def
-
-GS_PDF_ProcSet begin
-pdfdict begin
--- a/sys/src/cmd/page/ps.c
+++ /dev/null
@@ -1,450 +1,0 @@
-/*
- * ps.c
- * 
- * provide postscript file reading support for page
- */
-
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <bio.h>
-#include <ctype.h>
-#include "page.h"
-
-typedef struct PSInfo	PSInfo;
-typedef struct Page	Page;
-	
-struct Page {
-	char *name;
-	int offset;			/* offset of page beginning within file */
-};
-
-struct PSInfo {
-	GSInfo;
-	Rectangle bbox;	/* default bounding box */
-	Page *page;
-	int npage;
-	int clueless;	/* don't know where page boundaries are */
-	long psoff;	/* location of %! in file */
-	char ctm[256];
-};
-
-static int	pswritepage(Document *d, int fd, int page);
-static Image*	psdrawpage(Document *d, int page);
-static char*	pspagename(Document*, int);
-
-#define R(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
-Rectangle
-rdbbox(char *p)
-{
-	Rectangle r;
-	int a;
-	char *f[4];
-	while(*p == ':' || *p == ' ' || *p == '\t')
-		p++;
-	if(tokenize(p, f, 4) != 4)
-		return Rect(0,0,0,0);
-	r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
-	r = canonrect(r);
-	if(Dx(r) <= 0 || Dy(r) <= 0)
-		return Rect(0,0,0,0);
-
-	if(truetoboundingbox)
-		return r;
-
-	/* initdraw not called yet, can't use %R */
-	if(chatty) fprint(2, "[%d %d %d %d] -> ", R(r));
-	/*
-	 * attempt to sniff out A4, 8½×11, others
-	 * A4 is 596×842
-	 * 8½×11 is 612×792
-	 */
-
-	a = Dx(r)*Dy(r);
-	if(a < 300*300){	/* really small, probably supposed to be */
-		/* empty */
-	} else if(Dx(r) <= 596 && r.max.x <= 596 && Dy(r) > 792 && Dy(r) <= 842 && r.max.y <= 842)	/* A4 */
-		r = Rect(0, 0, 596, 842);
-	else {	/* cast up to 8½×11 */
-		if(Dx(r) <= 612 && r.max.x <= 612){
-			r.min.x = 0;
-			r.max.x = 612;
-		}
-		if(Dy(r) <= 792 && r.max.y <= 792){
-			r.min.y = 0;
-			r.max.y = 792;
-		}
-	}
-	if(chatty) fprint(2, "[%d %d %d %d]\n", R(r));
-	return r;
-}
-
-#define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y
-
-int
-prefix(char *x, char *y)
-{
-	return strncmp(x, y, strlen(y)) == 0;
-}
-
-/*
- * document ps is really being printed as n-up pages.
- * we need to treat every n pages as 1.
- */
-void
-repaginate(PSInfo *ps, int n)
-{
-	int i, np, onp;
-	Page *page;
-
-	page = ps->page;
-	onp = ps->npage;
-	np = (ps->npage+n-1)/n;
-
-	if(chatty) {
-		for(i=0; i<=onp+1; i++)
-			print("page %d: %d\n", i, page[i].offset);
-	}
-
-	for(i=0; i<np; i++)
-		page[i] = page[n*i];
-
-	/* trailer */
-	page[np] = page[onp];
-
-	/* EOF */
-	page[np+1] = page[onp+1];
-
-	ps->npage = np;
-
-	if(chatty) {
-		for(i=0; i<=np+1; i++)
-			print("page %d: %d\n", i, page[i].offset);
-	}
-
-}
-
-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, "/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 > 1) 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];
-
-	if(spawngs(ps, "-dSAFER") < 0)
-		return nil;
-
-	if(!cantranslate)
-		bbox.min = ZP;
-	setdim(ps, 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->gsfd, -1);
-		waitgs(ps);
-	}
-
-	if(dumb) {
-		fprint(ps->gsfd, "(%s) run\n", argv[0]);
-		fprint(ps->gsfd, "(/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n");
-	}
-
-	ps->bbox = bbox;
-
-	return d;
-}
-
-static int
-pswritepage(Document *d, int fd, int page)
-{
-	Biobuf *b = d->b;
-	PSInfo *ps = d->extra;
-	int t, n, i;
-	long begin, end;
-	char buf[8192];
-
-	if(page == -1)
-		begin = ps->psoff;
-	else
-		begin = ps->page[page].offset;
-
-	end = ps->page[page+1].offset;
-
-	if(chatty) {
-		fprint(2, "writepage(%d)... from #%ld to #%ld...\n",
-			page, begin, end);
-	}
-	Bseek(b, begin, 0);
-
-	t = end-begin;
-	n = sizeof(buf);
-	if(n > t) n = t;
-	while(t > 0 && (i=Bread(b, buf, n)) > 0) {
-		if(write(fd, buf, i) != i)
-			return -1;
-		t -= i;
-		if(n > t)
-			n = t;
-	}
-	return end-begin;
-}
-
-static Image*
-psdrawpage(Document *d, int page)
-{
-	PSInfo *ps = d->extra;
-	Image *im;
-
-	if(ps->clueless)
-		return readimage(display, ps->gsdfd, 0);
-
-	waitgs(ps);
-
-	if(goodps)
-		pswritepage(d, ps->gsfd, page);
-	else {
-		pswritepage(d, ps->gsfd, -1);
-		pswritepage(d, ps->gsfd, page);
-		pswritepage(d, ps->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->gsfd, "\n", 1);
-	im = readimage(display, ps->gsdfd, 0);
-	if(im == nil) {
-		fprint(2, "fatal: readimage error %r\n");
-		wexits("readimage");
-	}
-	waitgs(ps);
-
-	return im;
-}
-
-static char*
-pspagename(Document *d, int page)
-{
-	PSInfo *ps = (PSInfo *) d->extra;
-	return ps->page[page].name;
-}
--- a/sys/src/cmd/page/rotate.c
+++ /dev/null
@@ -1,500 +1,0 @@
-/*
- * rotate an image 180° in O(log Dx + log Dy) /dev/draw writes,
- * using an extra buffer same size as the image.
- * 
- * the basic concept is that you can invert an array by inverting
- * the top half, inverting the bottom half, and then swapping them.
- * the code does this slightly backwards to ensure O(log n) runtime.
- * (If you do it wrong, you can get O(log² n) runtime.)
- * 
- * This is usually overkill, but it speeds up slow remote
- * connections quite a bit.
- */
-
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <draw.h>
-#include <event.h>
-#include "page.h"
-
-int ndraw = 0;
-enum {
-	Xaxis = 0,
-	Yaxis = 1,
-};
-
-Image *mtmp;
-
-void
-writefile(char *name, Image *im, int gran)
-{
-	static int c = 100;
-	int fd;
-	char buf[200];
-
-	snprint(buf, sizeof buf, "%d%s%d", c++, name, gran);
-	fd = create(buf, OWRITE, 0666);
-	if(fd < 0)
-		return;	
-	writeimage(fd, im, 0);
-	close(fd);
-}
-
-void
-moveup(Image *im, Image *tmp, int a, int b, int c, int axis)
-{
-	Rectangle range;
-	Rectangle dr0, dr1;
-	Point p0, p1;
-
-	if(a == b || b == c)
-		return;
-
-	drawop(tmp, tmp->r, im, nil, im->r.min, S);
-
-	switch(axis){
-	case Xaxis:
-		range = Rect(a, im->r.min.y,  c, im->r.max.y);
-		dr0 = range;
-		dr0.max.x = dr0.min.x+(c-b);
-		p0 = Pt(b, im->r.min.y);
-
-		dr1 = range;
-		dr1.min.x = dr1.max.x-(b-a);
-		p1 = Pt(a, im->r.min.y);
-		break;
-	case Yaxis:
-		range = Rect(im->r.min.x, a,  im->r.max.x, c);
-		dr0 = range;
-		dr0.max.y = dr0.min.y+(c-b);
-		p0 = Pt(im->r.min.x, b);
-
-		dr1 = range;
-		dr1.min.y = dr1.max.y-(b-a);
-		p1 = Pt(im->r.min.x, a);
-		break;
-	}
-	drawop(im, dr0, tmp, nil, p0, S);
-	drawop(im, dr1, tmp, nil, p1, S);
-}
-
-void
-interlace(Image *im, Image *tmp, int axis, int n, Image *mask, int gran)
-{
-	Point p0, p1;
-	Rectangle r0, r1;
-
-	r0 = im->r;
-	r1 = im->r;
-	switch(axis) {
-	case Xaxis:
-		r0.max.x = n;
-		r1.min.x = n;
-		p0 = (Point){gran, 0};
-		p1 = (Point){-gran, 0};
-		break;
-	case Yaxis:
-		r0.max.y = n;
-		r1.min.y = n;
-		p0 = (Point){0, gran};
-		p1 = (Point){0, -gran};
-		break;
-	}
-
-	drawop(tmp, im->r, im, display->opaque, im->r.min, S);
-	gendrawop(im, r0, tmp, p0, mask, mask->r.min, S);
-	gendrawop(im, r0, tmp, p1, mask, p1, S);
-}
-
-/*
- * Halve the grating period in the mask.
- * The grating currently looks like 
- * ####____####____####____####____
- * where #### is opacity.
- *
- * We want
- * ##__##__##__##__##__##__##__##__
- * which is achieved by shifting the mask
- * and drawing on itself through itself.
- * Draw doesn't actually allow this, so 
- * we have to copy it first.
- *
- *     ####____####____####____####____ (dst)
- * +   ____####____####____####____#### (src)
- * in  __####____####____####____####__ (mask)
- * ===========================================
- *     ##__##__##__##__##__##__##__##__
- */
-int
-nextmask(Image *mask, int axis, int maskdim)
-{
-	Point δ;
-
-	δ = axis==Xaxis ? Pt(maskdim,0) : Pt(0,maskdim);
-	drawop(mtmp, mtmp->r, mask, nil, mask->r.min, S);
-	gendrawop(mask, mask->r, mtmp, δ, mtmp, divpt(δ,-2), S);
-//	writefile("mask", mask, maskdim/2);
-	return maskdim/2;
-}
-
-void
-shuffle(Image *im, Image *tmp, int axis, int n, Image *mask, int gran,
-	int lastnn)
-{
-	int nn, left;
-
-	if(gran == 0)
-		return;
-	left = n%(2*gran);
-	nn = n - left;
-
-	interlace(im, tmp, axis, nn, mask, gran);
-//	writefile("interlace", im, gran);
-	
-	gran = nextmask(mask, axis, gran);
-	shuffle(im, tmp, axis, n, mask, gran, nn);
-//	writefile("shuffle", im, gran);
-	moveup(im, tmp, lastnn, nn, n, axis);
-//	writefile("move", im, gran);
-}
-
-void
-rot180(Image *im)
-{
-	Image *tmp, *tmp0;
-	Image *mask;
-	Rectangle rmask;
-	int gran;
-
-	if(chantodepth(im->chan) < 8){
-		/* this speeds things up dramatically; draw is too slow on sub-byte pixel sizes */
-		tmp0 = xallocimage(display, im->r, CMAP8, 0, DNofill);
-		drawop(tmp0, tmp0->r, im, nil, im->r.min, S);
-	}else
-		tmp0 = im;
-
-	tmp = xallocimage(display, tmp0->r, tmp0->chan, 0, DNofill);
-	if(tmp == nil){
-		if(tmp0 != im)
-			freeimage(tmp0);
-		return;
-	}
-	for(gran=1; gran<Dx(im->r); gran *= 2)
-		;
-	gran /= 4;
-
-	rmask.min = ZP;
-	rmask.max = (Point){2*gran, 100};
-
-	mask = xallocimage(display, rmask, GREY1, 1, DTransparent);
-	mtmp = xallocimage(display, rmask, GREY1, 1, DTransparent);
-	if(mask == nil || mtmp == nil) {
-		fprint(2, "out of memory during rot180: %r\n");
-		wexits("memory");
-	}
-	rmask.max.x = gran;
-	drawop(mask, rmask, display->opaque, nil, ZP, S);
-//	writefile("mask", mask, gran);
-	shuffle(im, tmp, Xaxis, Dx(im->r), mask, gran, 0);
-	freeimage(mask);
-	freeimage(mtmp);
-
-	for(gran=1; gran<Dy(im->r); gran *= 2)
-		;
-	gran /= 4;
-	rmask.max = (Point){100, 2*gran};
-	mask = xallocimage(display, rmask, GREY1, 1, DTransparent);
-	mtmp = xallocimage(display, rmask, GREY1, 1, DTransparent);
-	if(mask == nil || mtmp == nil) {
-		fprint(2, "out of memory during rot180: %r\n");
-		wexits("memory");
-	}
-	rmask.max.y = gran;
-	drawop(mask, rmask, display->opaque, nil, ZP, S);
-	shuffle(im, tmp, Yaxis, Dy(im->r), mask, gran, 0);
-	freeimage(mask);
-	freeimage(mtmp);
-	freeimage(tmp);
-	if(tmp0 != im)
-		freeimage(tmp0);
-}
-
-/* rotates an image 90 degrees clockwise */
-Image *
-rot90(Image *im)
-{
-	Image *tmp;
-	int i, j, dx, dy;
-
-	dx = Dx(im->r);
-	dy = Dy(im->r);
-	tmp = xallocimage(display, Rect(0, 0, dy, dx), im->chan, 0, DCyan);
-	if(tmp == nil) {
-		fprint(2, "out of memory during rot90: %r\n");
-		wexits("memory");
-	}
-
-	for(j = 0; j < dx; j++) {
-		for(i = 0; i < dy; i++) {
-			drawop(tmp, Rect(i, j, i+1, j+1), im, nil, Pt(j, dy-(i+1)), S);
-		}
-	}
-	freeimage(im);
-
-	return(tmp);
-}
-
-/* rotates an image 270 degrees clockwise */
-Image *
-rot270(Image *im)
-{
-	Image *tmp;
-	int i, j, dx, dy;
-
-	dx = Dx(im->r);
-	dy = Dy(im->r);
-	tmp = xallocimage(display, Rect(0, 0, dy, dx), im->chan, 0, DCyan);
-	if(tmp == nil) {
-		fprint(2, "out of memory during rot270: %r\n");
-		wexits("memory");
-	}
-
-	for(i = 0; i < dy; i++) {
-		for(j = 0; j < dx; j++) {
-			drawop(tmp, Rect(i, j, i+1, j+1), im, nil, Pt(dx-(j+1), i), S);
-		}
-	}
-	freeimage(im);
-
-	return(tmp);
-}
-
-/* from resample.c -- resize from → to using interpolation */
-
-
-#define K2 7	/* from -.7 to +.7 inclusive, meaning .2 into each adjacent pixel */
-#define NK (2*K2+1)
-double K[NK];
-
-double
-fac(int L)
-{
-	int i, f;
-
-	f = 1;
-	for(i=L; i>1; --i)
-		f *= i;
-	return f;
-}
-
-/* 
- * i0(x) is the modified Bessel function, Σ (x/2)^2L / (L!)²
- * There are faster ways to calculate this, but we precompute
- * into a table so let's keep it simple.
- */
-double
-i0(double x)
-{
-	double v;
-	int L;
-
-	v = 1.0;
-	for(L=1; L<10; L++)
-		v += pow(x/2., 2*L)/pow(fac(L), 2);
-	return v;
-}
-
-double
-kaiser(double x, double τ, double α)
-{
-	if(fabs(x) > τ)
-		return 0.;
-	return i0(α*sqrt(1-(x*x/(τ*τ))))/i0(α);
-}
-
-
-void
-resamplex(uchar *in, int off, int d, int inx, uchar *out, int outx)
-{
-	int i, x, k;
-	double X, xx, v, rat;
-
-
-	rat = (double)inx/(double)outx;
-	for(x=0; x<outx; x++){
-		if(inx == outx){
-			/* don't resample if size unchanged */
-			out[off+x*d] = in[off+x*d];
-			continue;
-		}
-		v = 0.0;
-		X = x*rat;
-		for(k=-K2; k<=K2; k++){
-			xx = X + rat*k/10.;
-			i = xx;
-			if(i < 0)
-				i = 0;
-			if(i >= inx)
-				i = inx-1;
-			v += in[off+i*d] * K[K2+k];
-		}
-		out[off+x*d] = v;
-	}
-}
-
-void
-resampley(uchar **in, int off, int iny, uchar **out, int outy)
-{
-	int y, i, k;
-	double Y, yy, v, rat;
-
-	rat = (double)iny/(double)outy;
-	for(y=0; y<outy; y++){
-		if(iny == outy){
-			/* don't resample if size unchanged */
-			out[y][off] = in[y][off];
-			continue;
-		}
-		v = 0.0;
-		Y = y*rat;
-		for(k=-K2; k<=K2; k++){
-			yy = Y + rat*k/10.;
-			i = yy;
-			if(i < 0)
-				i = 0;
-			if(i >= iny)
-				i = iny-1;
-			v += in[i][off] * K[K2+k];
-		}
-		out[y][off] = v;
-	}
-
-}
-
-Image*
-resample(Image *from, Image *to)
-{
-	int i, j, bpl, nchan;
-	uchar **oscan, **nscan;
-	char tmp[20];
-	int xsize, ysize;
-	double v;
-	Image *t1, *t2;
-	ulong tchan;
-
-	for(i=-K2; i<=K2; i++){
-		K[K2+i] = kaiser(i/10., K2/10., 4.);
-	}
-
-	/* normalize */
-	v = 0.0;
-	for(i=0; i<NK; i++)
-		v += K[i];
-	for(i=0; i<NK; i++)
-		K[i] /= v;
-
-	switch(from->chan){
-	case GREY8:
-	case RGB24:
-	case RGBA32:
-	case ARGB32:
-	case XRGB32:
-		break;
-
-	case CMAP8:
-	case RGB15:
-	case RGB16:
-		tchan = RGB24;
-		goto Convert;
-
-	case GREY1:
-	case GREY2:
-	case GREY4:
-		tchan = GREY8;
-	Convert:
-		/* use library to convert to byte-per-chan form, then convert back */
-		t1 = xallocimage(display, Rect(0, 0, Dx(from->r), Dy(from->r)), tchan, 0, DNofill);
-		if(t1 == nil) {
-			fprint(2, "out of memory for temp image 1 in resample: %r\n");
-			wexits("memory");
-		}
-		drawop(t1, t1->r, from, nil, ZP, S);
-		t2 = xallocimage(display, to->r, tchan, 0, DNofill);
-		if(t2 == nil) {
-			fprint(2, "out of memory temp image 2 in resample: %r\n");
-			wexits("memory");
-		}
-		resample(t1, t2);
-		drawop(to, to->r, t2, nil, ZP, S);
-		freeimage(t1);
-		freeimage(t2);
-		return to;
-
-	default:
-		sysfatal("can't handle channel type %s", chantostr(tmp, from->chan));
-	}
-
-	xsize = Dx(to->r);
-	ysize = Dy(to->r);
-	oscan = malloc(Dy(from->r)*sizeof(uchar*));
-	nscan = malloc(max(ysize, Dy(from->r))*sizeof(uchar*));
-	if(oscan == nil || nscan == nil)
-		sysfatal("can't allocate: %r");
-
-	/* unload original image into scan lines */
-	bpl = bytesperline(from->r, from->depth);
-	for(i=0; i<Dy(from->r); i++){
-		oscan[i] = malloc(bpl);
-		if(oscan[i] == nil)
-			sysfatal("can't allocate: %r");
-		j = unloadimage(from, Rect(from->r.min.x, from->r.min.y+i, from->r.max.x, from->r.min.y+i+1), oscan[i], bpl);
-		if(j != bpl)
-			sysfatal("unloadimage");
-	}
-
-	/* allocate scan lines for destination. we do y first, so need at least Dy(from->r) lines */
-	bpl = bytesperline(Rect(0, 0, xsize, Dy(from->r)), from->depth);
-	for(i=0; i<max(ysize, Dy(from->r)); i++){
-		nscan[i] = malloc(bpl);
-		if(nscan[i] == nil)
-			sysfatal("can't allocate: %r");
-	}
-
-	/* resample in X */
-	nchan = from->depth/8;
-	for(i=0; i<Dy(from->r); i++){
-		for(j=0; j<nchan; j++){
-			if(j==0 && from->chan==XRGB32)
-				continue;
-			resamplex(oscan[i], j, nchan, Dx(from->r), nscan[i], xsize);
-		}
-		free(oscan[i]);
-		oscan[i] = nscan[i];
-		nscan[i] = malloc(bpl);
-		if(nscan[i] == nil)
-			sysfatal("can't allocate: %r");
-	}
-
-	/* resample in Y */
-	for(i=0; i<xsize; i++)
-		for(j=0; j<nchan; j++)
-			resampley(oscan, nchan*i+j, Dy(from->r), nscan, ysize);
-
-	/* pack data into destination */
-	bpl = bytesperline(to->r, from->depth);
-	for(i=0; i<ysize; i++){
-		j = loadimage(to, Rect(0, i, xsize, i+1), nscan[i], bpl);
-		if(j != bpl)
-			sysfatal("loadimage: %r");
-	}
-
-	for(i=0; i<Dy(from->r); i++){
-		free(oscan[i]);
-		free(nscan[i]);
-	}
-	free(oscan);
-	free(nscan);
-
-	return to;
-}
--- a/sys/src/cmd/page/util.c
+++ /dev/null
@@ -1,131 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <event.h>
-#include <bio.h>
-#include "page.h"
-
-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;
-}
-
-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;
-}
-
-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;
-}
-
-int
-opentemp(char *template)
-{
-	int fd, i;
-	char *p;
-
-	p = estrdup(template);
-	fd = -1;
-	for(i=0; i<10; i++){
-		mktemp(p);
-		if(access(p, 0) < 0 && (fd=create(p, ORDWR|ORCLOSE, 0400)) >= 0)
-			break;
-		strcpy(p, template);
-	}
-	if(fd < 0){
-		fprint(2, "couldn't make temporary file\n");
-		wexits("Ecreat");
-	}
-	strcpy(template, p);
-	free(p);
-
-	return fd;
-}
-
-/*
- * spool standard input to /tmp.
- * we've already read the initial in bytes into ibuf.
- */
-int
-spooltodisk(uchar *ibuf, int in, char **name)
-{
-	uchar buf[8192];
-	int fd, n;
-	char temp[40];
-
-	strcpy(temp, "/tmp/pagespoolXXXXXXXXX");
-	fd = opentemp(temp);
-	if(name)
-		*name = estrdup(temp);
-
-	if(write(fd, ibuf, in) != in){
-		fprint(2, "error writing temporary file\n");
-		wexits("write temp");
-	}
-
-	while((n = read(stdinfd, buf, sizeof buf)) > 0){
-		if(write(fd, buf, n) != n){
-			fprint(2, "error writing temporary file\n");
-			wexits("write temp0");
-		}
-	}
-	seek(fd, 0, 0);
-	return fd;
-}
-
-/*
- * spool standard input into a pipe.
- * we've already ready the first in bytes into ibuf
- */
-int
-stdinpipe(uchar *ibuf, int in)
-{
-	uchar buf[8192];
-	int n;
-	int p[2];
-	if(pipe(p) < 0){
-		fprint(2, "pipe fails: %r\n");	
-		wexits("pipe");
-	}
-
-	switch(rfork(RFMEM|RFPROC|RFFDG)){
-	case -1:
-		fprint(2, "fork fails: %r\n");
-		wexits("fork");
-	default:
-		close(p[1]);
-		return p[0];
-	case 0:
-		break;
-	}
-
-	close(p[0]);
-	write(p[1], ibuf, in);
-	while((n = read(stdinfd, buf, sizeof buf)) > 0)
-		write(p[1], buf, n);
-
-	_exits(0);
-	return -1;	/* not reached */
-}
--- a/sys/src/cmd/page/view.c
+++ /dev/null
@@ -1,1073 +1,0 @@
-/*
- * the actual viewer that handles screen stuff
- */
-
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include <cursor.h>
-#include <event.h>
-#include <bio.h>
-#include <plumb.h>
-#include <ctype.h>
-#include <keyboard.h>
-#include "page.h"
-
-Document *doc;
-Image *im;
-Image *tofree;
-int page;
-int angle = 0;
-int showbottom = 0;		/* on the next showpage, move the image so the bottom is visible. */
-
-Rectangle ulrange;	/* the upper left corner of the image must be in this rectangle */
-Point ul;			/* the upper left corner of the image is at this point on the screen */
-
-Point pclip(Point, Rectangle);
-Rectangle mkrange(Rectangle screenr, Rectangle imr);
-void redraw(Image*);
-
-Cursor reading={
-	{-1, -1},
-	{0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, 
-	 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, 
-	 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, 
-	 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
-	{0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, 
-	 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, 
-	 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, 
-	 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
-};
-
-Cursor query = {
-	{-7,-7},
-	{0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 
-	 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8, 
-	 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0, 
-	 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
-	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c, 
-	 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0, 
-	 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80, 
-	 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
-};
-
-enum {
-	Left = 1,
-	Middle = 2,
-	Right = 4,
-
-	RMenu = 3,
-};
-
-static void
-delayfreeimage(Image *m)
-{
-	if(m == tofree)
-		return;
-	if(tofree)
-		freeimage(tofree);
-	tofree = m;
-}
-
-void
-unhide(void)
-{
-	static int wctl = -1;
-
-	if(wctl < 0)
-		wctl = open("/dev/wctl", OWRITE);
-	if(wctl < 0)
-		return;
-
-	write(wctl, "unhide", 6);
-}
-
-int 
-max(int a, int b)
-{
-	return a > b ? a : b;
-}
-
-int 
-min(int a, int b)
-{
-	return a < b ? a : b;
-}
-
-
-char*
-menugen(int n)
-{
-	static char menustr[32];
-	char *p;
-	int len;
-
-	if(n == doc->npage)
-		return "exit";
-	if(n > doc->npage)
-		return nil;
-
-	if(reverse)
-		n = doc->npage-1-n;
-
-	p = doc->pagename(doc, n);
-	len = (sizeof menustr)-2;
-
-	if(strlen(p) > len && strrchr(p, '/'))
-		p = strrchr(p, '/')+1;
-	if(strlen(p) > len)
-		p = p+strlen(p)-len;
-
-	strcpy(menustr+1, p);
-	if(page == n)
-		menustr[0] = '>';
-	else
-		menustr[0] = ' ';
-	return menustr;
-}
-
-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);
-}
-
-char*
-writebitmap(void)
-{
-	char basename[64];
-	char name[64+30];
-	static char result[200];
-	char *p, *q;
-	int fd;
-
-	if(im == nil)
-		return "no image";
-
-	memset(basename, 0, sizeof basename);
-	if(doc->docname)
-		strncpy(basename, doc->docname, sizeof(basename)-1);
-	else if((p = menugen(page)) && p[0] != '\0')
-		strncpy(basename, p+1, sizeof(basename)-1);
-
-	if(basename[0]) {
-		if(q = strrchr(basename, '/'))
-			q++;
-		else
-			q = basename;
-		if(p = strchr(q, '.'))
-			*p = 0;
-		
-		memset(name, 0, sizeof name);
-		snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1);
-		if(access(name, 0) >= 0) {
-			strcat(name, "XXXX");
-			mktemp(name);
-		}
-		if(access(name, 0) >= 0)
-			return "couldn't think of a name for bitmap";
-	} else {
-		strcpy(name, "bitXXXX");
-		mktemp(name);
-		if(access(name, 0) >= 0) 
-			return "couldn't think of a name for bitmap";
-	}
-
-	if((fd = create(name, OWRITE, 0666)) < 0) {
-		snprint(result, sizeof result, "cannot create %s: %r", name);
-		return result;
-	}
-
-	if(writeimage(fd, im, 0) < 0) {
-		snprint(result, sizeof result, "cannot writeimage: %r");
-		close(fd);
-		return result;
-	}
-	close(fd);
-
-	snprint(result, sizeof result, "wrote %s", name);
-	return result;
-}
-
-static void translate(Point);
-
-static int
-showdata(Plumbmsg *msg)
-{
-	char *s;
-
-	s = plumblookup(msg->attr, "action");
-	return s && strcmp(s, "showdata")==0;
-}
-
-static int
-plumbquit(Plumbmsg *msg)
-{
-	char *s;
-
-	s = plumblookup(msg->attr, "action");
-	return s && strcmp(s, "quit")==0;
-}
-
-/* correspond to entries in miditems[] below,
- * changing one means you need to change
- */
-enum{
-	Restore = 0,
-	Zin,
-	Fit,
-	Rot,
-	Upside,
-	Empty1,
-	Next,
-	Prev,
-	Zerox,
-	Empty2,
-	Reverse,
-	Del,
-	Write,
-	Empty3,
-	Exit,
-};
- 
-void
-viewer(Document *dd)
-{
-	int i, fd, n, oldpage;
-	int nxt;
-	Menu menu, midmenu;
-	Mouse m;
-	Event e;
-	Point dxy, oxy, xy0;
-	Rectangle r;
-	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);
-	r.min = ZP;
-
-	/*
-	 * 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;
-
-	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;
-		}
-	}
-}
-
-Image *gray;
-
-/*
- * A draw operation that touches only the area contained in bot but not in top.
- * mp and sp get aligned with bot.min.
- */
-static void
-gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
-	Image *src, Point sp, Image *mask, Point mp, int op)
-{
-	Rectangle r;
-	Point origin;
-	Point delta;
-
-	USED(op);
-
-	if(Dx(bot)*Dy(bot) == 0)
-		return;
-
-	/* no points in bot - top */
-	if(rectinrect(bot, top))
-		return;
-
-	/* bot - top ≡ bot */
-	if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
-		gendrawop(dst, bot, src, sp, mask, mp, op);
-		return;
-	}
-
-	origin = bot.min;
-	/* split bot into rectangles that don't intersect top */
-	/* left side */
-	if(bot.min.x < top.min.x){
-		r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
-		delta = subpt(r.min, origin);
-		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
-		bot.min.x = top.min.x;
-	}
-
-	/* right side */
-	if(bot.max.x > top.max.x){
-		r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
-		delta = subpt(r.min, origin);
-		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
-		bot.max.x = top.max.x;
-	}
-
-	/* top */
-	if(bot.min.y < top.min.y){
-		r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
-		delta = subpt(r.min, origin);
-		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
-		bot.min.y = top.min.y;
-	}
-
-	/* bottom */
-	if(bot.max.y > top.max.y){
-		r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
-		delta = subpt(r.min, origin);
-		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
-		bot.max.y = top.max.y;
-	}
-}
-
-static void
-drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p, int op)
-{
-	gendrawdiff(dst, bot, top, src, p, mask, p, op);
-}
-
-/*
- * Translate the image in the window by delta.
- */
-static void
-translate(Point delta)
-{
-	Point u;
-	Rectangle r, or;
-
-	if(im == nil)
-		return;
-
-	u = pclip(addpt(ul, delta), ulrange);
-	delta = subpt(u, ul);
-	if(delta.x == 0 && delta.y == 0)
-		return;
-
-	/*
-	 * The upper left corner of the image is currently at ul.
-	 * We want to move it to u.
-	 */
-	or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul);
-	r = rectaddpt(or, delta);
-
-	drawop(screen, r, screen, nil, ul, S);
-	ul = u;
-
-	/* fill in gray where image used to be but isn't. */
-	drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP, S);
-
-	/* fill in black border */
-	drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP, S);
-
-	/* fill in image where it used to be off the screen. */
-	if(rectclip(&or, screen->r))
-		drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min, S);
-	else
-		drawop(screen, r, im, nil, im->r.min, S);
-	flushimage(display, 1);
-}
-
-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);	
-}
-
-void
-eresized(int new)
-{
-	Rectangle r;
-	r = screen->r;
-	if(new && getwindow(display, Refnone) < 0)
-		fprint(2,"can't reattach to window");
-	ul = addpt(ul, subpt(screen->r.min, r.min));
-	redraw(screen);
-}
-
-/* clip p to be in r */
-Point
-pclip(Point p, Rectangle r)
-{
-	if(p.x < r.min.x)
-		p.x = r.min.x;
-	else if(p.x >= r.max.x)
-		p.x = r.max.x-1;
-
-	if(p.y < r.min.y)
-		p.y = r.min.y;
-	else if(p.y >= r.max.y)
-		p.y = r.max.y-1;
-
-	return p;
-}
-
-/*
- * resize is perhaps a misnomer. 
- * this really just grows the window to be at least dx across
- * and dy high.  if the window hits the bottom or right edge,
- * it is backed up until it hits the top or left edge.
- */
-void
-resize(int dx, int dy)
-{
-	static Rectangle sr;
-	Rectangle r, or;
-
-	dx += 2*Borderwidth;
-	dy += 2*Borderwidth;
-	if(wctlfd < 0){
-		wctlfd = open("/dev/wctl", OWRITE);
-		if(wctlfd < 0)
-			return;
-	}
-
-	r = insetrect(screen->r, -Borderwidth);
-	if(Dx(r) >= dx && Dy(r) >= dy)
-		return;
-
-	if(Dx(sr)*Dy(sr) == 0)
-		sr = screenrect();
-
-	or = r;
-
-	r.max.x = max(r.min.x+dx, r.max.x);
-	r.max.y = max(r.min.y+dy, r.max.y);
-	if(r.max.x > sr.max.x){
-		if(Dx(r) > Dx(sr)){
-			r.min.x = 0;
-			r.max.x = sr.max.x;
-		}else
-			r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0));
-	}
-	if(r.max.y > sr.max.y){
-		if(Dy(r) > Dy(sr)){
-			r.min.y = 0;
-			r.max.y = sr.max.y;
-		}else
-			r = rectaddpt(r, Pt(0, sr.max.y-r.max.y));
-	}
-
-	/*
-	 * Sometimes we can't actually grow the window big enough,
-	 * and resizing it to the same shape makes it flash.
-	 */
-	if(Dx(r) == Dx(or) && Dy(r) == Dy(or))
-		return;
-
-	fprint(wctlfd, "resize -minx %d -miny %d -maxx %d -maxy %d\n",
-		r.min.x, r.min.y, r.max.x, r.max.y);
-}
-
-/*
- * If we allocimage after a resize but before flushing the draw buffer,
- * we won't have seen the reshape event, and we won't have called
- * getwindow, and allocimage will fail.  So we flushimage before every alloc.
- */
-Image*
-xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val)
-{
-	flushimage(display, 0);
-	return allocimage(d, r, chan, repl, val);
-}
-
-/* all code below this line should be in the library, but is stolen from colors instead */
-static char*
-rdenv(char *name)
-{
-	char *v;
-	int fd, size;
-
-	fd = open(name, OREAD);
-	if(fd < 0)
-		return 0;
-	size = seek(fd, 0, 2);
-	v = malloc(size+1);
-	if(v == 0){
-		fprint(2, "page: can't malloc: %r\n");
-		wexits("no mem");
-	}
-	seek(fd, 0, 0);
-	read(fd, v, size);
-	v[size] = 0;
-	close(fd);
-	return v;
-}
-
-void
-newwin(void)
-{
-	char *srv, *mntsrv;
-	char spec[100];
-	int srvfd, cons, pid;
-
-	switch(rfork(RFFDG|RFPROC|RFNAMEG|RFENVG|RFNOTEG|RFNOWAIT)){
-	case -1:
-		fprint(2, "page: can't fork: %r\n");
-		wexits("no fork");
-	case 0:
-		break;
-	default:
-		wexits(0);
-	}
-
-	srv = rdenv("/env/wsys");
-	if(srv == 0){
-		mntsrv = rdenv("/mnt/term/env/wsys");
-		if(mntsrv == 0){
-			fprint(2, "page: can't find $wsys\n");
-			wexits("srv");
-		}
-		srv = malloc(strlen(mntsrv)+10);
-		sprint(srv, "/mnt/term%s", mntsrv);
-		free(mntsrv);
-		pid  = 0;			/* can't send notes to remote processes! */
-	}else
-		pid = getpid();
-	srvfd = open(srv, ORDWR);
-	if(srvfd == -1){
-		fprint(2, "page: can't open %s: %r\n", srv);
-		wexits("no srv");
-	}
-	free(srv);
-	sprint(spec, "new -pid %d", pid);
-	if(mount(srvfd, -1, "/mnt/wsys", 0, spec) == -1){
-		fprint(2, "page: can't mount /mnt/wsys: %r (spec=%s)\n", spec);
-		wexits("no mount");
-	}
-	close(srvfd);
-	unmount("/mnt/acme", "/dev");
-	bind("/mnt/wsys", "/dev", MBEFORE);
-	cons = open("/dev/cons", OREAD);
-	if(cons==-1){
-	NoCons:
-		fprint(2, "page: can't open /dev/cons: %r");
-		wexits("no cons");
-	}
-	dup(cons, 0);
-	close(cons);
-	cons = open("/dev/cons", OWRITE);
-	if(cons==-1)
-		goto NoCons;
-	dup(cons, 1);
-	dup(cons, 2);
-	close(cons);
-//	wctlfd = open("/dev/wctl", OWRITE);
-}
-
-Rectangle
-screenrect(void)
-{
-	int fd;
-	char buf[12*5];
-
-	fd = open("/dev/screen", OREAD);
-	if(fd == -1)
-		fd=open("/mnt/term/dev/screen", OREAD);
-	if(fd == -1){
-		fprint(2, "page: can't open /dev/screen: %r\n");
-		wexits("window read");
-	}
-	if(read(fd, buf, sizeof buf) != sizeof buf){
-		fprint(2, "page: can't read /dev/screen: %r\n");
-		wexits("screen read");
-	}
-	close(fd);
-	return Rect(atoi(buf+12), atoi(buf+24), atoi(buf+36), atoi(buf+48));
-}
-
-void
-zerox(void)
-{
-	int pfd[2];
-
-	pipe(pfd);
-	switch(rfork(RFFDG|RFREND|RFPROC)) {
-		case -1:
-			wexits("cannot fork in zerox: %r");
-		case 0: 
-			dup(pfd[1], 0);
-			close(pfd[0]);
-			execl("/bin/page", "page", "-w", nil);
-			wexits("cannot exec in zerox: %r\n");
-		default:
-			close(pfd[1]);
-			writeimage(pfd[0], im, 0);
-			close(pfd[0]);
-			break;
-	}
-}