shithub: fork

Download patch

ref: 4f5e576a621998ccd52de0236eea9090c637caae
parent: 3fcd0f4232fd1fce99af6db0416b9b6471c1c441
author: qwx <qwx@sciops.net>
date: Sun Aug 13 23:11:30 EDT 2023

add fucking themed libdraw

--- /dev/null
+++ b/sys/include/draw.h
@@ -1,0 +1,537 @@
+#pragma src "/sys/src/libdraw"
+#pragma lib "libdraw.a"
+
+typedef struct	Cachefont Cachefont;
+typedef struct	Cacheinfo Cacheinfo;
+typedef struct	Cachesubf Cachesubf;
+typedef struct	Display Display;
+typedef struct	Font Font;
+typedef struct	Fontchar Fontchar;
+typedef struct	Image Image;
+typedef struct	Mouse Mouse;
+typedef struct	Point Point;
+typedef struct	Rectangle Rectangle;
+typedef struct	RGB RGB;
+typedef struct	Screen Screen;
+typedef struct	Subfont Subfont;
+typedef struct	Theme Theme;
+
+#pragma incomplete Mouse
+
+#pragma varargck	type	"R"	Rectangle
+#pragma varargck	type	"P"	Point
+extern	int	Rfmt(Fmt*);
+extern	int	Pfmt(Fmt*);
+
+enum
+{
+	DOpaque		= 0xFFFFFFFF,
+	DTransparent	= 0x00000000,		/* only useful for allocimage, memfillcolor */
+	DBlack		= 0x000000FF,
+	DWhite		= 0xFFFFFFFF,
+	DRed		= 0xFF0000FF,
+	DGreen		= 0x00FF00FF,
+	DBlue		= 0x0000FFFF,
+	DCyan		= 0x00FFFFFF,
+	DMagenta		= 0xFF00FFFF,
+	DYellow		= 0xFFFF00FF,
+	DPaleyellow	= 0xFFFFAAFF,
+	DDarkyellow	= 0xEEEE9EFF,
+	DDarkgreen	= 0x448844FF,
+	DPalegreen	= 0xAAFFAAFF,
+	DMedgreen	= 0x88CC88FF,
+	DDarkblue	= 0x000055FF,
+	DPalebluegreen= 0xAAFFFFFF,
+	DPaleblue		= 0x0000BBFF,
+	DBluegreen	= 0x008888FF,
+	DGreygreen	= 0x55AAAAFF,
+	DPalegreygreen	= 0x9EEEEEFF,
+	DYellowgreen	= 0x99994CFF,
+	DMedblue		= 0x000099FF,
+	DGreyblue	= 0x005DBBFF,
+	DPalegreyblue	= 0x4993DDFF,
+	DPurpleblue	= 0x8888CCFF,
+
+	DNotacolor	= 0xFFFFFF00,
+	DNofill		= DNotacolor,
+	
+};
+
+enum
+{
+	Displaybufsize	= 8000,
+	ICOSSCALE	= 1024,
+	Borderwidth =	4,
+};
+
+enum
+{
+	/* refresh methods */
+	Refbackup	= 0,
+	Refnone		= 1,
+	Refmesg		= 2
+};
+#define	NOREFRESH	((void*)-1)
+
+enum
+{
+	/* line ends */
+	Endsquare	= 0,
+	Enddisc		= 1,
+	Endarrow	= 2,
+	Endmask		= 0x1F
+};
+
+#define	ARROW(a, b, c)	(Endarrow|((a)<<5)|((b)<<14)|((c)<<23))
+
+typedef enum
+{
+	/* Porter-Duff compositing operators */
+	Clear	= 0,
+
+	SinD	= 8,
+	DinS	= 4,
+	SoutD	= 2,
+	DoutS	= 1,
+
+	S		= SinD|SoutD,
+	SoverD	= SinD|SoutD|DoutS,
+	SatopD	= SinD|DoutS,
+	SxorD	= SoutD|DoutS,
+
+	D		= DinS|DoutS,
+	DoverS	= DinS|DoutS|SoutD,
+	DatopS	= DinS|SoutD,
+	DxorS	= DoutS|SoutD,	/* == SxorD */
+
+	Ncomp = 12,
+} Drawop;
+
+/*
+ * image channel descriptors 
+ */
+enum {
+	CRed = 0,
+	CGreen,
+	CBlue,
+	CGrey,
+	CAlpha,
+	CMap,
+	CIgnore,
+	NChan,
+};
+
+#define __DC(type, nbits)	((((type)&15)<<4)|((nbits)&15))
+#define CHAN1(a,b)	__DC(a,b)
+#define CHAN2(a,b,c,d)	(CHAN1((a),(b))<<8|__DC((c),(d)))
+#define CHAN3(a,b,c,d,e,f)	(CHAN2((a),(b),(c),(d))<<8|__DC((e),(f)))
+#define CHAN4(a,b,c,d,e,f,g,h)	(CHAN3((a),(b),(c),(d),(e),(f))<<8|__DC((g),(h)))
+
+#define NBITS(c) ((c)&15)
+#define TYPE(c) (((c)>>4)&15)
+
+enum {
+	GREY1	= CHAN1(CGrey, 1),
+	GREY2	= CHAN1(CGrey, 2),
+	GREY4	= CHAN1(CGrey, 4),
+	GREY8	= CHAN1(CGrey, 8),
+	CMAP8	= CHAN1(CMap, 8),
+	RGB15	= CHAN4(CIgnore, 1, CRed, 5, CGreen, 5, CBlue, 5),
+	RGB16	= CHAN3(CRed, 5, CGreen, 6, CBlue, 5),
+	RGB24	= CHAN3(CRed, 8, CGreen, 8, CBlue, 8),
+	RGBA32	= CHAN4(CRed, 8, CGreen, 8, CBlue, 8, CAlpha, 8),
+	ARGB32	= CHAN4(CAlpha, 8, CRed, 8, CGreen, 8, CBlue, 8),	/* stupid VGAs */
+	XRGB32	= CHAN4(CIgnore, 8, CRed, 8, CGreen, 8, CBlue, 8),
+	BGR24	= CHAN3(CBlue, 8, CGreen, 8, CRed, 8),
+	ABGR32	= CHAN4(CAlpha, 8, CBlue, 8, CGreen, 8, CRed, 8),
+	XBGR32	= CHAN4(CIgnore, 8, CBlue, 8, CGreen, 8, CRed, 8),
+};
+
+extern	char*	chantostr(char*, ulong);
+extern	ulong	strtochan(char*);
+extern	int		chantodepth(ulong);
+
+struct	Point
+{
+	int	x;
+	int	y;
+};
+
+struct Rectangle
+{
+	Point	min;
+	Point	max;
+};
+
+typedef void	(*Reffn)(Image*, Rectangle, void*);
+
+struct Screen
+{
+	Display	*display;	/* display holding data */
+	int	id;		/* id of system-held Screen */
+	Image	*image;		/* unused; for reference only */
+	Image	*fill;		/* color to paint behind windows */
+};
+
+struct Display
+{
+	QLock		qlock;
+	int		locking;	/*program is using lockdisplay */
+	int		dirno;
+	int		fd;
+	int		reffd;
+	int		ctlfd;
+	int		imageid;
+	int		local;
+	void		(*error)(Display*, char*);
+	char		*devdir;
+	char		*windir;
+	char		oldlabel[64];
+	ulong		dataqid;
+	Image		*white;
+	Image		*black;
+	Image		*opaque;
+	Image		*transparent;
+	Image		*image;
+	uchar		*buf;
+	int		bufsize;
+	uchar		*bufp;
+	Font		*defaultfont;
+	Subfont		*defaultsubfont;
+	Image		*windows;
+	Image		*screenimage;
+	int		_isnewdisplay;
+};
+
+struct Image
+{
+	Display		*display;	/* display holding data */
+	int		id;		/* id of system-held Image */
+	Rectangle	r;		/* rectangle in data area, local coords */
+	Rectangle 	clipr;		/* clipping region */
+	int		depth;		/* number of bits per pixel */
+	ulong		chan;
+	int		repl;		/* flag: data replicates to tile clipr */
+	Screen		*screen;	/* 0 if not a window */
+	Image		*next;	/* next in list of windows */
+};
+
+struct RGB
+{
+	ulong	red;
+	ulong	green;
+	ulong	blue;
+};
+
+struct	Theme
+{
+	char		*name;
+	u32int		c;
+};
+
+/*
+ * Subfonts
+ *
+ * given char c, Subfont *f, Fontchar *i, and Point p, one says
+ *	i = f->info+c;
+ *	draw(b, Rect(p.x+i->left, p.y+i->top,
+ *		p.x+i->left+((i+1)->x-i->x), p.y+i->bottom),
+ *		color, f->bits, Pt(i->x, i->top));
+ *	p.x += i->width;
+ * to draw characters in the specified color (itself an Image) in Image b.
+ */
+
+struct	Fontchar
+{
+	int		x;		/* left edge of bits */
+	uchar		top;		/* first non-zero scan-line */
+	uchar		bottom;		/* last non-zero scan-line + 1 */
+	char		left;		/* offset of baseline */
+	uchar		width;		/* width of baseline */
+};
+
+struct	Subfont
+{
+	char		*name;
+	short		n;		/* number of chars in font */
+	uchar		height;		/* height of image */
+	char		ascent;		/* top of image to baseline */
+	Fontchar 	*info;		/* n+1 character descriptors */
+	Image		*bits;		/* of font */
+	int		ref;
+};
+
+enum
+{
+	/* starting values */
+	LOG2NFCACHE =	6,
+	NFCACHE =	(1<<LOG2NFCACHE),	/* #chars cached */
+	NFLOOK =	5,			/* #chars to scan in cache */
+	NFSUBF =	2,			/* #subfonts to cache */
+	/* max value */
+	MAXFCACHE =	1024+NFLOOK,		/* upper limit */
+	MAXSUBF =	50,			/* generous upper limit */
+	/* deltas */
+	DSUBF = 	4,
+	/* expiry ages */
+	SUBFAGE	=	10000,
+	CACHEAGE =	10000
+};
+
+struct Cachefont
+{
+	Rune		min;	/* lowest rune value to be taken from subfont */
+	Rune		max;	/* highest rune value+1 to be taken from subfont */
+	int		offset;	/* position in subfont of character at min */
+	char		*name;			/* stored in font */
+	char		*subfontname;		/* to access subfont */
+};
+
+struct Cacheinfo
+{
+	ushort		x;		/* left edge of bits */
+	uchar		width;		/* width of baseline */
+	schar		left;		/* offset of baseline */
+	Rune		value;	/* value of character at this slot in cache */
+	ushort		age;
+};
+
+struct Cachesubf
+{
+	ulong		age;	/* for replacement */
+	Cachefont	*cf;	/* font info that owns us */
+	Subfont		*f;	/* attached subfont */
+};
+
+struct Font
+{
+	char		*name;
+	Display		*display;
+	short		height;	/* max height of image, interline spacing */
+	short		ascent;	/* top of image to baseline */
+	short		width;	/* widest so far; used in caching only */	
+	short		nsub;	/* number of subfonts */
+	ulong		age;	/* increasing counter; used for LRU */
+	int		maxdepth;	/* maximum depth of all loaded subfonts */
+	int		ncache;	/* size of cache */
+	int		nsubf;	/* size of subfont list */
+	Cacheinfo	*cache;
+	Cachesubf	*subf;
+	Cachefont	**sub;	/* as read from file */
+	Image		*cacheimage;
+};
+
+#define	Dx(r)	((r).max.x-(r).min.x)
+#define	Dy(r)	((r).max.y-(r).min.y)
+
+/*
+ * One of a kind
+ */
+extern int		mousescrollsize(int);
+
+/*
+ * Image management
+ */
+extern Image*	_allocimage(Image*, Display*, Rectangle, ulong, int, ulong, int, int);
+extern Image*	allocimage(Display*, Rectangle, ulong, int, ulong);
+extern uchar*	bufimage(Display*, int);
+extern int	bytesperline(Rectangle, int);
+extern void	closedisplay(Display*);
+extern void	drawerror(Display*, char*);
+extern int	flushimage(Display*, int);
+extern int	freeimage(Image*);
+extern int	_freeimage1(Image*);
+extern int	geninitdraw(char*, void(*)(Display*, char*), char*, char*, char*, int);
+extern int	initdraw(void(*)(Display*, char*), char*, char*);
+extern int	newwindow(char*);
+extern Display*	initdisplay(char*, char*, void(*)(Display*, char*));
+extern int	loadimage(Image*, Rectangle, uchar*, int);
+extern int	cloadimage(Image*, Rectangle, uchar*, int);
+extern int	getwindow(Display*, int);
+extern int	gengetwindow(Display*, char*, Image**, Screen**, int);
+extern Image* readimage(Display*, int, int);
+extern Image* creadimage(Display*, int, int);
+extern int	unloadimage(Image*, Rectangle, uchar*, int);
+extern int	wordsperline(Rectangle, int);
+extern int	writeimage(int, Image*, int);
+extern Image*	namedimage(Display*, char*);
+extern int	nameimage(Image*, char*, int);
+extern Image* allocimagemix(Display*, ulong, ulong);
+
+/*
+ * Colors
+ */
+extern	void	readcolmap(Display*, RGB*);
+extern	void	writecolmap(Display*, RGB*);
+extern	ulong	setalpha(ulong, uchar);
+
+/*
+ * Bullshit
+ */
+extern	int	readtheme(Theme*, int, char*);
+
+/*
+ * Windows
+ */
+extern Screen*	allocscreen(Image*, Image*, int);
+extern Image*	_allocwindow(Image*, Screen*, Rectangle, int, ulong);
+extern Image*	allocwindow(Screen*, Rectangle, int, ulong);
+extern void	bottomnwindows(Image**, int);
+extern void	bottomwindow(Image*);
+extern int	freescreen(Screen*);
+extern Screen*	publicscreen(Display*, int, ulong);
+extern void	topnwindows(Image**, int);
+extern void	topwindow(Image*);
+extern int	originwindow(Image*, Point, Point);
+
+/*
+ * Geometry
+ */
+extern Point		Pt(int, int);
+extern Rectangle	Rect(int, int, int, int);
+extern Rectangle	Rpt(Point, Point);
+extern Point		addpt(Point, Point);
+extern Point		subpt(Point, Point);
+extern Point		divpt(Point, int);
+extern Point		mulpt(Point, int);
+extern int		eqpt(Point, Point);
+extern int		eqrect(Rectangle, Rectangle);
+extern Rectangle	insetrect(Rectangle, int);
+extern Rectangle	rectaddpt(Rectangle, Point);
+extern Rectangle	rectsubpt(Rectangle, Point);
+extern Rectangle	canonrect(Rectangle);
+extern int		rectXrect(Rectangle, Rectangle);
+extern int		rectinrect(Rectangle, Rectangle);
+extern void		combinerect(Rectangle*, Rectangle);
+extern int		rectclip(Rectangle*, Rectangle);
+extern int		ptinrect(Point, Rectangle);
+extern void		replclipr(Image*, int, Rectangle);
+extern int		drawreplxy(int, int, int);	/* used to be drawsetxy */
+extern Point	drawrepl(Rectangle, Point);
+extern int		rgb2cmap(int, int, int);
+extern int		cmap2rgb(int);
+extern int		cmap2rgba(int);
+extern void		icossin(int, int*, int*);
+extern void		icossin2(int, int, int*, int*);
+extern int		badrect(Rectangle);
+
+/*
+ * Graphics
+ */
+extern void	draw(Image*, Rectangle, Image*, Image*, Point);
+extern void	drawop(Image*, Rectangle, Image*, Image*, Point, Drawop);
+extern void	gendraw(Image*, Rectangle, Image*, Point, Image*, Point);
+extern void	gendrawop(Image*, Rectangle, Image*, Point, Image*, Point, Drawop);
+extern void	line(Image*, Point, Point, int, int, int, Image*, Point);
+extern void	lineop(Image*, Point, Point, int, int, int, Image*, Point, Drawop);
+extern void	poly(Image*, Point*, int, int, int, int, Image*, Point);
+extern void	polyop(Image*, Point*, int, int, int, int, Image*, Point, Drawop);
+extern void	fillpoly(Image*, Point*, int, int, Image*, Point);
+extern void	fillpolyop(Image*, Point*, int, int, Image*, Point, Drawop);
+extern Point	string(Image*, Point, Image*, Point, Font*, char*);
+extern Point	stringop(Image*, Point, Image*, Point, Font*, char*, Drawop);
+extern Point	stringn(Image*, Point, Image*, Point, Font*, char*, int);
+extern Point	stringnop(Image*, Point, Image*, Point, Font*, char*, int, Drawop);
+extern Point	runestring(Image*, Point, Image*, Point, Font*, Rune*);
+extern Point	runestringop(Image*, Point, Image*, Point, Font*, Rune*, Drawop);
+extern Point	runestringn(Image*, Point, Image*, Point, Font*, Rune*, int);
+extern Point	runestringnop(Image*, Point, Image*, Point, Font*, Rune*, int, Drawop);
+extern Point	stringbg(Image*, Point, Image*, Point, Font*, char*, Image*, Point);
+extern Point	stringbgop(Image*, Point, Image*, Point, Font*, char*, Image*, Point, Drawop);
+extern Point	stringnbg(Image*, Point, Image*, Point, Font*, char*, int, Image*, Point);
+extern Point	stringnbgop(Image*, Point, Image*, Point, Font*, char*, int, Image*, Point, Drawop);
+extern Point	runestringbg(Image*, Point, Image*, Point, Font*, Rune*, Image*, Point);
+extern Point	runestringbgop(Image*, Point, Image*, Point, Font*, Rune*, Image*, Point, Drawop);
+extern Point	runestringnbg(Image*, Point, Image*, Point, Font*, Rune*, int, Image*, Point);
+extern Point	runestringnbgop(Image*, Point, Image*, Point, Font*, Rune*, int, Image*, Point, Drawop);
+extern Point	_string(Image*, Point, Image*, Point, Font*, char*, Rune*, int, Rectangle, Image*, Point, Drawop);
+extern Point	stringsubfont(Image*, Point, Image*, Subfont*, char*);
+extern int		bezier(Image*, Point, Point, Point, Point, int, int, int, Image*, Point);
+extern int		bezierop(Image*, Point, Point, Point, Point, int, int, int, Image*, Point, Drawop);
+extern int		bezierpts(Point, Point, Point, Point, Point**);
+extern int		bezspline(Image*, Point*, int, int, int, int, Image*, Point);
+extern int		bezsplineop(Image*, Point*, int, int, int, int, Image*, Point, Drawop);
+extern int		bezsplinepts(Point*, int, Point**);
+extern int		fillbezier(Image*, Point, Point, Point, Point, int, Image*, Point);
+extern int		fillbezierop(Image*, Point, Point, Point, Point, int, Image*, Point, Drawop);
+extern int		fillbezspline(Image*, Point*, int, int, Image*, Point);
+extern int		fillbezsplineop(Image*, Point*, int, int, Image*, Point, Drawop);
+extern void	ellipse(Image*, Point, int, int, int, Image*, Point);
+extern void	ellipseop(Image*, Point, int, int, int, Image*, Point, Drawop);
+extern void	fillellipse(Image*, Point, int, int, Image*, Point);
+extern void	fillellipseop(Image*, Point, int, int, Image*, Point, Drawop);
+extern void	arc(Image*, Point, int, int, int, Image*, Point, int, int);
+extern void	arcop(Image*, Point, int, int, int, Image*, Point, int, int, Drawop);
+extern void	fillarc(Image*, Point, int, int, Image*, Point, int, int);
+extern void	fillarcop(Image*, Point, int, int, Image*, Point, int, int, Drawop);
+extern void	border(Image*, Rectangle, int, Image*, Point);
+extern void	borderop(Image*, Rectangle, int, Image*, Point, Drawop);
+
+/*
+ * Font management
+ */
+extern Font*	openfont(Display*, char*);
+extern Font*	buildfont(Display*, char*, char*);
+extern void	freefont(Font*);
+extern Font*	mkfont(Subfont*, Rune);
+extern int	cachechars(Font*, char**, Rune**, ushort*, int, int*, char**);
+extern void	agefont(Font*);
+extern Subfont*	allocsubfont(char*, int, int, int, Fontchar*, Image*);
+extern Subfont*	lookupsubfont(Display*, char*);
+extern void	installsubfont(char*, Subfont*);
+extern void	uninstallsubfont(Subfont*);
+extern void	freesubfont(Subfont*);
+extern Subfont*	readsubfont(Display*, char*, int, int);
+extern Subfont*	readsubfonti(Display*, char*, int, Image*, int);
+extern int	writesubfont(int, Subfont*);
+extern void	_unpackinfo(Fontchar*, uchar*, int);
+extern Point	stringsize(Font*, char*);
+extern int	stringwidth(Font*, char*);
+extern int	stringnwidth(Font*, char*, int);
+extern Point	runestringsize(Font*, Rune*);
+extern int	runestringwidth(Font*, Rune*);
+extern int	runestringnwidth(Font*, Rune*, int);
+extern Point	strsubfontwidth(Subfont*, char*);
+extern int	loadchar(Font*, Rune, Cacheinfo*, int, int, char**);
+extern char*	subfontname(char*, char*, int);
+extern Subfont*	_getsubfont(Display*, char*);
+extern Subfont*	getdefont(Display*);
+extern void		lockdisplay(Display*);
+extern void	unlockdisplay(Display*);
+
+/*
+ * Predefined 
+ */
+extern	uchar	defontdata[];
+extern	int		sizeofdefont;
+extern	Point		ZP;
+extern	Rectangle	ZR;
+
+/*
+ * Set up by initdraw()
+ */
+extern	Display	*display;
+extern	Font		*font;
+extern	Image	*screen;
+extern	Screen	*_screen;
+extern	int	_cursorfd;
+extern	void	_setdrawop(Display*, Drawop);
+
+#define	BGSHORT(p)	((p)[0]|((p)[1]<<8))
+#define	BGLONG(p)	((p)[0]|((p)[1]<<8)|((p)[2]<<16)|((p)[3]<<24))
+#define BPSHORT(p,v)	do{ushort _v_=(v);(p)[0]=_v_;(p)[1]=_v_>>8;}while(0)
+#define BPLONG(p,v)	do{ulong _v_=(v);(p)[0]=_v_;(p)[1]=_v_>>8;(p)[2]=_v_>>16;(p)[3]=_v_>>24;}while(0)
+
+/*
+ * Compressed image file parameters and helper routines
+ */
+#define	NMATCH	3		/* shortest match possible */
+#define	NRUN	(NMATCH+31)	/* longest match possible */
+#define	NMEM	1024		/* window size */
+#define	NDUMP	128		/* maximum length of dump */
+#define	NCBLOCK	6000		/* size of compressed blocks */
+extern	void	_twiddlecompressed(uchar*, int);
+extern	int	_compblocksize(Rectangle, int);
+
+extern	ulong	drawld2chan[];
+extern	void	drawsetdebug(int);
--- /dev/null
+++ b/sys/src/libdraw/alloc.c
@@ -1,0 +1,235 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Image*
+allocimage(Display *d, Rectangle r, ulong chan, int repl, ulong col)
+{
+	Image *i;
+
+	i = _allocimage(nil, d, r, chan, repl, col, 0, 0);
+	if(i != nil)
+		setmalloctag(i, getcallerpc(&d));
+	return i;
+}
+
+Image*
+_allocimage(Image *ai, Display *d, Rectangle r, ulong chan, int repl, ulong col, int screenid, int refresh)
+{
+	uchar *a;
+	char *err;
+	Image *i;
+	Rectangle clipr;
+	int id;
+	int depth;
+
+	err = nil;
+	i = nil;
+
+	if(badrect(r)){
+		werrstr("bad rectangle");
+		return nil;
+	}
+	if(chan == 0){
+		werrstr("bad channel descriptor");
+		return nil;
+	}
+
+	depth = chantodepth(chan);
+	if(depth == 0){
+		err = "bad channel descriptor";
+    Error:
+		if(err != nil)
+			werrstr("allocimage: %s", err);
+		else
+			werrstr("allocimage: %r");
+		free(i);
+		return nil;
+	}
+
+	a = bufimage(d, 1+4+4+1+4+1+4*4+4*4+4);
+	if(a == nil)
+		goto Error;
+	d->imageid++;
+	id = d->imageid;
+	a[0] = 'b';
+	BPLONG(a+1, id);
+	BPLONG(a+5, screenid);
+	a[9] = refresh;
+	BPLONG(a+10, chan);
+	a[14] = repl;
+	BPLONG(a+15, r.min.x);
+	BPLONG(a+19, r.min.y);
+	BPLONG(a+23, r.max.x);
+	BPLONG(a+27, r.max.y);
+	if(repl)
+		/* huge but not infinite, so various offsets will leave it huge, not overflow */
+		clipr = Rect(-0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF);
+	else
+		clipr = r;
+	BPLONG(a+31, clipr.min.x);
+	BPLONG(a+35, clipr.min.y);
+	BPLONG(a+39, clipr.max.x);
+	BPLONG(a+43, clipr.max.y);
+	BPLONG(a+47, col);
+
+	if(ai != nil)
+		i = ai;
+	else{
+		i = malloc(sizeof(Image));
+		if(i == nil){
+			a = bufimage(d, 1+4);
+			if(a != nil){
+				a[0] = 'f';
+				BPLONG(a+1, id);
+				flushimage(d, 0);
+			}
+			goto Error;
+		}
+	}
+	i->display = d;
+	i->id = id;
+	i->depth = depth;
+	i->chan = chan;
+	i->r = r;
+	i->clipr = clipr;
+	i->repl = repl;
+	i->screen = nil;
+	i->next = nil;
+	return i;
+}
+
+Image*
+namedimage(Display *d, char *name)
+{
+	uchar *a;
+	char *err, buf[12*12+1];
+	Image *i;
+	int id, n;
+	ulong chan;
+
+	err = nil;
+	i = nil;
+
+	n = strlen(name);
+	if(n >= 256){
+		err = "name too long";
+    Error:
+		if(err != nil)
+			werrstr("namedimage: %s", err);
+		else
+			werrstr("namedimage: %r");
+		free(i);
+		return nil;
+	}
+	/* flush pending data so we don't get error allocating the image */
+	flushimage(d, 0);
+	a = bufimage(d, 1+4+1+n);
+	if(a == nil)
+		goto Error;
+	d->imageid++;
+	id = d->imageid;
+	a[0] = 'n';
+	BPLONG(a+1, id);
+	a[5] = n;
+	memmove(a+6, name, n);
+	if(flushimage(d, 0) < 0)
+		goto Error;
+
+	if(pread(d->ctlfd, buf, sizeof buf, 0) < 12*12)
+		goto Error;
+	buf[12*12] = '\0';
+
+	i = malloc(sizeof(Image));
+	if(i == nil){
+	Error1:
+		a = bufimage(d, 1+4);
+		if(a != nil){
+			a[0] = 'f';
+			BPLONG(a+1, id);
+			flushimage(d, 0);
+		}
+		goto Error;
+	}
+	i->display = d;
+	i->id = id;
+	if((chan=strtochan(buf+2*12))==0){
+		werrstr("bad channel '%.12s' from devdraw", buf+2*12);
+		goto Error1;
+	}
+	i->chan = chan;
+	i->depth = chantodepth(chan);
+	i->repl = atoi(buf+3*12);
+	i->r.min.x = atoi(buf+4*12);
+	i->r.min.y = atoi(buf+5*12);
+	i->r.max.x = atoi(buf+6*12);
+	i->r.max.y = atoi(buf+7*12);
+	i->clipr.min.x = atoi(buf+8*12);
+	i->clipr.min.y = atoi(buf+9*12);
+	i->clipr.max.x = atoi(buf+10*12);
+	i->clipr.max.y = atoi(buf+11*12);
+	i->screen = nil;
+	i->next = nil;
+	return i;
+}
+
+int
+nameimage(Image *i, char *name, int in)
+{
+	uchar *a;
+	int n;
+
+	n = strlen(name);
+	a = bufimage(i->display, 1+4+1+1+n);
+	if(a == nil)
+		return 0;
+	a[0] = 'N';
+	BPLONG(a+1, i->id);
+	a[5] = in;
+	a[6] = n;
+	memmove(a+7, name, n);
+	if(flushimage(i->display, 0) < 0)
+		return 0;
+	return 1;
+}
+
+int
+_freeimage1(Image *i)
+{
+	uchar *a;
+	Display *d;
+	Image *w;
+
+	if(i == nil || i->display == nil)
+		return 0;
+	d = i->display;
+	if(i->screen != nil){
+		w = d->windows;
+		if(w == i)
+			d->windows = i->next;
+		else
+			while(w != nil){
+				if(w->next == i){
+					w->next = i->next;
+					break;
+				}
+				w = w->next;
+			}
+	}
+	a = bufimage(d, 1+4);
+	if(a == nil)
+		return -1;
+	a[0] = 'f';
+	BPLONG(a+1, i->id);
+	return 0;
+}
+
+int
+freeimage(Image *i)
+{
+	int ret;
+
+	ret = _freeimage1(i);
+	free(i);
+	return ret;
+}
--- /dev/null
+++ b/sys/src/libdraw/allocimagemix.c
@@ -1,0 +1,43 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Image*
+allocimagemix(Display *d, ulong color1, ulong color3)
+{
+	Image *t, *b;
+	static Image *qmask;
+
+	if(qmask == nil)
+		qmask = allocimage(d, Rect(0,0,1,1), GREY8, 1, 0x3F3F3FFF);
+		
+	if(d->screenimage->depth <= 8){	/* create a 2×2 texture */
+		t = allocimage(d, Rect(0,0,1,1), d->screenimage->chan, 0, color1);
+		if(t == nil)
+			return nil;
+
+		b = allocimage(d, Rect(0,0,2,2), d->screenimage->chan, 1, color3);
+		if(b == nil){
+			freeimage(t);
+			return nil;
+		}
+
+		draw(b, Rect(0,0,1,1), t, nil, ZP);
+		freeimage(t);
+		return b;
+	}else{	/* use a solid color, blended using alpha */
+		t = allocimage(d, Rect(0,0,1,1), d->screenimage->chan, 1, color1);
+		if(t == nil)
+			return nil;
+
+		b = allocimage(d, Rect(0,0,1,1), d->screenimage->chan, 1, color3);
+		if(b == nil){
+			freeimage(t);
+			return nil;
+		}
+
+		draw(b, b->r, t, qmask, ZP);
+		freeimage(t);
+		return b;
+	}
+}
--- /dev/null
+++ b/sys/src/libdraw/arith.c
@@ -1,0 +1,185 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Point
+Pt(int x, int y)
+{
+	Point p;
+
+	p.x = x;
+	p.y = y;
+	return p;
+}
+
+Rectangle
+Rect(int x, int y, int bx, int by)
+{
+	Rectangle r;
+
+	r.min.x = x;
+	r.min.y = y;
+	r.max.x = bx;
+	r.max.y = by;
+	return r;
+}
+
+Rectangle
+Rpt(Point min, Point max)
+{
+	Rectangle r;
+
+	r.min = min;
+	r.max = max;
+	return r;
+}
+
+Point
+addpt(Point a, Point b)
+{
+	a.x += b.x;
+	a.y += b.y;
+	return a;
+}
+
+Point
+subpt(Point a, Point b)
+{
+	a.x -= b.x;
+	a.y -= b.y;
+	return a;
+}
+
+Rectangle
+insetrect(Rectangle r, int n)
+{
+	r.min.x += n;
+	r.min.y += n;
+	r.max.x -= n;
+	r.max.y -= n;
+	return r;
+}
+
+Point
+divpt(Point a, int b)
+{
+	a.x /= b;
+	a.y /= b;
+	return a;
+}
+
+Point
+mulpt(Point a, int b)
+{
+	a.x *= b;
+	a.y *= b;
+	return a;
+}
+
+Rectangle
+rectsubpt(Rectangle r, Point p)
+{
+	r.min.x -= p.x;
+	r.min.y -= p.y;
+	r.max.x -= p.x;
+	r.max.y -= p.y;
+	return r;
+}
+
+Rectangle
+rectaddpt(Rectangle r, Point p)
+{
+	r.min.x += p.x;
+	r.min.y += p.y;
+	r.max.x += p.x;
+	r.max.y += p.y;
+	return r;
+}
+
+int
+eqpt(Point p, Point q)
+{
+	return p.x==q.x && p.y==q.y;
+}
+
+int
+eqrect(Rectangle r, Rectangle s)
+{
+	return r.min.x==s.min.x && r.max.x==s.max.x &&
+	       r.min.y==s.min.y && r.max.y==s.max.y;
+}
+
+int
+rectXrect(Rectangle r, Rectangle s)
+{
+	return r.min.x<s.max.x && s.min.x<r.max.x &&
+	       r.min.y<s.max.y && s.min.y<r.max.y;
+}
+
+int
+rectinrect(Rectangle r, Rectangle s)
+{
+	return s.min.x<=r.min.x && r.max.x<=s.max.x && s.min.y<=r.min.y && r.max.y<=s.max.y;
+}
+
+int
+ptinrect(Point p, Rectangle r)
+{
+	return p.x>=r.min.x && p.x<r.max.x &&
+	       p.y>=r.min.y && p.y<r.max.y;
+}
+
+Rectangle
+canonrect(Rectangle r)
+{
+	int t;
+	if (r.max.x < r.min.x) {
+		t = r.min.x;
+		r.min.x = r.max.x;
+		r.max.x = t;
+	}
+	if (r.max.y < r.min.y) {
+		t = r.min.y;
+		r.min.y = r.max.y;
+		r.max.y = t;
+	}
+	return r;
+}
+
+void
+combinerect(Rectangle *r1, Rectangle r2)
+{
+	if(r1->min.x > r2.min.x)
+		r1->min.x = r2.min.x;
+	if(r1->min.y > r2.min.y)
+		r1->min.y = r2.min.y;
+	if(r1->max.x < r2.max.x)
+		r1->max.x = r2.max.x;
+	if(r1->max.y < r2.max.y)
+		r1->max.y = r2.max.y;
+}
+
+ulong drawld2chan[] = {
+	GREY1,
+	GREY2,
+	GREY4,
+	CMAP8,
+};
+
+ulong
+setalpha(ulong color, uchar alpha)
+{
+	int red, green, blue;
+
+	red = (color >> 3*8) & 0xFF;
+	green = (color >> 2*8) & 0xFF;
+	blue = (color >> 1*8) & 0xFF;
+	/* ignore incoming alpha */
+	red = (red * alpha)/255;
+	green = (green * alpha)/255;
+	blue = (blue * alpha)/255;
+	return (red<<3*8) | (green<<2*8) | (blue<<1*8) | (alpha<<0*8);
+}
+
+Point	ZP;
+Rectangle ZR;
--- /dev/null
+++ b/sys/src/libdraw/badrect.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * check for zero, negative size or insanely huge rectangle.
+ */
+int
+badrect(Rectangle r)
+{
+	int x, y;
+	uint z;
+
+	x = Dx(r);
+	y = Dy(r);
+	if(x > 0 && y > 0){
+		z = x*y;
+		if(z/x == y && z < 0x10000000)
+			return 0;
+	}
+	return 1;
+}
--- /dev/null
+++ b/sys/src/libdraw/bezier.c
@@ -1,0 +1,255 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+#define	PINC	32		/* realloc granularity */
+
+typedef struct Plist Plist;
+struct Plist
+{
+	Point *p;
+	int np;			/* -1 if malloc/realloc failed */
+};
+
+static void
+appendpt(Plist *l, Point p)
+{
+	if(l->np == -1)
+		return;
+	if(l->np == 0)
+		l->p = malloc(PINC*sizeof(Point));
+	else if(l->np%PINC == 0)
+		l->p = realloc(l->p, (l->np+PINC)*sizeof(Point));
+	if(l->p == 0){
+		l->np = -1;
+		return;
+	}
+	l->p[l->np++] = p;
+}
+
+static int
+normsq(Point p)
+{
+	return p.x*p.x+p.y*p.y;
+}
+
+static int
+psdist(Point p, Point a, Point b)
+{
+	int num, den;
+
+	p = subpt(p, a);
+	b = subpt(b, a);
+	num = p.x*b.x + p.y*b.y;
+	if(num <= 0)
+		return normsq(p);
+	den = normsq(b);
+	if(num >= den)
+		return normsq(subpt(b, p));
+	return normsq(subpt(divpt(mulpt(b, num), den), p));
+}
+
+/*
+ * Convert cubic Bezier curve control points to polyline
+ * vertices.  Leaves the last vertex off, so you can continue
+ * with another curve.
+ */
+static void
+bpts1(Plist *l, Point p0, Point p1, Point p2, Point p3, int scale)
+{
+	Point p01, p12, p23, p012, p123, p0123;
+	Point tp0, tp1, tp2, tp3;
+	tp0=divpt(p0, scale);
+	tp1=divpt(p1, scale);
+	tp2=divpt(p2, scale);
+	tp3=divpt(p3, scale);
+	if(psdist(tp1, tp0, tp3)<=1 && psdist(tp2, tp0, tp3)<=1){
+		appendpt(l, tp0);
+		appendpt(l, tp1);
+		appendpt(l, tp2);
+	}
+	else{
+		/*
+		 * if scale factor is getting too big for comfort,
+		 * rescale now & concede the rounding error
+		 */
+		if(scale>(1<<12)){
+			p0=tp0;
+			p1=tp1;
+			p2=tp2;
+			p3=tp3;
+			scale=1;
+		}
+		p01=addpt(p0, p1);
+		p12=addpt(p1, p2);
+		p23=addpt(p2, p3);
+		p012=addpt(p01, p12);
+		p123=addpt(p12, p23);
+		p0123=addpt(p012, p123);
+		bpts1(l, mulpt(p0, 8), mulpt(p01, 4), mulpt(p012, 2), p0123, scale*8);
+		bpts1(l, p0123, mulpt(p123, 2), mulpt(p23, 4), mulpt(p3, 8), scale*8);
+	}
+}
+
+static void
+bpts(Plist *l, Point p0, Point p1, Point p2, Point p3)
+{
+	bpts1(l, p0, p1, p2, p3, 1);
+}
+
+static void
+_bezierpts(Plist *l, Point p0, Point p1, Point p2, Point p3)
+{
+	bpts(l, p0, p1, p2, p3);
+	appendpt(l, p3);
+}
+
+int
+bezierpts(Point p0, Point p1, Point p2, Point p3, Point **pp)
+{
+	Plist l;
+	l.p = nil;
+	l.np = 0;
+	_bezierpts(&l, p0, p1, p2, p3);
+	*pp = l.p;
+	return l.np;
+}
+
+static void
+_bezsplinepts(Plist *l, Point *pt, int npt)
+{
+	Point *p, *ep;
+	Point a, b, c, d;
+	int periodic;
+
+	if(npt<3)
+		return;
+	ep = &pt[npt-3];
+	periodic = eqpt(pt[0], ep[2]);
+	if(periodic){
+		a = divpt(addpt(ep[1], pt[0]), 2);
+		b = divpt(addpt(ep[1], mulpt(pt[0], 5)), 6);
+		c = divpt(addpt(mulpt(pt[0], 5), pt[1]), 6);
+		d = divpt(addpt(pt[0], pt[1]), 2);
+		bpts(l, a, b, c, d);
+	}
+	for(p=pt; p<=ep; p++){
+		if(p==pt && !periodic){
+			a = p[0];
+			b = divpt(addpt(p[0], mulpt(p[1], 2)), 3);
+		}
+		else{
+			a = divpt(addpt(p[0], p[1]), 2);
+			b = divpt(addpt(p[0], mulpt(p[1], 5)), 6);
+		}
+		if(p==ep && !periodic){
+			c = divpt(addpt(mulpt(p[1], 2), p[2]), 3);
+			d = p[2];
+		}
+		else{
+			c = divpt(addpt(mulpt(p[1], 5), p[2]), 6);
+			d = divpt(addpt(p[1], p[2]), 2);
+		}
+		bpts(l, a, b, c, d);
+	}
+	appendpt(l, d);
+}
+
+int
+bezsplinepts(Point *pt, int npt, Point **pp)
+{
+	Plist l;
+	l.np = 0;
+	l.p = nil;
+	_bezsplinepts(&l, pt, npt);
+	*pp  = l.p;
+	return l.np;
+}
+
+int
+bezier(Image *dst, Point p0, Point p1, Point p2, Point p3, int end0, int end1, int radius, Image *src, Point sp)
+{
+	return bezierop(dst, p0, p1, p2, p3, end0, end1, radius, src, sp, SoverD);
+}
+
+int
+bezierop(Image *dst, Point p0, Point p1, Point p2, Point p3, int end0, int end1, int radius, Image *src, Point sp, Drawop op)
+{
+	Plist l;
+
+	l.np = 0;
+	_bezierpts(&l, p0, p1, p2, p3);
+	if(l.np == -1)
+		return 0;
+	if(l.np != 0){
+		polyop(dst, l.p, l.np, end0, end1, radius, src, addpt(subpt(sp, p0), l.p[0]), op);
+		free(l.p);
+	}
+	return 1;
+}
+
+int
+bezspline(Image *dst, Point *pt, int npt, int end0, int end1, int radius, Image *src, Point sp)
+{
+	return bezsplineop(dst, pt, npt, end0, end1, radius, src, sp, SoverD);
+}
+
+int
+bezsplineop(Image *dst, Point *pt, int npt, int end0, int end1, int radius, Image *src, Point sp, Drawop op)
+{
+	Plist l;
+
+	l.np = 0;
+	_bezsplinepts(&l, pt, npt);
+	if(l.np==-1)
+		return 0;
+	if(l.np != 0){
+		polyop(dst, l.p, l.np, end0, end1, radius, src, addpt(subpt(sp, pt[0]), l.p[0]), op);
+		free(l.p);
+	}
+	return 1;
+}
+
+int
+fillbezier(Image *dst, Point p0, Point p1, Point p2, Point p3, int w, Image *src, Point sp)
+{
+	return fillbezierop(dst, p0, p1, p2, p3, w, src, sp, SoverD);
+}
+
+int
+fillbezierop(Image *dst, Point p0, Point p1, Point p2, Point p3, int w, Image *src, Point sp, Drawop op)
+{
+	Plist l;
+
+	l.np = 0;
+	_bezierpts(&l, p0, p1, p2, p3);
+	if(l.np == -1)
+		return 0;
+	if(l.np != 0){
+		fillpolyop(dst, l.p, l.np, w, src, addpt(subpt(sp, p0), l.p[0]), op);
+		free(l.p);
+	}
+	return 1;
+}
+
+int
+fillbezspline(Image *dst, Point *pt, int npt, int w, Image *src, Point sp)
+{
+	return fillbezsplineop(dst, pt, npt, w, src, sp, SoverD);
+}
+
+int
+fillbezsplineop(Image *dst, Point *pt, int npt, int w, Image *src, Point sp, Drawop op)
+{
+	Plist l;
+
+	l.np = 0;
+	_bezsplinepts(&l, pt, npt);
+	if(l.np == -1)
+		return 0;
+	if(l.np > 0){
+		fillpolyop(dst, l.p, l.np, w, src, addpt(subpt(sp, pt[0]), l.p[0]), op);
+		free(l.p);
+	}
+	return 1;
+}
--- /dev/null
+++ b/sys/src/libdraw/border.c
@@ -1,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+void
+borderop(Image *im, Rectangle r, int i, Image *color, Point sp, Drawop op)
+{
+	if(i < 0){
+		r = insetrect(r, i);
+		sp = addpt(sp, Pt(i,i));
+		i = -i;
+	}
+	drawop(im, Rect(r.min.x, r.min.y, r.max.x, r.min.y+i),
+		color, nil, sp, op);
+	drawop(im, Rect(r.min.x, r.max.y-i, r.max.x, r.max.y),
+		color, nil, Pt(sp.x, sp.y+Dy(r)-i), op);
+	drawop(im, Rect(r.min.x, r.min.y+i, r.min.x+i, r.max.y-i),
+		color, nil, Pt(sp.x, sp.y+i), op);
+	drawop(im, Rect(r.max.x-i, r.min.y+i, r.max.x, r.max.y-i),
+		color, nil, Pt(sp.x+Dx(r)-i, sp.y+i), op);
+}
+
+void
+border(Image *im, Rectangle r, int i, Image *color, Point sp)
+{
+	borderop(im, r, i, color, sp, SoverD);
+}
--- /dev/null
+++ b/sys/src/libdraw/buildfont.c
@@ -1,0 +1,140 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static char*
+skip(char *s)
+{
+	while(*s==' ' || *s=='\n' || *s=='\t')
+		s++;
+	return s;
+}
+
+Font*
+buildfont(Display *d, char *buf, char *name)
+{
+	Font *fnt;
+	Cachefont *c, **sub;
+	char *s, *t;
+	ulong min, max;
+	int offset;
+	char badform[] = "bad font format: number expected (char position %d)";
+
+	s = buf;
+	fnt = malloc(sizeof(Font));
+	if(fnt == nil)
+		return nil;
+	memset(fnt, 0, sizeof(Font));
+	fnt->display = d;
+	fnt->name = strdup(name);
+	fnt->ncache = NFCACHE+NFLOOK;
+	fnt->nsubf = NFSUBF;
+	fnt->cache = malloc(fnt->ncache * sizeof(fnt->cache[0]));
+	fnt->subf = malloc(fnt->nsubf * sizeof(fnt->subf[0]));
+	if(fnt->name==nil || fnt->cache==nil || fnt->subf==nil){
+    Err2:
+		free(fnt->name);
+		free(fnt->cache);
+		free(fnt->subf);
+		free(fnt->sub);
+		free(fnt);
+		return nil;
+	}
+	fnt->height = strtol(s, &s, 0);
+	s = skip(s);
+	fnt->ascent = strtol(s, &s, 0);
+	s = skip(s);
+	if(fnt->height<=0 || fnt->ascent<=0){
+		werrstr("bad height or ascent in font file");
+		goto Err2;
+	}
+	fnt->width = 0;
+	fnt->nsub = 0;
+	fnt->sub = nil;
+
+	memset(fnt->subf, 0, fnt->nsubf * sizeof(fnt->subf[0]));
+	memset(fnt->cache, 0, fnt->ncache*sizeof(fnt->cache[0]));
+	fnt->age = 1;
+	do{
+		/* must be looking at a number now */
+		if(*s<'0' || '9'<*s){
+			werrstr(badform, s-buf);
+			goto Err3;
+		}
+		min = strtol(s, &s, 0);
+		s = skip(s);
+		/* must be looking at a number now */
+		if(*s<'0' || '9'<*s){
+			werrstr(badform, s-buf);
+			goto Err3;
+		}
+		max = strtol(s, &s, 0);
+		s = skip(s);
+		if(*s==0 || min>Runemax || max>Runemax || min>max){
+			werrstr("illegal subfont range");
+    Err3:
+			freefont(fnt);
+			return 0;
+		}
+		t = s;
+		offset = strtol(s, &t, 0);
+		if(t>s && (*t==' ' || *t=='\t' || *t=='\n'))
+			s = skip(t);
+		else
+			offset = 0;
+		sub = realloc(fnt->sub, (fnt->nsub+1)*sizeof(Cachefont*));
+		if(sub == nil)
+			goto Err3;
+		fnt->sub = sub;
+		c = malloc(sizeof(Cachefont));
+		if(c == nil)
+			goto Err3;
+		c->min = min;
+		c->max = max;
+		c->offset = offset;
+		t = s;
+		while(*s && *s!=' ' && *s!='\n' && *s!='\t')
+			s++;
+		*s++ = 0;
+		c->subfontname = nil;
+		c->name = strdup(t);
+		if(c->name == nil){
+			free(c);
+			goto Err3;
+		}
+		sub[fnt->nsub++] = c;
+		s = skip(s);
+	}while(*s);
+	return fnt;
+}
+
+void
+freefont(Font *f)
+{
+	int i;
+	Cachefont *c;
+	Subfont *s;
+
+	if(f == nil)
+		return;
+
+	for(i=0; i<f->nsub; i++){
+		c = f->sub[i];
+		free(c->subfontname);
+		free(c->name);
+		free(c);
+	}
+	for(i=0; i<f->nsubf; i++){
+		s = f->subf[i].f;
+		if(s != nil){
+			if(f->display == nil || s != f->display->defaultsubfont)
+				freesubfont(s);
+		}
+	}
+	freeimage(f->cacheimage);
+	free(f->name);
+	free(f->cache);
+	free(f->subf);
+	free(f->sub);
+	free(f);
+}
--- /dev/null
+++ b/sys/src/libdraw/bytesperline.c
@@ -1,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static
+int
+unitsperline(Rectangle r, int d, int bitsperunit)
+{
+	ulong l, t;
+
+	if(d <= 0 || d > 32)	/* being called wrong.  d is image depth. */
+		abort();
+
+	if(r.min.x >= 0){
+		l = (r.max.x*d+bitsperunit-1)/bitsperunit;
+		l -= (r.min.x*d)/bitsperunit;
+	}else{			/* make positive before divide */
+		t = (-r.min.x*d+bitsperunit-1)/bitsperunit;
+		l = t+(r.max.x*d+bitsperunit-1)/bitsperunit;
+	}
+	return l;
+}
+
+int
+wordsperline(Rectangle r, int d)
+{
+	return unitsperline(r, d, 8*sizeof(ulong));
+}
+
+int
+bytesperline(Rectangle r, int d)
+{
+	return unitsperline(r, d, 8);
+}
--- /dev/null
+++ b/sys/src/libdraw/chan.c
@@ -1,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static char channames[] = "rgbkamx";
+char*
+chantostr(char *buf, ulong cc)
+{
+	ulong c, rc;
+	char *p;
+
+	if(chantodepth(cc) == 0)
+		return nil;
+
+	/* reverse the channel descriptor so we can easily generate the string in the right order */
+	rc = 0;
+	for(c=cc; c; c>>=8){
+		rc <<= 8;
+		rc |= c&0xFF;
+	}
+
+	p = buf;
+	for(c=rc; c; c>>=8) {
+		*p++ = channames[TYPE(c)];
+		*p++ = '0'+NBITS(c);
+	}
+	*p = 0;
+
+	return buf;
+}
+
+/* avoid pulling in ctype when using with drawterm etc. */
+static int
+isspace(char c)
+{
+	return c==' ' || c== '\t' || c=='\r' || c=='\n';
+}
+
+ulong
+strtochan(char *s)
+{
+	char *p, *q;
+	ulong c;
+	int t, n, d;
+
+	c = 0;
+	d = 0;
+	p=s;
+	while(*p && isspace(*p))
+		p++;
+
+	while(*p && !isspace(*p)){
+		if((q = strchr(channames, p[0])) == nil) 
+			return 0;
+		t = q-channames;
+		if(p[1] < '0' || p[1] > '9')
+			return 0;
+		n = p[1]-'0';
+		d += n;
+		c = (c<<8) | __DC(t, n);
+		p += 2;
+	}
+	if(d==0 || (d>8 && d%8) || (d<8 && 8%d))
+		return 0;
+	return c;
+}
+
+int
+chantodepth(ulong c)
+{
+	int n;
+
+	for(n=0; c; c>>=8){
+		if(TYPE(c) >= NChan || NBITS(c) > 8 || NBITS(c) <= 0)
+			return 0;
+		n += NBITS(c);
+	}
+	if(n==0 || (n>8 && n%8) || (n<8 && 8%n))
+		return 0;
+	return n;
+}
--- /dev/null
+++ b/sys/src/libdraw/cloadimage.c
@@ -1,0 +1,49 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+cloadimage(Image *i, Rectangle r, uchar *data, int ndata)
+{
+	int m, nb, miny, maxy, ncblock;
+	uchar *a;
+
+	if(!rectinrect(r, i->r)){
+		werrstr("cloadimage: bad rectangle");
+		return -1;
+	}
+
+	miny = r.min.y;
+	m = 0;
+	ncblock = _compblocksize(r, i->depth);
+	while(miny != r.max.y){
+		maxy = atoi((char*)data+0*12);
+		nb = atoi((char*)data+1*12);
+		if(maxy<=miny || r.max.y<maxy){
+			werrstr("cloadimage: bad maxy %d", maxy);
+			return -1;
+		}
+		data += 2*12;
+		ndata -= 2*12;
+		m += 2*12;
+		if(nb<=0 || ncblock<nb || nb>ndata){
+			werrstr("cloadimage: bad count %d", nb);
+			return -1;
+		}
+		a = bufimage(i->display, 21+nb);
+		if(a == nil)
+			return -1;
+		a[0] = 'Y';
+		BPLONG(a+1, i->id);
+		BPLONG(a+5, r.min.x);
+		BPLONG(a+9, miny);
+		BPLONG(a+13, r.max.x);
+		BPLONG(a+17, maxy);
+		memmove(a+21, data, nb);
+		miny = maxy;
+		data += nb;
+		ndata += nb;
+		m += nb;
+	}
+	return m;
+}
--- /dev/null
+++ b/sys/src/libdraw/computil.c
@@ -1,0 +1,38 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * compressed data are seuences of byte codes.  
+ * if the first byte b has the 0x80 bit set, the next (b^0x80)+1 bytes
+ * are data.  otherwise, it's two bytes specifying a previous string to repeat.
+ */
+void
+_twiddlecompressed(uchar *buf, int n)
+{
+	uchar *ebuf;
+	int j, k, c;
+
+	ebuf = buf+n;
+	while(buf < ebuf){
+		c = *buf++;
+		if(c >= 128){
+			k = c-128+1;
+			for(j=0; j<k; j++, buf++)
+				*buf ^= 0xFF;
+		}else
+			buf++;
+	}
+}
+
+int
+_compblocksize(Rectangle r, int depth)
+{
+	int bpl;
+
+	bpl = bytesperline(r, depth);
+	bpl = 2*bpl;	/* add plenty extra for blocking, etc. */
+	if(bpl < NCBLOCK)
+		return NCBLOCK;
+	return bpl;
+}
--- /dev/null
+++ b/sys/src/libdraw/creadimage.c
@@ -1,0 +1,122 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Image *
+creadimage(Display *d, int fd, int dolock)
+{
+	char hdr[5*12+1];
+	Rectangle r;
+	int m, nb, miny, maxy, new, ldepth, ncblock;
+	uchar *buf, *a;
+	Image *i;
+	ulong chan;
+
+	if(readn(fd, hdr, 5*12) != 5*12)
+		return nil;
+
+	/*
+	 * distinguish new channel descriptor from old ldepth.
+	 * channel descriptors have letters as well as numbers,
+	 * while ldepths are a single digit formatted as %-11d.
+	 */
+	new = 0;
+	for(m=0; m<10; m++){
+		if(hdr[m] != ' '){
+			new = 1;
+			break;
+		}
+	}
+	if(hdr[11] != ' '){
+		werrstr("creadimage: bad format");
+		return nil;
+	}
+	if(new){
+		hdr[11] = '\0';
+		if((chan = strtochan(hdr)) == 0){
+			werrstr("creadimage: bad channel string %s", hdr);
+			return nil;
+		}
+	}else{
+		ldepth = ((int)hdr[10])-'0';
+		if(ldepth<0 || ldepth>3){
+			werrstr("creadimage: bad ldepth %d", ldepth);
+			return nil;
+		}
+		chan = drawld2chan[ldepth];
+	}
+	r.min.x=atoi(hdr+1*12);
+	r.min.y=atoi(hdr+2*12);
+	r.max.x=atoi(hdr+3*12);
+	r.max.y=atoi(hdr+4*12);
+	if(r.min.x>r.max.x || r.min.y>r.max.y){
+		werrstr("creadimage: bad rectangle");
+		return nil;
+	}
+
+	if(d != nil){
+		if(dolock)
+			lockdisplay(d);
+		i = allocimage(d, r, chan, 0, 0);
+		if(dolock)
+			unlockdisplay(d);
+		if(i == nil)
+			return nil;
+	}else{
+		i = mallocz(sizeof(Image), 1);
+		if(i == nil)
+			return nil;
+	}
+	setmalloctag(i, getcallerpc(&d));
+	ncblock = _compblocksize(r, chantodepth(chan));
+	buf = malloc(ncblock);
+	if(buf == nil)
+		goto Errout;
+	miny = r.min.y;
+	while(miny != r.max.y){
+		if(readn(fd, hdr, 2*12) != 2*12){
+		Errout:
+			if(dolock)
+				lockdisplay(d);
+		Erroutlock:
+			freeimage(i);
+			if(dolock)
+				unlockdisplay(d);
+			free(buf);
+			return nil;
+		}
+		maxy = atoi(hdr+0*12);
+		nb = atoi(hdr+1*12);
+		if(maxy<=miny || r.max.y<maxy){
+			werrstr("creadimage: bad maxy %d", maxy);
+			goto Errout;
+		}
+		if(nb<=0 || ncblock<nb){
+			werrstr("creadimage: bad count %d", nb);
+			goto Errout;
+		}
+		if(readn(fd, buf, nb)!=nb)
+			goto Errout;
+		if(d != nil){
+			if(dolock)
+				lockdisplay(d);
+			a = bufimage(i->display, 21+nb);
+			if(a == nil)
+				goto Erroutlock;
+			a[0] = 'Y';
+			BPLONG(a+1, i->id);
+			BPLONG(a+5, r.min.x);
+			BPLONG(a+9, miny);
+			BPLONG(a+13, r.max.x);
+			BPLONG(a+17, maxy);
+			if(!new)	/* old image: flip the data bits */
+				_twiddlecompressed(buf, nb);
+			memmove(a+21, buf, nb);
+			if(dolock)
+				unlockdisplay(d);
+		}
+		miny = maxy;
+	}
+	free(buf);
+	return i;
+}
--- /dev/null
+++ b/sys/src/libdraw/debug.c
@@ -1,0 +1,16 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+void
+drawsetdebug(int v)
+{
+	uchar *a;
+	a = bufimage(display, 1+1);
+	if(a == nil){
+		fprint(2, "drawsetdebug: %r\n");
+		return;
+	}
+	a[0] = 'D';
+	a[1] = v;
+}
--- /dev/null
+++ b/sys/src/libdraw/defont.c
@@ -1,0 +1,388 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * vga/vga00, in uncompressed form
+ */
+uchar
+defontdata[] =
+{
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x30,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x30,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x30,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x32,0x30,0x34,0x38,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x31,0x36,0x20,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x30,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x0c,0x10,0x76,
+0x6c,0x38,0x00,0x00,0x30,0x0c,0x10,0x6c,0x30,0x0c,0x18,0x66,0x00,0x76,0x60,0x0c,
+0x10,0x76,0x6c,0x00,0x00,0x60,0x0c,0x10,0x6c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0xe0,
+0xe0,0xe0,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0xe0,0xe0,0xe0,
+0xe0,0x90,0x70,0xe0,0x70,0x00,0x70,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x00,
+0x18,0x00,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x30,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x38,
+0x00,0x00,0x00,0x7c,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x70,0x70,
+0x00,0x00,0x00,0x00,0x00,0x30,0x38,0x00,0xc0,0xc0,0xe0,0x00,0x30,0x18,0x38,0xdc,
+0x6c,0x6c,0x00,0x00,0x18,0x18,0x38,0x6c,0x18,0x18,0x3c,0x66,0x00,0xdc,0x30,0x18,
+0x38,0xdc,0x6c,0x00,0x00,0x30,0x18,0x38,0x6c,0x18,0x00,0x00,0x00,0x00,0x10,0x00,
+0x00,0x38,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,
+0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0xda,0x00,0x80,0x80,
+0x80,0x80,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x90,0x90,0x90,
+0x90,0xd0,0x80,0x80,0x90,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x18,0x66,0x00,
+0x7c,0x00,0x38,0x30,0x0c,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x18,0x7c,0x7c,
+0x0c,0xfe,0x38,0xfe,0x7c,0x7c,0x00,0x00,0x00,0x00,0x00,0x7c,0x00,0x10,0xfc,0x3c,
+0xf8,0xfe,0xfe,0x3c,0xc6,0x3c,0x1e,0xe6,0xf0,0xc6,0xc6,0x7c,0xfc,0x7c,0xfc,0x7c,
+0x7e,0xc6,0xc6,0xc6,0xc6,0x66,0xfe,0x3c,0x00,0x3c,0x6c,0x00,0x18,0x00,0xe0,0x00,
+0x1c,0x00,0x38,0x00,0xe0,0x18,0x06,0xe0,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x18,0x70,0x76,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x6c,
+0x00,0x66,0x18,0xc6,0x6c,0x3c,0x6c,0x00,0x00,0x00,0x38,0x00,0x6c,0x00,0xd8,0xd8,
+0x0c,0x00,0x7f,0x00,0x00,0x70,0x6c,0x00,0xc0,0xc0,0x30,0x30,0x00,0x00,0x6c,0x00,
+0x00,0x38,0x3e,0x3c,0x00,0x00,0x44,0x00,0x00,0x00,0x42,0x00,0xf8,0x00,0x00,0x00,
+0x44,0x00,0x00,0x00,0x7a,0x00,0x00,0x44,0x00,0x00,0xf0,0x3c,0x60,0x18,0x38,0x76,
+0x6c,0x6c,0x00,0x00,0x60,0x0c,0x38,0x6c,0x30,0x0c,0x38,0x66,0x76,0x76,0x60,0x0c,
+0x38,0x76,0x6c,0x00,0x00,0x60,0x18,0x38,0xcc,0x0c,0xe0,0x6c,0x02,0x00,0xe0,0xe0,
+0xe0,0xe0,0xf0,0x18,0x70,0x48,0x20,0x48,0x70,0x38,0x38,0x38,0x90,0x90,0x90,0x90,
+0x90,0xb0,0x60,0xe0,0x80,0xe0,0x60,0xe0,0x70,0x38,0x70,0x48,0x00,0x3c,0x66,0x6c,
+0xc6,0x00,0x6c,0x30,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x6c,0x38,0xc6,0xc6,
+0x1c,0xc0,0x60,0xc6,0xc6,0xc6,0x00,0x00,0x06,0x00,0x60,0xc6,0x7c,0x38,0x66,0x66,
+0x6c,0x66,0x66,0x66,0xc6,0x18,0x0c,0x66,0x60,0xee,0xe6,0xc6,0x66,0xc6,0x66,0xc6,
+0x7e,0xc6,0xc6,0xc6,0xc6,0x66,0xc6,0x30,0x80,0x0c,0xc6,0x00,0x00,0x00,0x60,0x00,
+0x0c,0x00,0x6c,0x00,0x60,0x18,0x06,0x60,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0xdc,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x3c,0x64,
+0x00,0x66,0x18,0x60,0x6c,0x42,0x6c,0x00,0x00,0x00,0x44,0x7c,0x6c,0x00,0x30,0x30,
+0x18,0x00,0xdb,0x00,0x00,0x30,0x6c,0x00,0xc2,0xc2,0x62,0x30,0x10,0x10,0x10,0x10,
+0x10,0x10,0x6c,0x66,0xfe,0xfe,0xfe,0xfe,0x3c,0x3c,0x3c,0x3c,0x6c,0xc6,0x7c,0x7c,
+0x7c,0x7c,0x7c,0x00,0xc4,0xc6,0xc6,0xc6,0xc6,0x66,0x60,0x66,0x30,0x30,0x6c,0xdc,
+0x6c,0x38,0x00,0x00,0x30,0x18,0x6c,0x6c,0x18,0x18,0x6c,0x66,0x1c,0xdc,0x30,0x18,
+0x6c,0xdc,0x6c,0x00,0x00,0x30,0x30,0x6c,0xcc,0x18,0x60,0x6c,0x80,0x00,0x10,0x80,
+0x80,0x80,0x90,0x3c,0x48,0x48,0x20,0x48,0x40,0x48,0x40,0x40,0x90,0x90,0x90,0x90,
+0x90,0x90,0x10,0x80,0x80,0x80,0x10,0x80,0x40,0x40,0x48,0x48,0x00,0x3c,0x24,0x6c,
+0xc2,0xc2,0x6c,0x20,0x30,0x0c,0x00,0x00,0x00,0x00,0x00,0x02,0xc6,0x78,0x06,0x06,
+0x3c,0xc0,0xc0,0x06,0xc6,0xc6,0x18,0x18,0x0c,0x00,0x30,0xc6,0xc6,0x6c,0x66,0xc2,
+0x66,0x62,0x62,0xc2,0xc6,0x18,0x0c,0x66,0x60,0xfe,0xf6,0xc6,0x66,0xc6,0x66,0xc6,
+0x5a,0xc6,0xc6,0xc6,0x6c,0x66,0x86,0x30,0xc0,0x0c,0x00,0x00,0x00,0x00,0x60,0x00,
+0x0c,0x00,0x64,0x00,0x60,0x00,0x00,0x60,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x00,0x10,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x66,0x60,
+0x66,0x3c,0x18,0x38,0x00,0x99,0x3e,0x00,0x00,0x00,0xba,0x00,0x38,0x18,0x60,0x18,
+0x00,0x00,0xdb,0x00,0x00,0x30,0x38,0x00,0xc6,0xc6,0x36,0x00,0x38,0x38,0x38,0x38,
+0x38,0x38,0xcc,0xc2,0x66,0x66,0x66,0x66,0x18,0x18,0x18,0x18,0x66,0xe6,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x00,0xce,0xc6,0xc6,0xc6,0xc6,0x66,0x7c,0x66,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,
+0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x82,0x00,0xe0,0xe0,
+0xe0,0xe0,0x90,0x3c,0x70,0x78,0x20,0x48,0x70,0x40,0x30,0x30,0xe0,0xe0,0xe0,0xe0,
+0xe0,0x90,0xe0,0xe0,0x70,0xe0,0xe0,0xe0,0x70,0x58,0x70,0x48,0x00,0x3c,0x00,0xfe,
+0xc0,0xc6,0x38,0x00,0x30,0x0c,0x66,0x18,0x00,0x00,0x00,0x06,0xc6,0x18,0x0c,0x06,
+0x6c,0xc0,0xc0,0x06,0xc6,0xc6,0x18,0x18,0x18,0x7e,0x18,0x0c,0xc6,0xc6,0x66,0xc0,
+0x66,0x68,0x68,0xc0,0xc6,0x18,0x0c,0x6c,0x60,0xfe,0xfe,0xc6,0x66,0xc6,0x66,0x60,
+0x18,0xc6,0xc6,0xc6,0x7c,0x66,0x0c,0x30,0xe0,0x0c,0x00,0x00,0x00,0x78,0x78,0x7c,
+0x3c,0x7c,0x60,0x76,0x6c,0x38,0x0e,0x66,0x18,0xec,0xdc,0x7c,0xdc,0x76,0xdc,0x7c,
+0xfc,0xcc,0x66,0xc6,0xc6,0xc6,0xfe,0x18,0x18,0x18,0x00,0x38,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x60,0xf0,
+0x3c,0x18,0x18,0x6c,0x00,0xa5,0x00,0x36,0x00,0x00,0xb2,0x00,0x00,0x18,0xc8,0xd8,
+0x00,0xcc,0xdb,0x00,0x00,0x30,0x00,0xd8,0xcc,0xcc,0xec,0x30,0x6c,0x6c,0x6c,0x6c,
+0x6c,0x6c,0xcc,0xc0,0x62,0x62,0x62,0x62,0x18,0x18,0x18,0x18,0x66,0xf6,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x66,0xce,0xc6,0xc6,0xc6,0xc6,0x66,0x66,0x66,0x78,0x78,0x78,0x78,
+0x78,0x78,0xcc,0x7c,0x7c,0x7c,0x7c,0x7c,0x38,0x38,0x38,0x38,0x06,0xdc,0x7c,0x7c,
+0x7c,0x7c,0x7c,0x18,0x7a,0xcc,0xcc,0xcc,0xcc,0xc6,0x7c,0xc6,0x02,0x00,0x1c,0x1c,
+0x18,0x24,0x1c,0x3c,0x48,0x48,0x20,0x30,0x40,0x40,0x08,0x08,0x10,0x1c,0x1c,0x1c,
+0x1c,0x0c,0x44,0x1c,0x0c,0x80,0x24,0x1c,0x40,0x48,0x50,0x48,0x00,0x18,0x00,0x6c,
+0x7c,0x0c,0x76,0x00,0x30,0x0c,0x3c,0x18,0x00,0x00,0x00,0x0c,0xd6,0x18,0x18,0x3c,
+0xcc,0xfc,0xfc,0x0c,0x7c,0x7e,0x00,0x00,0x30,0x00,0x0c,0x18,0xde,0xc6,0x7c,0xc0,
+0x66,0x78,0x78,0xc0,0xfe,0x18,0x0c,0x78,0x60,0xd6,0xde,0xc6,0x7c,0xc6,0x7c,0x38,
+0x18,0xc6,0xc6,0xd6,0x38,0x3c,0x18,0x30,0x70,0x0c,0x00,0x00,0x00,0x0c,0x6c,0xc6,
+0x6c,0xc6,0xf0,0xcc,0x76,0x18,0x06,0x6c,0x18,0xfe,0x66,0xc6,0x66,0xcc,0x76,0xc6,
+0x30,0xcc,0x66,0xc6,0x6c,0xc6,0xcc,0x70,0x00,0x0e,0x00,0x6c,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x60,0x60,
+0x66,0x7e,0x00,0xc6,0x00,0xa1,0x7e,0x6c,0xfe,0x00,0xaa,0x00,0x00,0x7e,0xf8,0x70,
+0x00,0xcc,0x7b,0x00,0x00,0x78,0x7c,0x6c,0x18,0x18,0x18,0x30,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xfe,0xc0,0x68,0x68,0x68,0x68,0x18,0x18,0x18,0x18,0xf6,0xfe,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x3c,0xd6,0xc6,0xc6,0xc6,0xc6,0x3c,0x66,0x6c,0x0c,0x0c,0x0c,0x0c,
+0x0c,0x0c,0x76,0xc6,0xc6,0xc6,0xc6,0xc6,0x18,0x18,0x18,0x18,0x7e,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x00,0xc4,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0x80,0x00,0x08,0x08,
+0x24,0x34,0x24,0x3c,0x70,0x48,0x38,0x30,0x40,0x38,0x70,0x70,0x10,0x24,0x24,0x24,
+0x24,0x12,0x28,0x08,0x12,0xe0,0x24,0x20,0x40,0x38,0x48,0x30,0x00,0x18,0x00,0x6c,
+0x06,0x18,0xdc,0x00,0x30,0x0c,0xff,0x7e,0x00,0xfe,0x00,0x18,0xd6,0x18,0x30,0x06,
+0xfe,0x06,0xc6,0x18,0xc6,0x06,0x00,0x00,0x60,0x00,0x06,0x18,0xde,0xfe,0x66,0xc0,
+0x66,0x68,0x68,0xde,0xc6,0x18,0x0c,0x78,0x60,0xc6,0xce,0xc6,0x60,0xc6,0x6c,0x0c,
+0x18,0xc6,0xc6,0xd6,0x38,0x18,0x30,0x30,0x38,0x0c,0x00,0x00,0x00,0x7c,0x66,0xc0,
+0xcc,0xfe,0x60,0xcc,0x66,0x18,0x06,0x78,0x18,0xd6,0x66,0xc6,0x66,0xcc,0x66,0x60,
+0x30,0xcc,0x66,0xd6,0x38,0xc6,0x18,0x18,0x18,0x18,0x00,0xc6,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x60,0x60,
+0x66,0x18,0x18,0xc6,0x00,0xa1,0x00,0xd8,0x06,0x3c,0x44,0x00,0x00,0x18,0x00,0x00,
+0x00,0xcc,0x1b,0x00,0x00,0x00,0x00,0x36,0x30,0x30,0x30,0x60,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xcc,0xc0,0x78,0x78,0x78,0x78,0x18,0x18,0x18,0x18,0x66,0xde,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x18,0xd6,0xc6,0xc6,0xc6,0xc6,0x18,0x66,0x66,0x7c,0x7c,0x7c,0x7c,
+0x7c,0x7c,0x36,0xc0,0xfe,0xfe,0xfe,0xfe,0x18,0x18,0x18,0x18,0xc6,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x7e,0xce,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0x82,0x00,0x08,0x08,
+0x24,0x2c,0x20,0x66,0x07,0x07,0x07,0x07,0x0e,0x00,0x06,0x07,0x10,0x20,0x20,0x20,
+0x20,0x1e,0x10,0x08,0x1e,0x11,0x24,0x18,0x0e,0x07,0x07,0x07,0x00,0x18,0x00,0x6c,
+0x06,0x30,0xcc,0x00,0x30,0x0c,0x3c,0x18,0x00,0x00,0x00,0x30,0xc6,0x18,0x60,0x06,
+0x0c,0x06,0xc6,0x30,0xc6,0x06,0x00,0x00,0x30,0x7e,0x0c,0x18,0xde,0xc6,0x66,0xc0,
+0x66,0x60,0x60,0xc6,0xc6,0x18,0xcc,0x6c,0x60,0xc6,0xc6,0xc6,0x60,0xc6,0x66,0x06,
+0x18,0xc6,0xc6,0xd6,0x7c,0x18,0x60,0x30,0x1c,0x0c,0x00,0x00,0x00,0xcc,0x66,0xc0,
+0xcc,0xc0,0x60,0xcc,0x66,0x18,0x06,0x78,0x18,0xd6,0x66,0xc6,0x66,0xcc,0x60,0x38,
+0x30,0xcc,0x66,0xd6,0x38,0xc6,0x30,0x18,0x18,0x18,0x00,0xc6,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x66,0x60,
+0x66,0x7e,0x18,0x6c,0x00,0xa5,0x00,0x6c,0x06,0x00,0x38,0x00,0x00,0x18,0x00,0x00,
+0x00,0xcc,0x1b,0x18,0x00,0x00,0x00,0x6c,0x66,0x60,0x66,0xc0,0xfe,0xfe,0xfe,0xfe,
+0xfe,0xfe,0xcc,0xc0,0x68,0x68,0x68,0x68,0x18,0x18,0x18,0x18,0x66,0xce,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x3c,0xe6,0xc6,0xc6,0xc6,0xc6,0x18,0x66,0x66,0xcc,0xcc,0xcc,0xcc,
+0xcc,0xcc,0x7e,0xc0,0xc0,0xc0,0xc0,0xc0,0x18,0x18,0x18,0x18,0xc6,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x00,0xd6,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0x02,0x00,0x08,0x08,
+0x24,0x24,0x20,0xc3,0x08,0x02,0x04,0x02,0x08,0x0e,0x09,0x02,0x10,0x20,0x20,0x20,
+0x20,0x12,0x10,0x08,0x12,0x1b,0x24,0x04,0x10,0x08,0x08,0x08,0x00,0x00,0x00,0xfe,
+0x86,0x60,0xcc,0x00,0x30,0x0c,0x66,0x18,0x18,0x00,0x00,0x60,0xc6,0x18,0xc0,0x06,
+0x0c,0x06,0xc6,0x30,0xc6,0x06,0x18,0x18,0x18,0x00,0x18,0x00,0xdc,0xc6,0x66,0xc2,
+0x66,0x62,0x60,0xc6,0xc6,0x18,0xcc,0x66,0x62,0xc6,0xc6,0xc6,0x60,0xd6,0x66,0xc6,
+0x18,0xc6,0x6c,0xfe,0x6c,0x18,0xc2,0x30,0x0e,0x0c,0x00,0x00,0x00,0xcc,0x66,0xc0,
+0xcc,0xc0,0x60,0xcc,0x66,0x18,0x06,0x6c,0x18,0xd6,0x66,0xc6,0x66,0xcc,0x60,0x0c,
+0x30,0xcc,0x66,0xd6,0x38,0xc6,0x60,0x18,0x18,0x18,0x00,0xc6,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x3c,0x60,
+0x3c,0x18,0x18,0x38,0x00,0x99,0x00,0x36,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xcc,0x1b,0x00,0x00,0x00,0x00,0xd8,0xce,0xdc,0xce,0xc6,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xcc,0xc2,0x62,0x62,0x62,0x62,0x18,0x18,0x18,0x18,0x66,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x66,0xe6,0xc6,0xc6,0xc6,0xc6,0x18,0x7c,0x66,0xcc,0xcc,0xcc,0xcc,
+0xcc,0xcc,0xd8,0xc0,0xc0,0xc0,0xc0,0xc0,0x18,0x18,0x18,0x18,0xc6,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x18,0xe6,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0x80,0x00,0x08,0x08,
+0x18,0x24,0x1c,0xff,0x06,0x02,0x07,0x02,0x0e,0x09,0x09,0x02,0x1c,0x1c,0x1c,0x1c,
+0x1c,0x12,0x10,0x08,0x12,0x15,0x18,0x38,0x0c,0x06,0x06,0x06,0x00,0x18,0x00,0x6c,
+0xc6,0xc6,0xcc,0x00,0x18,0x18,0x00,0x00,0x18,0x00,0x18,0xc0,0x6c,0x18,0xc6,0xc6,
+0x0c,0xc6,0xc6,0x30,0xc6,0x0c,0x18,0x18,0x0c,0x00,0x30,0x18,0xc0,0xc6,0x66,0x66,
+0x6c,0x66,0x60,0x66,0xc6,0x18,0xcc,0x66,0x66,0xc6,0xc6,0xc6,0x60,0xde,0x66,0xc6,
+0x18,0xc6,0x38,0xee,0xc6,0x18,0xc6,0x30,0x06,0x0c,0x00,0x00,0x00,0xcc,0x66,0xc6,
+0xcc,0xc6,0x60,0xcc,0x66,0x18,0x06,0x66,0x18,0xd6,0x66,0xc6,0x66,0xcc,0x60,0xc6,
+0x36,0xcc,0x3c,0xfe,0x6c,0xc6,0xc6,0x18,0x18,0x18,0x00,0xfe,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3c,0x18,0xe6,
+0x66,0x18,0x18,0x0c,0x00,0x42,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x7e,0x00,0x00,
+0x00,0xcc,0x1b,0x00,0x00,0x00,0x00,0x00,0x9e,0x86,0x9e,0xc6,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xcc,0x66,0x66,0x66,0x66,0x66,0x18,0x18,0x18,0x18,0x6c,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x00,0x46,0xc6,0xc6,0xc6,0xc6,0x18,0x60,0x66,0xcc,0xcc,0xcc,0xcc,
+0xcc,0xcc,0xd8,0xc6,0xc6,0xc6,0xc6,0xc6,0x18,0x18,0x18,0x18,0xc6,0x66,0xc6,0xc6,
+0xc6,0xc6,0xc6,0x18,0x46,0xcc,0xcc,0xcc,0xcc,0xc6,0x66,0xc6,0xb6,0x00,0x05,0x05,
+0x07,0x0c,0x09,0x18,0x01,0x02,0x04,0x02,0x08,0x0e,0x09,0x02,0x07,0x02,0x06,0x06,
+0x02,0x09,0x09,0x0e,0x09,0x15,0x0e,0x07,0x02,0x01,0x01,0x01,0x00,0x18,0x00,0x6c,
+0x7c,0x86,0x76,0x00,0x0c,0x30,0x00,0x00,0x18,0x00,0x18,0x80,0x38,0x7e,0xfe,0x7c,
+0x1e,0x7c,0x7c,0x30,0x7c,0x78,0x00,0x30,0x06,0x00,0x60,0x18,0x7c,0xc6,0xfc,0x3c,
+0xf8,0xfe,0xf0,0x3a,0xc6,0x3c,0x78,0xe6,0xfe,0xc6,0xc6,0x7c,0xf0,0x7c,0xe6,0x7c,
+0x3c,0x7c,0x10,0x6c,0xc6,0x3c,0xfe,0x3c,0x02,0x3c,0x00,0x00,0x00,0x76,0x7c,0x7c,
+0x76,0x7c,0xf0,0x7c,0xe6,0x3c,0x06,0xe6,0x3c,0xc6,0x66,0x7c,0x7c,0x7c,0xf0,0x7c,
+0x1c,0x76,0x18,0x6c,0xc6,0x7e,0xfe,0x0e,0x18,0x70,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0xfc,
+0x00,0x18,0x18,0xc6,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xf6,0x1b,0x00,0x00,0x00,0x00,0x00,0x3e,0x0c,0x3e,0x7c,0xc6,0xc6,0xc6,0xc6,
+0xc6,0xc6,0xce,0x3c,0xfe,0xfe,0xfe,0xfe,0x3c,0x3c,0x3c,0x3c,0xf8,0xc6,0x7c,0x7c,
+0x7c,0x7c,0x7c,0x00,0xbc,0x7c,0x7c,0x7c,0x7c,0x3c,0xf0,0xec,0x76,0x76,0x76,0x76,
+0x76,0x76,0x6e,0x7c,0x7c,0x7c,0x7c,0x7c,0x3c,0x3c,0x3c,0x3c,0x7c,0x66,0x7c,0x7c,
+0x7c,0x7c,0x7c,0x00,0xbc,0x76,0x76,0x76,0x76,0x7e,0x7c,0x7e,0x00,0x00,0x05,0x05,
+0x02,0x12,0x0a,0x00,0x0e,0x02,0x04,0x02,0x08,0x0a,0x06,0x07,0x04,0x06,0x09,0x01,
+0x06,0x0a,0x0d,0x09,0x0d,0x11,0x09,0x09,0x1c,0x0e,0x0e,0x0e,0x00,0x00,0x00,0x00,
+0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x0c,0x00,0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x60,0x0c,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xc0,0x00,0x00,0x18,0x00,0x00,0x00,0x06,0x18,0x06,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x60,0x06,0x00,0x00,0x02,0x02,
+0x02,0x12,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x07,0x02,0x02,0x06,
+0x0a,0x0c,0x0b,0x0e,0x0b,0x00,0x0e,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0xcc,0x00,0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x60,0x0c,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xc0,0x00,0x00,0x0c,0x00,0x00,0x00,0x06,0x3e,0x06,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x60,0x0c,0x00,0x00,0x05,0x05,
+0x02,0x16,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x02,0x04,0x01,
+0x1f,0x0a,0x09,0x09,0x09,0x00,0x09,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x78,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0xf0,0x1e,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xc0,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0xf0,0xf8,0x00,0x00,0x05,0x05,
+0x02,0x0d,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x0f,0x06,
+0x02,0x09,0x09,0x0e,0x09,0x00,0x0e,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0x35,0x36,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x31,0x36,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x00,0x00,0x00,0x0f,0x00,0x08,0x08,0x00,0x00,0x0f,0x00,0x08,0x10,0x00,0x00,0x0f,
+0x00,0x08,0x18,0x00,0x00,0x0f,0x00,0x08,0x20,0x00,0x00,0x0f,0x00,0x08,0x28,0x00,
+0x00,0x0f,0x00,0x08,0x30,0x00,0x00,0x0f,0x00,0x08,0x38,0x00,0x00,0x0f,0x00,0x08,
+0x40,0x00,0x00,0x0f,0x00,0x08,0x48,0x00,0x00,0x0f,0x00,0x08,0x50,0x00,0x00,0x0f,
+0x00,0x08,0x58,0x00,0x00,0x0f,0x00,0x08,0x60,0x00,0x00,0x0f,0x00,0x08,0x68,0x00,
+0x00,0x0f,0x00,0x08,0x70,0x00,0x00,0x0f,0x00,0x08,0x78,0x00,0x00,0x0f,0x00,0x08,
+0x80,0x00,0x00,0x0f,0x00,0x08,0x88,0x00,0x00,0x0f,0x00,0x08,0x90,0x00,0x00,0x0f,
+0x00,0x08,0x98,0x00,0x00,0x0f,0x00,0x08,0xa0,0x00,0x00,0x0f,0x00,0x08,0xa8,0x00,
+0x00,0x0f,0x00,0x08,0xb0,0x00,0x00,0x0f,0x00,0x08,0xb8,0x00,0x00,0x0f,0x00,0x08,
+0xc0,0x00,0x00,0x0f,0x00,0x08,0xc8,0x00,0x00,0x0f,0x00,0x08,0xd0,0x00,0x00,0x0f,
+0x00,0x08,0xd8,0x00,0x00,0x0f,0x00,0x08,0xe0,0x00,0x00,0x0f,0x00,0x08,0xe8,0x00,
+0x00,0x0f,0x00,0x08,0xf0,0x00,0x00,0x0f,0x00,0x08,0xf8,0x00,0x00,0x0f,0x00,0x08,
+0x00,0x01,0x00,0x0f,0x00,0x08,0x08,0x01,0x00,0x0f,0x00,0x08,0x10,0x01,0x00,0x0f,
+0x00,0x08,0x18,0x01,0x00,0x0f,0x00,0x08,0x20,0x01,0x00,0x0f,0x00,0x08,0x28,0x01,
+0x00,0x0f,0x00,0x08,0x30,0x01,0x00,0x0f,0x00,0x08,0x38,0x01,0x00,0x0f,0x00,0x08,
+0x40,0x01,0x00,0x0f,0x00,0x08,0x48,0x01,0x00,0x0f,0x00,0x08,0x50,0x01,0x00,0x0f,
+0x00,0x08,0x58,0x01,0x00,0x0f,0x00,0x08,0x60,0x01,0x00,0x0f,0x00,0x08,0x68,0x01,
+0x00,0x0f,0x00,0x08,0x70,0x01,0x00,0x0f,0x00,0x08,0x78,0x01,0x00,0x0f,0x00,0x08,
+0x80,0x01,0x00,0x0f,0x00,0x08,0x88,0x01,0x00,0x0f,0x00,0x08,0x90,0x01,0x00,0x0f,
+0x00,0x08,0x98,0x01,0x00,0x0f,0x00,0x08,0xa0,0x01,0x00,0x0f,0x00,0x08,0xa8,0x01,
+0x00,0x0f,0x00,0x08,0xb0,0x01,0x00,0x0f,0x00,0x08,0xb8,0x01,0x00,0x0f,0x00,0x08,
+0xc0,0x01,0x00,0x0f,0x00,0x08,0xc8,0x01,0x00,0x0f,0x00,0x08,0xd0,0x01,0x00,0x0f,
+0x00,0x08,0xd8,0x01,0x00,0x0f,0x00,0x08,0xe0,0x01,0x00,0x0f,0x00,0x08,0xe8,0x01,
+0x00,0x0f,0x00,0x08,0xf0,0x01,0x00,0x0f,0x00,0x08,0xf8,0x01,0x00,0x0f,0x00,0x08,
+0x00,0x02,0x00,0x0f,0x00,0x08,0x08,0x02,0x00,0x0f,0x00,0x08,0x10,0x02,0x00,0x0f,
+0x00,0x08,0x18,0x02,0x00,0x0f,0x00,0x08,0x20,0x02,0x00,0x0f,0x00,0x08,0x28,0x02,
+0x00,0x0f,0x00,0x08,0x30,0x02,0x00,0x0f,0x00,0x08,0x38,0x02,0x00,0x0f,0x00,0x08,
+0x40,0x02,0x00,0x0f,0x00,0x08,0x48,0x02,0x00,0x0f,0x00,0x08,0x50,0x02,0x00,0x0f,
+0x00,0x08,0x58,0x02,0x00,0x0f,0x00,0x08,0x60,0x02,0x00,0x0f,0x00,0x08,0x68,0x02,
+0x00,0x0f,0x00,0x08,0x70,0x02,0x00,0x0f,0x00,0x08,0x78,0x02,0x00,0x0f,0x00,0x08,
+0x80,0x02,0x00,0x0f,0x00,0x08,0x88,0x02,0x00,0x0f,0x00,0x08,0x90,0x02,0x00,0x0f,
+0x00,0x08,0x98,0x02,0x00,0x0f,0x00,0x08,0xa0,0x02,0x00,0x0f,0x00,0x08,0xa8,0x02,
+0x00,0x0f,0x00,0x08,0xb0,0x02,0x00,0x0f,0x00,0x08,0xb8,0x02,0x00,0x0f,0x00,0x08,
+0xc0,0x02,0x00,0x0f,0x00,0x08,0xc8,0x02,0x00,0x0f,0x00,0x08,0xd0,0x02,0x00,0x0f,
+0x00,0x08,0xd8,0x02,0x00,0x0f,0x00,0x08,0xe0,0x02,0x00,0x0f,0x00,0x08,0xe8,0x02,
+0x00,0x0f,0x00,0x08,0xf0,0x02,0x00,0x0f,0x00,0x08,0xf8,0x02,0x00,0x0f,0x00,0x08,
+0x00,0x03,0x00,0x0f,0x00,0x08,0x08,0x03,0x00,0x0f,0x00,0x08,0x10,0x03,0x00,0x0f,
+0x00,0x08,0x18,0x03,0x00,0x0f,0x00,0x08,0x20,0x03,0x00,0x0f,0x00,0x08,0x28,0x03,
+0x00,0x0f,0x00,0x08,0x30,0x03,0x00,0x0f,0x00,0x08,0x38,0x03,0x00,0x0f,0x00,0x08,
+0x40,0x03,0x00,0x0f,0x00,0x08,0x48,0x03,0x00,0x0f,0x00,0x08,0x50,0x03,0x00,0x0f,
+0x00,0x08,0x58,0x03,0x00,0x0f,0x00,0x08,0x60,0x03,0x00,0x0f,0x00,0x08,0x68,0x03,
+0x00,0x0f,0x00,0x08,0x70,0x03,0x00,0x0f,0x00,0x08,0x78,0x03,0x00,0x0f,0x00,0x08,
+0x80,0x03,0x00,0x0f,0x00,0x08,0x88,0x03,0x00,0x0f,0x00,0x08,0x90,0x03,0x00,0x0f,
+0x00,0x08,0x98,0x03,0x00,0x0f,0x00,0x08,0xa0,0x03,0x00,0x0f,0x00,0x08,0xa8,0x03,
+0x00,0x0f,0x00,0x08,0xb0,0x03,0x00,0x0f,0x00,0x08,0xb8,0x03,0x00,0x0f,0x00,0x08,
+0xc0,0x03,0x00,0x0f,0x00,0x08,0xc8,0x03,0x00,0x0f,0x00,0x08,0xd0,0x03,0x00,0x0f,
+0x00,0x08,0xd8,0x03,0x00,0x0f,0x00,0x08,0xe0,0x03,0x00,0x0f,0x00,0x08,0xe8,0x03,
+0x00,0x0f,0x00,0x08,0xf0,0x03,0x00,0x0f,0x00,0x08,0xf8,0x03,0x00,0x0f,0x00,0x08,
+0x00,0x04,0x00,0x0f,0x00,0x08,0x08,0x04,0x00,0x0f,0x00,0x08,0x10,0x04,0x00,0x0f,
+0x00,0x08,0x18,0x04,0x00,0x0f,0x00,0x08,0x20,0x04,0x00,0x0f,0x00,0x08,0x28,0x04,
+0x00,0x0f,0x00,0x08,0x30,0x04,0x00,0x0f,0x00,0x08,0x38,0x04,0x00,0x0f,0x00,0x08,
+0x40,0x04,0x00,0x0f,0x00,0x08,0x48,0x04,0x00,0x0f,0x00,0x08,0x50,0x04,0x00,0x0f,
+0x00,0x08,0x58,0x04,0x00,0x0f,0x00,0x08,0x60,0x04,0x00,0x0f,0x00,0x08,0x68,0x04,
+0x00,0x0f,0x00,0x08,0x70,0x04,0x00,0x0f,0x00,0x08,0x78,0x04,0x00,0x0f,0x00,0x08,
+0x80,0x04,0x00,0x0f,0x00,0x08,0x88,0x04,0x00,0x0f,0x00,0x08,0x90,0x04,0x00,0x0f,
+0x00,0x08,0x98,0x04,0x00,0x0f,0x00,0x08,0xa0,0x04,0x00,0x0f,0x00,0x08,0xa8,0x04,
+0x00,0x0f,0x00,0x08,0xb0,0x04,0x00,0x0f,0x00,0x08,0xb8,0x04,0x00,0x0f,0x00,0x08,
+0xc0,0x04,0x00,0x0f,0x00,0x08,0xc8,0x04,0x00,0x0f,0x00,0x08,0xd0,0x04,0x00,0x0f,
+0x00,0x08,0xd8,0x04,0x00,0x0f,0x00,0x08,0xe0,0x04,0x00,0x0f,0x00,0x08,0xe8,0x04,
+0x00,0x0f,0x00,0x08,0xf0,0x04,0x00,0x0f,0x00,0x08,0xf8,0x04,0x00,0x0f,0x00,0x08,
+0x00,0x05,0x00,0x0f,0x00,0x08,0x08,0x05,0x00,0x0f,0x00,0x08,0x10,0x05,0x00,0x0f,
+0x00,0x08,0x18,0x05,0x00,0x0f,0x00,0x08,0x20,0x05,0x00,0x0f,0x00,0x08,0x28,0x05,
+0x00,0x0f,0x00,0x08,0x30,0x05,0x00,0x0f,0x00,0x08,0x38,0x05,0x00,0x0f,0x00,0x08,
+0x40,0x05,0x00,0x0f,0x00,0x08,0x48,0x05,0x00,0x0f,0x00,0x08,0x50,0x05,0x00,0x0f,
+0x00,0x08,0x58,0x05,0x00,0x0f,0x00,0x08,0x60,0x05,0x00,0x0f,0x00,0x08,0x68,0x05,
+0x00,0x0f,0x00,0x08,0x70,0x05,0x00,0x0f,0x00,0x08,0x78,0x05,0x00,0x0f,0x00,0x08,
+0x80,0x05,0x00,0x0f,0x00,0x08,0x88,0x05,0x00,0x0f,0x00,0x08,0x90,0x05,0x00,0x0f,
+0x00,0x08,0x98,0x05,0x00,0x0f,0x00,0x08,0xa0,0x05,0x00,0x0f,0x00,0x08,0xa8,0x05,
+0x00,0x0f,0x00,0x08,0xb0,0x05,0x00,0x0f,0x00,0x08,0xb8,0x05,0x00,0x0f,0x00,0x08,
+0xc0,0x05,0x00,0x0f,0x00,0x08,0xc8,0x05,0x00,0x0f,0x00,0x08,0xd0,0x05,0x00,0x0f,
+0x00,0x08,0xd8,0x05,0x00,0x0f,0x00,0x08,0xe0,0x05,0x00,0x0f,0x00,0x08,0xe8,0x05,
+0x00,0x0f,0x00,0x08,0xf0,0x05,0x00,0x0f,0x00,0x08,0xf8,0x05,0x00,0x0f,0x00,0x08,
+0x00,0x06,0x00,0x0f,0x00,0x08,0x08,0x06,0x00,0x0f,0x00,0x08,0x10,0x06,0x00,0x0f,
+0x00,0x08,0x18,0x06,0x00,0x0f,0x00,0x08,0x20,0x06,0x00,0x0f,0x00,0x08,0x28,0x06,
+0x00,0x0f,0x00,0x08,0x30,0x06,0x00,0x0f,0x00,0x08,0x38,0x06,0x00,0x0f,0x00,0x08,
+0x40,0x06,0x00,0x0f,0x00,0x08,0x48,0x06,0x00,0x0f,0x00,0x08,0x50,0x06,0x00,0x0f,
+0x00,0x08,0x58,0x06,0x00,0x0f,0x00,0x08,0x60,0x06,0x00,0x0f,0x00,0x08,0x68,0x06,
+0x00,0x0f,0x00,0x08,0x70,0x06,0x00,0x0f,0x00,0x08,0x78,0x06,0x00,0x0f,0x00,0x08,
+0x80,0x06,0x00,0x0f,0x00,0x08,0x88,0x06,0x00,0x0f,0x00,0x08,0x90,0x06,0x00,0x0f,
+0x00,0x08,0x98,0x06,0x00,0x0f,0x00,0x08,0xa0,0x06,0x00,0x0f,0x00,0x08,0xa8,0x06,
+0x00,0x0f,0x00,0x08,0xb0,0x06,0x00,0x0f,0x00,0x08,0xb8,0x06,0x00,0x0f,0x00,0x08,
+0xc0,0x06,0x00,0x0f,0x00,0x08,0xc8,0x06,0x00,0x0f,0x00,0x08,0xd0,0x06,0x00,0x0f,
+0x00,0x08,0xd8,0x06,0x00,0x0f,0x00,0x08,0xe0,0x06,0x00,0x0f,0x00,0x08,0xe8,0x06,
+0x00,0x0f,0x00,0x08,0xf0,0x06,0x00,0x0f,0x00,0x08,0xf8,0x06,0x00,0x0f,0x00,0x08,
+0x00,0x07,0x00,0x0f,0x00,0x08,0x08,0x07,0x00,0x0f,0x00,0x08,0x10,0x07,0x00,0x0f,
+0x00,0x08,0x18,0x07,0x00,0x0f,0x00,0x08,0x20,0x07,0x00,0x0f,0x00,0x08,0x28,0x07,
+0x00,0x0f,0x00,0x08,0x30,0x07,0x00,0x0f,0x00,0x08,0x38,0x07,0x00,0x0f,0x00,0x08,
+0x40,0x07,0x00,0x0f,0x00,0x08,0x48,0x07,0x00,0x0f,0x00,0x08,0x50,0x07,0x00,0x0f,
+0x00,0x08,0x58,0x07,0x00,0x0f,0x00,0x08,0x60,0x07,0x00,0x0f,0x00,0x08,0x68,0x07,
+0x00,0x0f,0x00,0x08,0x70,0x07,0x00,0x0f,0x00,0x08,0x78,0x07,0x00,0x0f,0x00,0x08,
+0x80,0x07,0x00,0x0f,0x00,0x08,0x88,0x07,0x00,0x0f,0x00,0x08,0x90,0x07,0x00,0x0f,
+0x00,0x08,0x98,0x07,0x00,0x0f,0x00,0x08,0xa0,0x07,0x00,0x0f,0x00,0x08,0xa8,0x07,
+0x00,0x0f,0x00,0x08,0xb0,0x07,0x00,0x0f,0x00,0x08,0xb8,0x07,0x00,0x0f,0x00,0x08,
+0xc0,0x07,0x00,0x0f,0x00,0x08,0xc8,0x07,0x00,0x0f,0x00,0x08,0xd0,0x07,0x00,0x0f,
+0x00,0x08,0xd8,0x07,0x00,0x0f,0x00,0x08,0xe0,0x07,0x00,0x0f,0x00,0x08,0xe8,0x07,
+0x00,0x0f,0x00,0x08,0xf0,0x07,0x00,0x0f,0x00,0x08,0xf8,0x07,0x00,0x0f,0x00,0x08,
+0x00,0x08,0x00,0x0f,0x00,0x08,
+};
+
+int	sizeofdefont = sizeof defontdata;
+
+void
+_unpackinfo(Fontchar *fc, uchar *p, int n)
+{
+	int j;
+
+	for(j=0;  j<=n;  j++){
+		fc->x = p[0]|(p[1]<<8);
+		fc->top = p[2];
+		fc->bottom = p[3];
+		fc->left = p[4];
+		fc->width = p[5];
+		fc++;
+		p += 6;
+	}
+}
--- /dev/null
+++ b/sys/src/libdraw/draw.c
@@ -1,0 +1,69 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+void
+_setdrawop(Display *d, Drawop op)
+{
+	uchar *a;
+
+	if(op != SoverD){
+		a = bufimage(d, 1+1);
+		if(a == nil)
+			return;
+		a[0] = 'O';
+		a[1] = op;
+	}
+}
+		
+static void
+draw1(Image *dst, Rectangle *r, Image *src, Point *p0, Image *mask, Point *p1, Drawop op)
+{
+	uchar *a;
+
+	_setdrawop(dst->display, op);
+
+	a = bufimage(dst->display, 1+4+4+4+4*4+2*4+2*4);
+	if(a == nil)
+		return;
+	if(src == nil)
+		src = dst->display->black;
+	if(mask == nil)
+		mask = dst->display->opaque;
+	a[0] = 'd';
+	BPLONG(a+1, dst->id);
+	BPLONG(a+5, src->id);
+	BPLONG(a+9, mask->id);
+	BPLONG(a+13, r->min.x);
+	BPLONG(a+17, r->min.y);
+	BPLONG(a+21, r->max.x);
+	BPLONG(a+25, r->max.y);
+	BPLONG(a+29, p0->x);
+	BPLONG(a+33, p0->y);
+	BPLONG(a+37, p1->x);
+	BPLONG(a+41, p1->y);
+}
+
+void
+draw(Image *dst, Rectangle r, Image *src, Image *mask, Point p1)
+{
+	draw1(dst, &r, src, &p1, mask, &p1, SoverD);
+}
+
+void
+drawop(Image *dst, Rectangle r, Image *src, Image *mask, Point p1, Drawop op)
+{
+	draw1(dst, &r, src, &p1, mask, &p1, op);
+}
+
+void
+gendraw(Image *dst, Rectangle r, Image *src, Point p0, Image *mask, Point p1)
+{
+	draw1(dst, &r, src, &p0, mask, &p1, SoverD);
+}
+
+void
+gendrawop(Image *dst, Rectangle r, Image *src, Point p0, Image *mask, Point p1, Drawop op)
+{
+	draw1(dst, &r, src, &p0, mask, &p1, op);
+}
--- /dev/null
+++ b/sys/src/libdraw/drawrepl.c
@@ -1,0 +1,23 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+drawreplxy(int min, int max, int x)
+{
+	int sx;
+
+	sx = (x-min)%(max-min);
+	if(sx < 0)
+		sx += max-min;
+	return sx+min;
+}
+
+Point
+drawrepl(Rectangle r, Point p)
+{
+	p.x = drawreplxy(r.min.x, r.max.x, p.x);
+	p.y = drawreplxy(r.min.y, r.max.y, p.y);
+	return p;
+}
+
--- /dev/null
+++ b/sys/src/libdraw/eenter.c
@@ -1,0 +1,241 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <keyboard.h>
+
+int
+eenter(char *ask, char *buf, int len, Mouse *m)
+{
+	int done, down, tick, n, h, w, l, i;
+	Image *b, *save, *backcol, *bordcol, *txtcol;
+	Point p, o, t;
+	Rectangle r, sc;
+	Event ev;
+	Rune k;
+
+	o = screen->r.min;
+
+	enum{
+		Cback,
+		Cbord,
+		Ctext,
+		Ncols,
+	};
+	Theme th[Ncols] = {
+		[Cback] { "menuback",	0xEAFFEAFF },
+		[Cbord]	{ "menubord",	DMedgreen },
+		[Ctext]	{ "menutext",	DBlack },
+	};
+	readtheme(th, nelem(th), nil);
+	backcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cback].c);
+	bordcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cbord].c);
+	txtcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Ctext].c);
+	if(backcol == nil || bordcol == nil || txtcol == nil)
+		return -1;
+
+	while(ecankbd())
+		ekbd();
+
+	if(m) o = m->xy;
+
+	if(buf && len > 0)
+		n = strlen(buf);
+	else {
+		buf = nil;
+		len = 0;
+		n = 0;
+	}
+
+	k = -1;
+	tick = n;
+	save = nil;
+	done = down = 0;
+
+	p = stringsize(font, " ");
+	h = p.y;
+	w = p.x;
+
+	b = screen;
+	sc = b->clipr;
+	replclipr(b, 0, b->r);
+
+	while(!done){
+		p = stringsize(font, buf ? buf : "");
+		if(ask && ask[0]){
+			if(buf) p.x += w;
+			p.x += stringwidth(font, ask);
+		}
+		r = rectaddpt(insetrect(Rpt(ZP, p), -4), o);
+		p.x = 0;
+		r = rectsubpt(r, p);
+
+		p = ZP;
+		if(r.min.x < screen->r.min.x)
+			p.x = screen->r.min.x - r.min.x;
+		if(r.min.y < screen->r.min.y)
+			p.y = screen->r.min.y - r.min.y;
+		r = rectaddpt(r, p);
+		p = ZP;
+		if(r.max.x > screen->r.max.x)
+			p.x = r.max.x - screen->r.max.x;
+		if(r.max.y > screen->r.max.y)
+			p.y = r.max.y - screen->r.max.y;
+		r = rectsubpt(r, p);
+
+		r = insetrect(r, -2);
+		if(save == nil){
+			save = allocimage(display, r, b->chan, 0, DNofill);
+			if(save == nil){
+				n = -1;
+				break;
+			}
+			draw(save, r, b, nil, r.min);
+		}
+		draw(b, r, backcol, nil, ZP);
+		border(b, r, 2, bordcol, ZP);
+		p = addpt(r.min, Pt(6, 6));
+		if(ask && ask[0]){
+			p = string(b, p, bordcol, ZP, font, ask);
+			if(buf) p.x += w;
+		}
+		if(buf){
+			t = p;
+			p = stringn(b, p, txtcol, ZP, font, buf, utfnlen(buf, tick));
+			draw(b, Rect(p.x-1, p.y, p.x+2, p.y+3), txtcol, nil, ZP);
+			draw(b, Rect(p.x, p.y, p.x+1, p.y+h), txtcol, nil, ZP);
+			draw(b, Rect(p.x-1, p.y+h-3, p.x+2, p.y+h), txtcol, nil, ZP);
+			p = string(b, p, txtcol, ZP, font, buf+tick);
+		}
+		flushimage(display, 1);
+
+nodraw:
+		i = Ekeyboard;
+		if(m != nil)
+			i |= Emouse;
+
+		replclipr(b, 0, sc);
+		i = eread(i, &ev);
+
+		/* screen might have been resized */
+		if(b != screen || !eqrect(screen->clipr, sc)){
+			freeimage(save);
+			save = nil;
+		}
+		b = screen;
+		sc = b->clipr;
+		replclipr(b, 0, b->r);
+
+		switch(i){
+		default:
+			done = 1;
+			n = -1;
+			break;
+		case Ekeyboard:
+			k = ev.kbdc;
+			if(buf == nil || k == Keof || k == '\n'){
+				done = 1;
+				break;
+			}
+			if(k == Knack || k == Kesc){
+				done = !n;
+				buf[n = tick = 0] = 0;
+				break;
+			}
+			if(k == Ksoh || k == Khome){
+				tick = 0;
+				continue;
+			}
+			if(k == Kenq || k == Kend){
+				tick = n;
+				continue;
+			}
+			if(k == Kright){
+				if(tick < n)
+					tick += chartorune(&k, buf+tick);
+				continue;
+			}
+			if(k == Kleft){
+				for(i = 0; i < n; i += l){
+					l = chartorune(&k, buf+i);
+					if(i+l >= tick){
+						tick = i;
+						break;
+					}
+				}
+				continue;
+			}
+			if(k == Ketb){
+				l = tick;
+				while(tick > 0){
+					tick--;
+					if(tick == 0 ||
+						strchr(" !\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", buf[tick-1]))
+						break;
+				}
+				memmove(buf+tick, buf+l, n-l);
+				buf[n -= l-tick] = 0;
+				break;
+			}
+			if(k == Kbs){
+				if(tick <= 0)
+					continue;
+				for(i = 0; i < n; i += l){
+					l = chartorune(&k, buf+i);
+					if(i+l >= tick){
+						memmove(buf+i, buf+i+l, n - (i+l));
+						buf[n -= l] = 0;
+						tick -= l;
+						break;
+					}
+				}
+				break;
+			}
+			if(k < 0x20 || k == Kdel || (k & 0xFF00) == KF || (k & 0xFF00) == Spec)
+				continue;
+			if((len-n) <= (l = runelen(k)))
+				continue;
+			memmove(buf+tick+l, buf+tick, n - tick);
+			runetochar(buf+tick, &k);
+			buf[n += l] = 0;
+			tick += l;
+			break;
+		case Emouse:
+			*m = ev.mouse;
+			if(!ptinrect(m->xy, r)){
+				down = 0;
+				goto nodraw;
+			}
+			if(m->buttons & 7){
+				down = 1;
+				if(buf && m->xy.x >= (t.x - w)){
+					down = 0;
+					for(i = 0; i < n; i += l){
+						l = chartorune(&k, buf+i);
+						t.x += stringnwidth(font, buf+i, 1);
+						if(t.x > m->xy.x)
+							break;
+					}
+					tick = i;
+				}
+				continue;
+			}
+			done = down;
+			break;
+		}
+		if(save){
+			draw(b, save->r, save, nil, save->r.min);
+			freeimage(save);
+			save = nil;
+		}
+	}
+
+	replclipr(b, 0, sc);
+
+	freeimage(backcol);
+	freeimage(bordcol);
+	flushimage(display, 1);
+
+	return n;
+}
+
--- /dev/null
+++ b/sys/src/libdraw/egetrect.c
@@ -1,0 +1,116 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <cursor.h>
+#include <event.h>
+
+#define	W	Borderwidth
+
+static Image *tmp[4];
+static Image *red;
+
+static Cursor sweep={
+	{-7, -7},
+	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x07,
+	 0xE0, 0x07, 0xE0, 0x07, 0xE3, 0xF7, 0xE3, 0xF7,
+	 0xE3, 0xE7, 0xE3, 0xF7, 0xE3, 0xFF, 0xE3, 0x7F,
+	 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,},
+	{0x00, 0x00, 0x7F, 0xFE, 0x40, 0x02, 0x40, 0x02,
+	 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x41, 0xE2,
+	 0x41, 0xC2, 0x41, 0xE2, 0x41, 0x72, 0x40, 0x38,
+	 0x40, 0x1C, 0x40, 0x0E, 0x7F, 0xE6, 0x00, 0x00,}
+};
+
+static
+void
+brects(Rectangle r, Rectangle rp[4])
+{
+	if(Dx(r) < 2*W)
+		r.max.x = r.min.x+2*W;
+	if(Dy(r) < 2*W)
+		r.max.y = r.min.y+2*W;
+	rp[0] = Rect(r.min.x, r.min.y, r.max.x, r.min.y+W);
+	rp[1] = Rect(r.min.x, r.max.y-W, r.max.x, r.max.y);
+	rp[2] = Rect(r.min.x, r.min.y+W, r.min.x+W, r.max.y-W);
+	rp[3] = Rect(r.max.x-W, r.min.y+W, r.max.x, r.max.y-W);
+}
+
+Rectangle
+egetrect(int but, Mouse *m)
+{
+	Rectangle r, rc;
+
+	but = 1<<(but-1);
+	esetcursor(&sweep);
+	while(m->buttons)
+		*m = emouse();
+	while(!(m->buttons & but)){
+		*m = emouse();
+		if(m->buttons & (7^but))
+			goto Return;
+	}
+	r.min = m->xy;
+	r.max = m->xy;
+	do{
+		rc = canonrect(r);
+		edrawgetrect(rc, 1);
+		*m = emouse();
+		edrawgetrect(rc, 0);
+		r.max = m->xy;
+	}while(m->buttons == but);
+
+    Return:
+	esetcursor(0);
+	if(m->buttons & (7^but)){
+		rc.min.x = rc.max.x = 0;
+		rc.min.y = rc.max.y = 0;
+		while(m->buttons)
+			*m = emouse();
+	}
+	return rc;
+}
+
+static
+void
+freetmp(void)
+{
+	freeimage(tmp[0]);
+	freeimage(tmp[1]);
+	freeimage(tmp[2]);
+	freeimage(tmp[3]);
+	freeimage(red);
+	tmp[0] = tmp[1] = tmp[2] = tmp[3] = red = nil;
+}
+
+void
+edrawgetrect(Rectangle rc, int up)
+{
+	int i;
+	Rectangle r, rects[4];
+
+	if(up && tmp[0]!=nil)
+		if(Dx(tmp[0]->r)<Dx(rc) || Dy(tmp[2]->r)<Dy(rc))
+			freetmp();
+
+	if(tmp[0] == 0){
+		r = Rect(0, 0, Dx(screen->r), W);
+		tmp[0] = allocimage(display, r, screen->chan, 0, -1);
+		tmp[1] = allocimage(display, r, screen->chan, 0, -1);
+		r = Rect(0, 0, W, Dy(screen->r));
+		tmp[2] = allocimage(display, r, screen->chan, 0, -1);
+		tmp[3] = allocimage(display, r, screen->chan, 0, -1);
+		red = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DRed);
+		if(tmp[0]==0 || tmp[1]==0 || tmp[2]==0 || tmp[3]==0 || red==0)
+			drawerror(display, "getrect: allocimage failed");
+	}
+	brects(rc, rects);
+	if(!up){
+		for(i=0; i<4; i++)
+			draw(screen, rects[i], tmp[i], nil, ZP);
+		return;
+	}
+	for(i=0; i<4; i++){
+		draw(tmp[i], Rect(0, 0, Dx(rects[i]), Dy(rects[i])), screen, nil, rects[i].min);
+		draw(screen, rects[i], red, nil, ZP);
+	}
+}
--- /dev/null
+++ b/sys/src/libdraw/ellipse.c
@@ -1,0 +1,82 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static
+void
+doellipse(int cmd, Image *dst, Point *c, int xr, int yr, int thick, Image *src, Point *sp, int alpha, int phi, Drawop op)
+{
+	uchar *a;
+
+	_setdrawop(dst->display, op);
+
+	a = bufimage(dst->display, 1+4+4+2*4+4+4+4+2*4+2*4);
+	if(a == nil){
+		fprint(2, "image ellipse: %r\n");
+		return;
+	}
+	a[0] = cmd;
+	BPLONG(a+1, dst->id);
+	BPLONG(a+5, src->id);
+	BPLONG(a+9, c->x);
+	BPLONG(a+13, c->y);
+	BPLONG(a+17, xr);
+	BPLONG(a+21, yr);
+	BPLONG(a+25, thick);
+	BPLONG(a+29, sp->x);
+	BPLONG(a+33, sp->y);
+	BPLONG(a+37, alpha);
+	BPLONG(a+41, phi);
+}
+
+void
+ellipse(Image *dst, Point c, int a, int b, int thick, Image *src, Point sp)
+{
+	doellipse('e', dst, &c, a, b, thick, src, &sp, 0, 0, SoverD);
+}
+
+void
+ellipseop(Image *dst, Point c, int a, int b, int thick, Image *src, Point sp, Drawop op)
+{
+	doellipse('e', dst, &c, a, b, thick, src, &sp, 0, 0, op);
+}
+
+void
+fillellipse(Image *dst, Point c, int a, int b, Image *src, Point sp)
+{
+	doellipse('E', dst, &c, a, b, 0, src, &sp, 0, 0, SoverD);
+}
+
+void
+fillellipseop(Image *dst, Point c, int a, int b, Image *src, Point sp, Drawop op)
+{
+	doellipse('E', dst, &c, a, b, 0, src, &sp, 0, 0, op);
+}
+
+void
+arc(Image *dst, Point c, int a, int b, int thick, Image *src, Point sp, int alpha, int phi)
+{
+	alpha |= 1<<31;
+	doellipse('e', dst, &c, a, b, thick, src, &sp, alpha, phi, SoverD);
+}
+
+void
+arcop(Image *dst, Point c, int a, int b, int thick, Image *src, Point sp, int alpha, int phi, Drawop op)
+{
+	alpha |= 1<<31;
+	doellipse('e', dst, &c, a, b, thick, src, &sp, alpha, phi, op);
+}
+
+void
+fillarc(Image *dst, Point c, int a, int b, Image *src, Point sp, int alpha, int phi)
+{
+	alpha |= 1<<31;
+	doellipse('E', dst, &c, a, b, 0, src, &sp, alpha, phi, SoverD);
+}
+
+void
+fillarcop(Image *dst, Point c, int a, int b, Image *src, Point sp, int alpha, int phi, Drawop op)
+{
+	alpha |= 1<<31;
+	doellipse('E', dst, &c, a, b, 0, src, &sp, alpha, phi, op);
+}
--- /dev/null
+++ b/sys/src/libdraw/emenuhit.c
@@ -1,0 +1,290 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+
+enum
+{
+	Margin = 4,		/* outside to text */
+	Border = 2,		/* outside to selection boxes */
+	Blackborder = 2,	/* width of outlining border */
+	Vspacing = 2,		/* extra spacing between lines of text */
+	Maxunscroll = 25,	/* maximum #entries before scrolling turns on */
+	Nscroll = 20,		/* number entries in scrolling part */
+	Scrollwid = 14,		/* width of scroll bar */
+	Gap = 4,			/* between text and scroll bar */
+};
+
+static	Image	*menutxt;
+static	Image	*back;
+static	Image	*high;
+static	Image	*bord;
+static	Image	*text;
+static	Image	*htext;
+
+static
+void
+menucolors(void)
+{
+	enum{
+		Cback,
+		Chigh,
+		Cbord,
+		Ctext,
+		Chtext,
+		Cmtext,
+		Ncols,
+	};
+	Theme th[Ncols] = {
+		[Cback] { "menuback",	0xEAFFEAFF },
+		[Chigh] { "menuhigh",	DDarkgreen },
+		[Cbord]	{ "menubord",	DMedgreen },
+		[Ctext]	{ "menutext",	DBlack },
+		[Chtext]{ "menuhtext",	0xEAFFEAFF },
+		[Cmtext]{ "menubar",	DDarkgreen },
+	};
+
+	readtheme(th, nelem(th), nil);
+	back = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cback].c);
+	high = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Chigh].c);
+	bord = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cbord].c);
+	text = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Ctext].c);
+	htext = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Chtext].c);
+	menutxt = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cmtext].c);
+	if(back == nil || high == nil || bord == nil
+	|| text == nil || htext == nil || menutxt == nil)
+		goto Error;
+	return;
+
+    Error:
+	freeimage(back);
+	freeimage(high);
+	freeimage(bord);
+	freeimage(menutxt);
+	back = display->white;
+	high = display->black;
+	bord = display->black;
+	text = display->black;
+	htext = display->white;
+	menutxt = display->black;
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the rectangle, including its black edge, holding element i.
+ */
+static Rectangle
+menurect(Rectangle r, int i)
+{
+	if(i < 0)
+		return Rect(0, 0, 0, 0);
+	r.min.y += (font->height+Vspacing)*i;
+	r.max.y = r.min.y+font->height+Vspacing;
+	return insetrect(r, Border-Margin);
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the element number containing p.
+ */
+static int
+menusel(Rectangle r, Point p)
+{
+	if(!ptinrect(p, r))
+		return -1;
+	return (p.y-r.min.y)/(font->height+Vspacing);
+}
+
+static
+void
+paintitem(Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore)
+{
+	char *item;
+	Rectangle r;
+	Point pt;
+
+	if(i < 0)
+		return;
+	r = menurect(textr, i);
+	if(restore){
+		draw(screen, r, restore, nil, restore->r.min);
+		return;
+	}
+	if(save)
+		draw(save, save->r, screen, nil, r.min);
+	item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
+	pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2;
+	pt.y = textr.min.y+i*(font->height+Vspacing);
+	draw(screen, r, highlight? high : back, nil, pt);
+	string(screen, pt, highlight? htext : text, pt, font, item);
+}
+
+/*
+ * menur is a rectangle holding all the highlightable text elements.
+ * track mouse while inside the box, return what's selected when button
+ * is raised, -1 as soon as it leaves box.
+ * invariant: nothing is highlighted on entry or exit.
+ */
+static int
+menuscan(Menu *menu, int but, Mouse *m, Rectangle textr, int off, int lasti, Image *save)
+{
+	int i;
+
+	paintitem(menu, textr, off, lasti, 1, save, nil);
+	flushimage(display, 1);	/* in case display->locking is set */
+	*m = emouse();
+	while(m->buttons & (1<<(but-1))){
+		flushimage(display, 1);	/* in case display->locking is set */
+		*m = emouse();
+		i = menusel(textr, m->xy);
+		if(i != -1 && i == lasti)
+			continue;
+		paintitem(menu, textr, off, lasti, 0, nil, save);
+		if(i == -1)
+			return i;
+		lasti = i;
+		paintitem(menu, textr, off, lasti, 1, save, nil);
+	}
+	return lasti;
+}
+
+static void
+menupaint(Menu *menu, Rectangle textr, int off, int nitemdrawn)
+{
+	int i;
+
+	draw(screen, insetrect(textr, Border-Margin), back, nil, ZP);
+	for(i = 0; i<nitemdrawn; i++)
+		paintitem(menu, textr, off, i, 0, nil, nil);
+}
+
+static void
+menuscrollpaint(Rectangle scrollr, int off, int nitem, int nitemdrawn)
+{
+	Rectangle r;
+
+	draw(screen, scrollr, back, nil, ZP);
+	r.min.x = scrollr.min.x;
+	r.max.x = scrollr.max.x;
+	r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
+	r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
+	if(r.max.y < r.min.y+2)
+		r.max.y = r.min.y+2;
+	border(screen, r, 1, bord, ZP);
+	if(menutxt)
+		draw(screen, insetrect(r, 1), menutxt, nil, ZP);
+}
+
+int
+emenuhit(int but, Mouse *m, Menu *menu)
+{
+	int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
+	int scrolling;
+	Rectangle r, menur, sc, textr, scrollr;
+	Image *b, *save;
+	Point pt;
+	char *item;
+
+	if(back == nil)
+		menucolors();
+	sc = screen->clipr;
+	replclipr(screen, 0, screen->r);
+	maxwid = 0;
+	for(nitem = 0;
+	    item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
+	    nitem++){
+		i = stringwidth(font, item);
+		if(i > maxwid)
+			maxwid = i;
+	}
+	if(menu->lasthit<0 || menu->lasthit>=nitem)
+		menu->lasthit = 0;
+	screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
+	if(nitem>Maxunscroll || nitem>screenitem){
+		scrolling = 1;
+		nitemdrawn = Nscroll;
+		if(nitemdrawn > screenitem)
+			nitemdrawn = screenitem;
+		wid = maxwid + Gap + Scrollwid;
+		off = menu->lasthit - nitemdrawn/2;
+		if(off < 0)
+			off = 0;
+		if(off > nitem-nitemdrawn)
+			off = nitem-nitemdrawn;
+		lasti = menu->lasthit-off;
+	}else{
+		scrolling = 0;
+		nitemdrawn = nitem;
+		wid = maxwid;
+		off = 0;
+		lasti = menu->lasthit;
+	}
+	r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
+	r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
+	r = rectaddpt(r, m->xy);
+	pt = ZP;
+	if(r.max.x>screen->r.max.x)
+		pt.x = screen->r.max.x-r.max.x;
+	if(r.max.y>screen->r.max.y)
+		pt.y = screen->r.max.y-r.max.y;
+	if(r.min.x<screen->r.min.x)
+		pt.x = screen->r.min.x-r.min.x;
+	if(r.min.y<screen->r.min.y)
+		pt.y = screen->r.min.y-r.min.y;
+	menur = rectaddpt(r, pt);
+	textr.max.x = menur.max.x-Margin;
+	textr.min.x = textr.max.x-maxwid;
+	textr.min.y = menur.min.y+Margin;
+	textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
+	if(scrolling){
+		scrollr = insetrect(menur, Border);
+		scrollr.max.x = scrollr.min.x+Scrollwid;
+	}else
+		scrollr = Rect(0, 0, 0, 0);
+
+	b = allocimage(display, menur, screen->chan, 0, 0);
+	if(b == 0)
+		b = screen;
+	draw(b, menur, screen, nil, menur.min);
+	draw(screen, menur, back, nil, ZP);
+	border(screen, menur, Blackborder, bord, ZP);
+	save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
+	r = menurect(textr, lasti);
+	if(pt.x || pt.y)
+		emoveto(divpt(addpt(r.min, r.max), 2));
+	menupaint(menu, textr, off, nitemdrawn);
+	if(scrolling)
+		menuscrollpaint(scrollr, off, nitem, nitemdrawn);
+	while(m->buttons & (1<<(but-1))){
+		lasti = menuscan(menu, but, m, textr, off, lasti, save);
+		if(lasti >= 0)
+			break;
+		while(!ptinrect(m->xy, textr) && (m->buttons & (1<<(but-1)))){
+			if(scrolling && ptinrect(m->xy, scrollr)){
+				noff = ((m->xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
+				noff -= nitemdrawn/2;
+				if(noff < 0)
+					noff = 0;
+				if(noff > nitem-nitemdrawn)
+					noff = nitem-nitemdrawn;
+				if(noff != off){
+					off = noff;
+					menupaint(menu, textr, off, nitemdrawn);
+					menuscrollpaint(scrollr, off, nitem, nitemdrawn);
+				}
+			}
+			flushimage(display, 1);	/* in case display->locking is set */
+			*m = emouse();
+		}
+	}
+	draw(screen, menur, b, nil, menur.min);
+	if(b != screen)
+		freeimage(b);
+	freeimage(save);
+	replclipr(screen, 0, sc);
+	if(lasti >= 0){
+		menu->lasthit = lasti+off;
+		return menu->lasthit;
+	}
+	return -1;
+}
--- /dev/null
+++ b/sys/src/libdraw/enter.c
@@ -1,0 +1,251 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <keyboard.h>
+
+int
+enter(char *ask, char *buf, int len, Mousectl *mc, Keyboardctl *kc, Screen *scr)
+{
+	int done, down, tick, n, h, w, l, i;
+	Image *b, *save, *backcol, *bordcol, *txtcol;
+	Point p, o, t;
+	Rectangle r, sc;
+	Alt a[3];
+	Mouse m;
+	Rune k;
+
+	o = screen->r.min;
+
+	enum{
+		Cback,
+		Cbord,
+		Ctext,
+		Ncols,
+	};
+	Theme th[Ncols] = {
+		[Cback] { "menuback",	0xEAFFEAFF },
+		[Cbord]	{ "menubord",	DMedgreen },
+		[Ctext]	{ "menutext",	DBlack },
+	};
+	readtheme(th, nelem(th), nil);
+	backcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cback].c);
+	bordcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cbord].c);
+	txtcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Ctext].c);
+	if(backcol == nil || bordcol == nil || txtcol == nil)
+		return -1;
+
+	sc = screen->clipr;
+	replclipr(screen, 0, screen->r);
+
+	n = 0;
+	if(kc){
+		while(nbrecv(kc->c, nil) == 1)
+			;
+		a[n].op = CHANRCV;
+		a[n].c = kc->c;
+		a[n].v = &k;
+		n++;
+	}
+	if(mc){
+		o = mc->xy;
+		a[n].op = CHANRCV;
+		a[n].c = mc->c;
+		a[n].v = &m;
+		n++;
+	}
+	a[n].op = CHANEND;
+	a[n].c = nil;
+	a[n].v = nil;
+
+	if(buf && len > 0)
+		n = strlen(buf);
+	else {
+		buf = nil;
+		len = 0;
+		n = 0;
+	}
+
+	k = -1;
+	b = nil;
+	tick = n;
+	save = nil;
+	done = down = 0;
+
+	p = stringsize(font, " ");
+	h = p.y;
+	w = p.x;
+
+	while(!done){
+		p = stringsize(font, buf ? buf : "");
+		if(ask && ask[0]){
+			if(buf) p.x += w;
+			p.x += stringwidth(font, ask);
+		}
+		r = rectaddpt(insetrect(Rpt(ZP, p), -4), o);
+		p.x = 0;
+		r = rectsubpt(r, p);
+
+		p = ZP;
+		if(r.min.x < screen->r.min.x)
+			p.x = screen->r.min.x - r.min.x;
+		if(r.min.y < screen->r.min.y)
+			p.y = screen->r.min.y - r.min.y;
+		r = rectaddpt(r, p);
+		p = ZP;
+		if(r.max.x > screen->r.max.x)
+			p.x = r.max.x - screen->r.max.x;
+		if(r.max.y > screen->r.max.y)
+			p.y = r.max.y - screen->r.max.y;
+		r = rectsubpt(r, p);
+
+		r = insetrect(r, -2);
+		if(scr){
+			if(b == nil)
+				b = allocwindow(scr, r, Refbackup, DWhite);
+			if(b == nil)
+				scr = nil;
+		}
+		if(scr == nil && save == nil){
+			if(b == nil)
+				b = screen;
+			save = allocimage(display, r, b->chan, 0, DNofill);
+			if(save == nil){
+				n = -1;
+				break;
+			}
+			draw(save, r, b, nil, r.min);
+		}
+		draw(b, r, backcol, nil, ZP);
+		border(b, r, 2, bordcol, ZP);
+		p = addpt(r.min, Pt(6, 6));
+		if(ask && ask[0]){
+			p = string(b, p, bordcol, ZP, font, ask);
+			if(buf) p.x += w;
+		}
+		if(buf){
+			t = p;
+			p = stringn(b, p, txtcol, ZP, font, buf, utfnlen(buf, tick));
+			draw(b, Rect(p.x-1, p.y, p.x+2, p.y+3), txtcol, nil, ZP);
+			draw(b, Rect(p.x, p.y, p.x+1, p.y+h), txtcol, nil, ZP);
+			draw(b, Rect(p.x-1, p.y+h-3, p.x+2, p.y+h), txtcol, nil, ZP);
+			p = string(b, p, txtcol, ZP, font, buf+tick);
+		}
+		flushimage(display, 1);
+
+nodraw:
+		switch(alt(a)){
+		case -1:
+			done = 1;
+			n = -1;
+			break;
+		case 0:
+			if(buf == nil || k == Keof || k == '\n'){
+				done = 1;
+				break;
+			}
+			if(k == Knack || k == Kesc){
+				done = !n;
+				buf[n = tick = 0] = 0;
+				break;
+			}
+			if(k == Ksoh || k == Khome){
+				tick = 0;
+				continue;
+			}
+			if(k == Kenq || k == Kend){
+				tick = n;
+				continue;
+			}
+			if(k == Kright){
+				if(tick < n)
+					tick += chartorune(&k, buf+tick);
+				continue;
+			}
+			if(k == Kleft){
+				for(i = 0; i < n; i += l){
+					l = chartorune(&k, buf+i);
+					if(i+l >= tick){
+						tick = i;
+						break;
+					}
+				}
+				continue;
+			}
+			if(k == Ketb){
+				l = tick;
+				while(tick > 0){
+					tick--;
+					if(tick == 0 ||
+						strchr(" !\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", buf[tick-1]))
+						break;
+				}
+				memmove(buf+tick, buf+l, n-l);
+				buf[n -= l-tick] = 0;
+				break;
+			}
+			if(k == Kbs){
+				if(tick <= 0)
+					continue;
+				for(i = 0; i < n; i += l){
+					l = chartorune(&k, buf+i);
+					if(i+l >= tick){
+						memmove(buf+i, buf+i+l, n - (i+l));
+						buf[n -= l] = 0;
+						tick -= l;
+						break;
+					}
+				}
+				break;
+			}
+			if(k < 0x20 || k == Kdel || (k & 0xFF00) == KF || (k & 0xFF00) == Spec)
+				continue;
+			if((len-n) <= (l = runelen(k)))
+				continue;
+			memmove(buf+tick+l, buf+tick, n - tick);
+			runetochar(buf+tick, &k);
+			buf[n += l] = 0;
+			tick += l;
+			break;
+		case 1:
+			if(!ptinrect(m.xy, r)){
+				down = 0;
+				goto nodraw;
+			}
+			if(m.buttons & 7){
+				down = 1;
+				if(buf && m.xy.x >= (t.x - w)){
+					down = 0;
+					for(i = 0; i < n; i += l){
+						l = chartorune(&k, buf+i);
+						t.x += stringnwidth(font, buf+i, 1);
+						if(t.x > m.xy.x)
+							break;
+					}
+					tick = i;
+				}
+				continue;
+			}
+			done = down;
+			break;
+		}
+
+		if(b != screen) {
+			freeimage(b);
+			b = nil;
+		} else {
+			draw(b, save->r, save, nil, save->r.min);
+			freeimage(save);
+			save = nil;
+		}
+	}
+
+	replclipr(screen, 0, sc);
+
+	freeimage(backcol);
+	freeimage(bordcol);
+	flushimage(display, 1);
+
+	return n;
+}
--- /dev/null
+++ b/sys/src/libdraw/event.c
@@ -1,0 +1,475 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <cursor.h>
+#include <event.h>
+
+typedef struct	Slave Slave;
+typedef struct	Ebuf Ebuf;
+
+struct Slave
+{
+	int	pid;
+	Ebuf	*head;		/* queue of messages for this descriptor */
+	Ebuf	*tail;
+	int	(*fn)(int, Event*, uchar*, int);
+};
+
+struct Ebuf
+{
+	Ebuf	*next;
+	int	n;		/* number of bytes in buf */
+	uchar	buf[EMAXMSG];
+};
+
+static	Slave	eslave[MAXSLAVE];
+static	int	Skeyboard = -1;
+static	int	Smouse = -1;
+static	int	Stimer = -1;
+static	int	logfid;
+
+static	int	nslave;
+static	int	parentpid;
+static	int	epipe[2];
+
+static	int	eforkslave(ulong);
+static	void	extract(void);
+static	void	ekill(void);
+static	int	enote(void *, char *);
+
+static	int	mousefd;
+static	int	cursorfd;
+
+static
+Ebuf*
+ebread(Slave *s)
+{
+	Ebuf *eb;
+ 
+	while((eb = s->head) == 0)
+		extract();
+	s->head = eb->next;
+	if(s->head == 0)
+		s->tail = 0;
+	return eb;
+}
+
+ulong
+event(Event *e)
+{
+	return eread(~0UL, e);
+}
+
+ulong
+eread(ulong keys, Event *e)
+{
+	Ebuf *eb;
+	int i, id;
+
+	if(keys == 0)
+		return 0;
+	for(;;){
+		for(i=0; i<nslave; i++)
+			if((keys & (1<<i)) && eslave[i].head){
+				id = 1<<i;
+				if(i == Smouse)
+					e->mouse = emouse();
+				else if(i == Skeyboard)
+					e->kbdc = ekbd();
+				else if(i == Stimer)
+					eslave[i].head = 0;
+				else{
+					eb = ebread(&eslave[i]);
+					e->n = eb->n;
+					if(eslave[i].fn)
+						id = (*eslave[i].fn)(id, e, eb->buf, eb->n);
+					else
+						memmove(e->data, eb->buf, eb->n);
+					free(eb);
+				}
+				return id;
+			}
+		extract();
+	}
+}
+
+int
+ecanmouse(void)
+{
+	if(Smouse < 0)
+		drawerror(display, "events: mouse not initialized");
+	return ecanread(Emouse);
+}
+
+int
+ecankbd(void)
+{
+	if(Skeyboard < 0)
+		drawerror(display, "events: keyboard not initialzed");
+	return ecanread(Ekeyboard);
+}
+
+int
+ecanread(ulong keys)
+{
+	Dir *d;
+	int i;
+	ulong l;
+
+	for(;;){
+		for(i=0; i<nslave; i++)
+			if((keys & (1<<i)) && eslave[i].head)
+				return 1;
+		d = dirfstat(epipe[0]);
+		if(d == nil)
+			drawerror(display, "events: ecanread stat error");
+		l = d->length;
+		free(d);
+		if(l == 0)
+			return 0;
+		extract();
+	}
+}
+
+ulong
+estartfn(ulong key, int fd, int n, int (*fn)(int, Event*, uchar*, int))
+{
+	char buf[EMAXMSG+1];
+	int i, r;
+
+	if(fd < 0)
+		drawerror(display, "events: bad file descriptor");
+	if(n <= 0 || n > EMAXMSG)
+		n = EMAXMSG;
+	i = eforkslave(key);
+	if(i < MAXSLAVE){
+		eslave[i].fn = fn;
+		return 1<<i;
+	}
+	buf[0] = i - MAXSLAVE;
+	while((r = read(fd, buf+1, n))>0)
+		if(write(epipe[1], buf, r+1)!=r+1)
+			break;
+	buf[0] = MAXSLAVE;
+	write(epipe[1], buf, 1);
+	_exits(0);
+	return 0;
+}
+
+ulong
+estart(ulong key, int fd, int n)
+{
+	return estartfn(key, fd, n, nil);
+}
+
+ulong
+etimer(ulong key, int n)
+{
+	char t[2];
+
+	if(Stimer != -1)
+		drawerror(display, "events: timer started twice");
+	Stimer = eforkslave(key);
+	if(Stimer < MAXSLAVE)
+		return 1<<Stimer;
+	if(n <= 0)
+		n = 1000;
+	t[0] = t[1] = Stimer - MAXSLAVE;
+	do
+		sleep(n);
+	while(write(epipe[1], t, 2) == 2);
+	t[0] = MAXSLAVE;
+	write(epipe[1], t, 1);
+	_exits(0);
+	return 0;
+}
+
+static void
+ekeyslave(int fd)
+{
+	Rune r;
+	char t[1+UTFmax], k[10];
+	int kr, kn, w;
+
+	if(eforkslave(Ekeyboard) < MAXSLAVE)
+		return;
+	kn = 0;
+	t[0] = Skeyboard;
+	for(;;){
+		while(!fullrune(k, kn)){
+			kr = read(fd, k+kn, sizeof k - kn);
+			if(kr <= 0)
+				goto breakout;
+			kn += kr;
+		}
+		w = chartorune(&r, k);
+		kn -= w;
+		memmove(t+1, k, w);
+		memmove(k, &k[w], kn);
+		if(write(epipe[1], t, sizeof(t)) != sizeof(t))
+			break;
+	}
+breakout:;
+	t[0] = MAXSLAVE;
+	write(epipe[1], t, 1);
+	_exits(0);
+}
+
+void
+einit(ulong keys)
+{
+	int ctl, fd;
+	char buf[256];
+
+	parentpid = getpid();
+	if(pipe(epipe) < 0)
+		drawerror(display, "events: einit pipe");
+	atexit(ekill);
+	atnotify(enote, 1);
+	snprint(buf, sizeof buf, "%s/mouse", display->devdir);
+	mousefd = open(buf, ORDWR|OCEXEC);
+	if(mousefd < 0)
+		drawerror(display, "einit: can't open mouse\n");
+	snprint(buf, sizeof buf, "%s/cursor", display->devdir);
+	cursorfd = open(buf, ORDWR|OCEXEC);
+	if(cursorfd < 0)
+		drawerror(display, "einit: can't open cursor\n");
+	if(keys&Ekeyboard){
+		snprint(buf, sizeof buf, "%s/cons", display->devdir);
+		fd = open(buf, OREAD);
+		if(fd < 0)
+			drawerror(display, "events: can't open console");
+		snprint(buf, sizeof buf, "%s/consctl", display->devdir);
+		ctl = open("/dev/consctl", OWRITE|OCEXEC);
+		if(ctl < 0)
+			drawerror(display, "events: can't open consctl");
+		write(ctl, "rawon", 5);
+		for(Skeyboard=0; Ekeyboard & ~(1<<Skeyboard); Skeyboard++)
+			;
+		ekeyslave(fd);
+	}
+	if(keys&Emouse){
+		estart(Emouse, mousefd, 1+4*12);
+		for(Smouse=0; Emouse & ~(1<<Smouse); Smouse++)
+			;
+	}
+}
+
+static void
+extract(void)
+{
+	Slave *s;
+	Ebuf *eb;
+	int i, n;
+	uchar ebuf[EMAXMSG+1];
+
+	/* avoid generating a message if there's nothing to show. */
+	/* this test isn't perfect, though; could do flushimage(display, 0) then call extract */
+	/* also: make sure we don't interfere if we're multiprocessing the display */
+	if(display->locking){
+		/* if locking is being done by program, this means it can't depend on automatic flush in emouse() etc. */
+		if(canqlock(&display->qlock)){
+			if(display->bufp > display->buf)
+				flushimage(display, 1);
+			unlockdisplay(display);
+		}
+	}else
+		if(display->bufp > display->buf)
+			flushimage(display, 1);
+loop:
+	if((n=read(epipe[0], ebuf, EMAXMSG+1)) < 0
+	|| ebuf[0] >= MAXSLAVE)
+		drawerror(display, "eof on event pipe");
+	if(n == 0)
+		goto loop;
+	i = ebuf[0];
+	if(i >= nslave || n <= 1)
+		drawerror(display, "events: protocol error: short read");
+	s = &eslave[i];
+	if(i == Stimer){
+		s->head = (Ebuf *)1;
+		return;
+	}
+	if(i == Skeyboard && n != (1+UTFmax))
+		drawerror(display, "events: protocol error: keyboard");
+	if(i == Smouse){
+		if(n < 1+1+2*12)
+			drawerror(display, "events: protocol error: mouse");
+		if(ebuf[1] == 'r')
+			eresized(1);
+		/* squash extraneous mouse events */
+		if((eb=s->tail) && memcmp(eb->buf+1+2*12, ebuf+1+1+2*12, 12)==0){
+			memmove(eb->buf, &ebuf[1], n - 1);
+			return;
+		}
+	}
+	/* try to save space by only allocating as much buffer as we need */
+	eb = malloc(sizeof(*eb) - sizeof(eb->buf) + n - 1);
+	if(eb == 0)
+		drawerror(display, "events: protocol error 4");
+	eb->n = n - 1;
+	memmove(eb->buf, &ebuf[1], n - 1);
+	eb->next = 0;
+	if(s->head)
+		s->tail->next = eb;
+	else
+		s->head = eb;
+	s->tail = eb;
+}
+
+static int
+eforkslave(ulong key)
+{
+	int i, pid;
+
+	for(i=0; i<MAXSLAVE; i++)
+		if((key & ~(1<<i)) == 0 && eslave[i].pid == 0){
+			if(nslave <= i)
+				nslave = i + 1;
+			/*
+			 * share the file descriptors so the last child
+			 * out closes all connections to the window server.
+			 */
+			switch(pid = rfork(RFPROC)){
+			case 0:
+				return MAXSLAVE+i;
+			case -1:
+				fprint(2, "events: fork error\n");
+				exits("fork");
+			}
+			eslave[i].pid = pid;
+			eslave[i].head = eslave[i].tail = 0;
+			return i;
+		}
+	drawerror(display, "events: bad slave assignment");
+	return 0;
+}
+
+static int
+enote(void*, char *s)
+{
+	int i, pid;
+
+	if(strncmp(s, "sys:", 4) == 0 || strcmp(s, "alarm") == 0)
+		return 0;
+	pid = getpid();
+	for(i=0; i<nslave; i++)
+		if(pid == eslave[i].pid)
+			return 1;
+	if(pid != parentpid)
+		return 0;
+	exits("killed");
+	return 1;
+}
+
+static void
+ekill(void)
+{
+	int i, pid;
+
+	pid = getpid();
+	for(i=0; i<nslave; i++){
+		if(eslave[i].pid == 0 || pid == eslave[i].pid)
+			continue;	/* don't kill myself */
+		postnote(PNPROC, eslave[i].pid, "die");
+	}
+}
+
+Mouse
+emouse(void)
+{
+	Mouse m;
+	Ebuf *eb;
+	static int lastb;
+	int b;
+
+	if(Smouse < 0)
+		drawerror(display, "events: mouse not initialized");
+	for(;;){
+		eb = ebread(&eslave[Smouse]);
+		b = atoi((char*)eb->buf+1+2*12);
+		if(b != lastb || !ecanmouse())
+			break;
+		free(eb);	/* drop queued mouse events */
+	}
+	lastb = b;
+	m.buttons = b;
+	m.xy.x = atoi((char*)eb->buf+1+0*12);
+	m.xy.y = atoi((char*)eb->buf+1+1*12);
+	m.msec = (ulong)atoll((char*)eb->buf+1+3*12);
+	if (logfid)
+		fprint(logfid, "b: %d xy: %P\n", m.buttons, m.xy);
+	free(eb);
+	return m;
+}
+
+int
+ekbd(void)
+{
+	Ebuf *eb;
+	Rune r;
+
+	if(Skeyboard < 0)
+		drawerror(display, "events: keyboard not initialzed");
+	eb = ebread(&eslave[Skeyboard]);
+	chartorune(&r, (char*)eb->buf);
+	free(eb);
+	return r;
+}
+
+void
+emoveto(Point pt)
+{
+	char buf[2*12+2];
+	int n;
+
+	n = sprint(buf, "m%d %d", pt.x, pt.y);
+	write(mousefd, buf, n);
+}
+
+void
+esetcursor(Cursor *c)
+{
+	uchar curs[2*4+2*2*16];
+
+	if(c == 0)
+		write(cursorfd, curs, 0);
+	else{
+		BPLONG(curs+0*4, c->offset.x);
+		BPLONG(curs+1*4, c->offset.y);
+		memmove(curs+2*4, c->clr, 2*2*16);
+		write(cursorfd, curs, sizeof curs);
+	}
+}
+
+int
+ereadmouse(Mouse *m)
+{
+	int n;
+	char buf[128];
+
+	do{
+		n = read(mousefd, buf, sizeof(buf));
+		if(n < 0)	/* probably interrupted */
+			return -1;
+		n = eatomouse(m, buf, n);
+	}while(n == 0);
+	return n;
+}
+
+int
+eatomouse(Mouse *m, char *buf, int n)
+{
+	if(n != 1+4*12){
+		werrstr("eatomouse: bad count");
+		return -1;
+	}
+
+	if(buf[0] == 'r')
+		eresized(1);
+	m->xy.x = atoi(buf+1+0*12);
+	m->xy.y = atoi(buf+1+1*12);
+	m->buttons = atoi(buf+1+2*12);
+	m->msec = (ulong)atoll(buf+1+3*12);
+	return n;
+}
--- /dev/null
+++ b/sys/src/libdraw/fmt.c
@@ -1,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+Rfmt(Fmt *f)
+{
+	Rectangle r;
+
+	r = va_arg(f->args, Rectangle);
+	return fmtprint(f, "%P %P", r.min, r.max);
+}
+
+int
+Pfmt(Fmt *f)
+{
+	Point p;
+
+	p = va_arg(f->args, Point);
+	return fmtprint(f, "[%d %d]", p.x, p.y);
+}
+
--- /dev/null
+++ b/sys/src/libdraw/font.c
@@ -1,0 +1,360 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static int	fontresize(Font*, int, int, int);
+
+#define	PJW	0	/* use NUL==pjw for invisible characters */
+
+/* return number of translated cache indices, 0 must retry, -1 on error */
+int
+cachechars(Font *f, char **ss, Rune **rr, ushort *cp, int max, int *wp, char **subfontname)
+{
+	int i, j, h, w, rw, wid, nc;
+	char *sp;
+	Rune r, *rp, vr;
+	ulong a;
+	Cacheinfo *c, *tc, *ec;
+
+	if(ss){
+		sp = *ss;
+		rp = L"";
+	}else{
+		sp = "";
+		rp = *rr;
+	}
+	wid = 0;
+	*subfontname = nil;
+	for(i=0; i<max && (*sp || *rp); sp+=w, rp+=rw){
+		if(ss){
+			r = *(uchar*)sp;
+			if(r < Runeself)
+				w = 1;
+			else{
+				w = chartorune(&vr, sp);
+				r = vr;
+			}
+			rw = 0;
+		}else{
+			r = *rp;
+			w = 0;
+			rw = 1;
+		}
+
+		a = ~0;
+		h = (17 * (uint)r) & (f->ncache-NFLOOK-1);
+		c = &f->cache[h];
+		tc = c;
+		ec = c+NFLOOK;
+		while(c < ec){
+			if(c->value==r && c->age)
+				goto Found;
+			if(c->age < a){
+				a = c->age;
+				tc = c;
+			}
+			c++;
+			h++;
+		}
+		/* Not found; use oldest entry */
+		c = tc;
+		h = tc - f->cache;
+
+		if(a && (f->age-a)<500){	/* kicking out too recent; resize */
+			nc = 2*(f->ncache-NFLOOK) + NFLOOK;
+			if(nc <= MAXFCACHE){
+				if(i == 0)
+					fontresize(f, f->width, nc, f->maxdepth);
+				/* else flush first; retry will resize */
+				break;
+			}
+		}
+
+		if(i > 0 && c->age == f->age)	/* flush pending string output */
+			break;
+
+		j = loadchar(f, r, c, h, i, subfontname);
+		if(j <= 0){
+			if(j < 0 || i > 0)	/* flush output or retry */ 
+				break;
+			return -1;		/* stop retrying */
+		}
+
+	    Found:
+		wid += c->width;
+		c->age = f->age;
+		cp[i] = h;
+		i++;
+	}
+	if(ss)
+		*ss = sp;
+	else
+		*rr = rp;
+	*wp = wid;
+	return i;
+}
+
+void
+agefont(Font *f)
+{
+	Cacheinfo *c, *ec;
+	Cachesubf *s, *es;
+
+	f->age++;
+	if(f->age == 65536){
+		/*
+		 * Renormalize ages
+		 */
+		c = f->cache;
+		ec = c+f->ncache;
+		while(c < ec){
+			if(c->age){
+				c->age >>= 2;
+				c->age++;
+			}
+			c++;
+		}
+		s = f->subf;
+		es = s+f->nsubf;
+		while(s < es){
+			if(s->age){
+				if(s->age<SUBFAGE && s->cf->name != nil){
+					/* clean up */
+					if(f->display == nil || s->f != f->display->defaultsubfont)
+						freesubfont(s->f);
+					s->cf = nil;
+					s->f = nil;
+					s->age = 0;
+				}else{
+					s->age >>= 2;
+					s->age++;
+				}
+			}
+			s++;
+		}
+		f->age = (65536>>2) + 1;
+	}
+}
+
+static Subfont*
+cf2subfont(Cachefont *cf, Font *f)
+{
+	int depth;
+	char *name;
+	Subfont *sf;
+
+	name = cf->subfontname;
+	if(name == nil){
+		if(f->display != nil && f->display->screenimage != nil)
+			depth = f->display->screenimage->depth;
+		else
+			depth = 8;
+		name = subfontname(cf->name, f->name, depth);
+		if(name == nil)
+			return nil;
+		cf->subfontname = name;
+	}
+	sf = lookupsubfont(f->display, name);
+	return sf;
+}
+
+/* return 1 if load succeeded, 0 if failed, -1 if must retry */
+int
+loadchar(Font *f, Rune r, Cacheinfo *c, int h, int noflush, char **subfontname)
+{
+	int i, oi, wid, top, bottom;
+	Rune pic;
+	Fontchar *fi;
+	Cachefont *cf;
+	Cachesubf *subf, *of;
+	uchar *b;
+
+	pic = r;
+    Again:
+	for(i=0; i<f->nsub; i++){
+		cf = f->sub[i];
+		if(cf->min<=pic && pic<=cf->max)
+			goto Found;
+	}
+    TryPJW:
+	if(pic != PJW){
+		pic = PJW;
+		goto Again;
+	}
+	return 0;
+
+    Found:
+	/*
+	 * Choose exact or oldest
+	 */
+	oi = 0;
+	subf = &f->subf[0];
+	for(i=0; i<f->nsubf; i++){
+		if(cf == subf->cf)
+			goto Found2;
+		if(subf->age < f->subf[oi].age)
+			oi = i;
+		subf++;
+	}
+	subf = &f->subf[oi];
+
+	if(subf->f){
+		if(f->age-subf->age>SUBFAGE || f->nsubf>MAXSUBF){
+    Toss:
+			/* ancient data; toss */
+			freesubfont(subf->f);
+			subf->cf = nil;
+			subf->f = nil;
+			subf->age = 0;
+		}else{				/* too recent; grow instead */
+			of = f->subf;
+			f->subf = realloc(of, (f->nsubf+DSUBF)*sizeof *subf);
+			if(f->subf == nil){
+				f->subf = of;
+				goto Toss;
+			}
+			subf = &f->subf[f->nsubf];
+			memset(subf, 0, DSUBF*sizeof *subf);
+			f->nsubf += DSUBF;
+		}
+	}
+	subf->age = 0;
+	subf->cf = nil;
+	subf->f = cf2subfont(cf, f);
+	if(subf->f == nil){
+		if(cf->subfontname == nil)
+			goto TryPJW;
+		*subfontname = cf->subfontname;
+		return -1;
+	}
+
+	subf->cf = cf;
+	if(subf->f->ascent > f->ascent && f->display){
+		/* should print something? this is a mistake in the font file */
+		/* must prevent c->top from going negative when loading cache */
+		Image *b;
+		int d, t;
+		d = subf->f->ascent - f->ascent;
+		b = subf->f->bits;
+		draw(b, b->r, b, nil, addpt(b->r.min, Pt(0, d)));
+		draw(b, Rect(b->r.min.x, b->r.max.y-d, b->r.max.x, b->r.max.y), f->display->black, nil, b->r.min);
+		for(i=0; i<subf->f->n; i++){
+			t = subf->f->info[i].top-d;
+			if(t < 0)
+				t = 0;
+			subf->f->info[i].top = t;
+			t = subf->f->info[i].bottom-d;
+			if(t < 0)
+				t = 0;
+			subf->f->info[i].bottom = t;
+		}
+		subf->f->ascent = f->ascent;
+	}
+
+    Found2:
+	subf->age = f->age;
+
+	/* possible overflow here, but works out okay */
+	pic += cf->offset;
+	pic -= cf->min;
+	if(pic >= subf->f->n)
+		goto TryPJW;
+	fi = &subf->f->info[pic];
+	if(fi->width == 0)
+		goto TryPJW;
+	wid = (fi+1)->x - fi->x;
+	if(f->width < wid || f->width == 0 || f->maxdepth < subf->f->bits->depth
+	|| (f->display != nil && f->cacheimage == nil)){
+		/*
+		 * Flush, free, reload (easier than reformatting f->b)
+		 */
+		if(noflush)
+			return -1;
+		if(f->width < wid)
+			f->width = wid;
+		if(f->maxdepth < subf->f->bits->depth)
+			f->maxdepth = subf->f->bits->depth;
+		if(fontresize(f, f->width, f->ncache, f->maxdepth) <= 0)
+			return -1;
+		/* c is still valid as didn't reallocate f->cache */
+	}
+	c->value = r;
+	c->width = fi->width;
+	c->x = h*f->width;
+	c->left = fi->left;
+	if(f->display == nil)
+		return 1;
+	b = bufimage(f->display, 37);
+	if(b == nil)
+		return 0;
+	top = fi->top + (f->ascent-subf->f->ascent);
+	bottom = fi->bottom + (f->ascent-subf->f->ascent);
+	b[0] = 'l';
+	BPLONG(b+1, f->cacheimage->id);
+	BPLONG(b+5, subf->f->bits->id);
+	BPSHORT(b+9, h);
+	BPLONG(b+11, c->x);
+	BPLONG(b+15, top);
+	BPLONG(b+19, c->x+wid);
+	BPLONG(b+23, bottom);
+	BPLONG(b+27, fi->x);
+	BPLONG(b+31, fi->top);
+	b[35] = fi->left;
+	b[36] = fi->width;
+	return 1;
+}
+
+/* returns whether resize succeeded && f->cache is unchanged */
+static int
+fontresize(Font *f, int wid, int ncache, int depth)
+{
+	Cacheinfo *i;
+	int ret;
+	Image *new;
+	uchar *b;
+	Display *d;
+
+	ret = 0;
+	if(depth <= 0)
+		depth = 1;
+	if(wid <= 0)
+		wid = 1;
+
+	d = f->display;
+	if(d == nil)
+		goto Nodisplay;
+
+	new = allocimage(d, Rect(0, 0, ncache*wid, f->height), CHAN1(CGrey, depth), 0, 0);
+	if(new == nil){
+		fprint(2, "font cache resize failed: %r\n");
+		goto Return;
+	}
+	b = bufimage(d, 1+4+4+1);
+	if(b == nil){
+		freeimage(new);
+		goto Return;
+	}
+	b[0] = 'i';
+	BPLONG(b+1, new->id);
+	BPLONG(b+5, ncache);
+	b[9] = f->ascent;
+	freeimage(f->cacheimage);
+	f->cacheimage = new;
+    Nodisplay:
+	f->width = wid;
+	f->maxdepth = depth;
+	ret = 1;
+	if(f->ncache != ncache){
+		i = malloc(ncache*sizeof f->cache[0]);
+		if(i != nil){
+			ret = 0;
+			free(f->cache);
+			f->ncache = ncache;
+			f->cache = i;
+		}
+		/* else just wipe the cache clean and things will be ok */
+	}
+    Return:
+	memset(f->cache, 0, f->ncache*sizeof f->cache[0]);
+	return ret;
+}
--- /dev/null
+++ b/sys/src/libdraw/freesubfont.c
@@ -1,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+void
+freesubfont(Subfont *f)
+{
+	if(f == nil || --f->ref)
+		return;
+	uninstallsubfont(f);
+	free(f->name);
+	free(f->info);	/* note: f->info must have been malloc'ed! */
+	freeimage(f->bits);
+	free(f);
+}
--- /dev/null
+++ b/sys/src/libdraw/getdefont.c
@@ -1,0 +1,60 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Subfont*
+getdefont(Display *d)
+{
+	char *hdr, *p;
+	int n;
+	Fontchar *fc;
+	Subfont *f;
+	int ld;
+	Rectangle r;
+	Image *i;
+
+	/*
+	 * make sure data is word-aligned.  this is true with Plan 9 compilers
+	 * but not in general.  the byte order is right because the data is
+	 * declared as char*, not ulong*.
+	 */
+	p = (char*)defontdata;
+	n = (int)(uvlong)p & 3;				/* stupid ape */
+	if(n != 0){
+		memmove(p+(4-n), p, sizeofdefont-n);
+		p += 4-n;
+	}
+	ld = atoi(p+0*12);
+	r.min.x = atoi(p+1*12);
+	r.min.y = atoi(p+2*12);
+	r.max.x = atoi(p+3*12);
+	r.max.y = atoi(p+4*12);
+
+	i = allocimage(d, r, drawld2chan[ld], 0, 0);
+	if(i == 0)
+		return 0;
+
+	p += 5*12;
+	n = loadimage(i, r, (uchar*)p, (defontdata+sizeofdefont)-(uchar*)p);
+	if(n < 0){
+		freeimage(i);
+		return 0;
+	}
+
+	hdr = p+n;
+	n = atoi(hdr);
+	p = hdr+3*12;
+	fc = malloc(sizeof(Fontchar)*(n+1));
+	if(fc == 0){
+		freeimage(i);
+		return 0;
+	}
+	_unpackinfo(fc, (uchar*)p, n);
+	f = allocsubfont("*default*", n, atoi(hdr+12), atoi(hdr+24), fc, i);
+	if(f == 0){
+		freeimage(i);
+		free(fc);
+		return 0;
+	}
+	return f;
+}
--- /dev/null
+++ b/sys/src/libdraw/getrect.c
@@ -1,0 +1,133 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+
+#define	W	Borderwidth
+
+static Image *tmp[4];
+static Image *red;
+
+static Cursor sweep={
+	{-7, -7},
+	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x07,
+	 0xE0, 0x07, 0xE0, 0x07, 0xE3, 0xF7, 0xE3, 0xF7,
+	 0xE3, 0xE7, 0xE3, 0xF7, 0xE3, 0xFF, 0xE3, 0x7F,
+	 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,},
+	{0x00, 0x00, 0x7F, 0xFE, 0x40, 0x02, 0x40, 0x02,
+	 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x41, 0xE2,
+	 0x41, 0xC2, 0x41, 0xE2, 0x41, 0x72, 0x40, 0x38,
+	 0x40, 0x1C, 0x40, 0x0E, 0x7F, 0xE6, 0x00, 0x00,}
+};
+
+static
+void
+brects(Rectangle r, Rectangle rp[4])
+{
+	if(Dx(r) < 2*W)
+		r.max.x = r.min.x+2*W;
+	if(Dy(r) < 2*W)
+		r.max.y = r.min.y+2*W;
+	rp[0] = Rect(r.min.x, r.min.y, r.max.x, r.min.y+W);
+	rp[1] = Rect(r.min.x, r.max.y-W, r.max.x, r.max.y);
+	rp[2] = Rect(r.min.x, r.min.y+W, r.min.x+W, r.max.y-W);
+	rp[3] = Rect(r.max.x-W, r.min.y+W, r.max.x, r.max.y-W);
+}
+
+Rectangle
+getrect(int but, Mousectl *mc)
+{
+	Rectangle r, rc;
+
+	but = 1<<(but-1);
+	setcursor(mc, &sweep);
+	while(mc->buttons)
+		readmouse(mc);
+	while(!(mc->buttons & but)){
+		readmouse(mc);
+		if(mc->buttons & (7^but))
+			goto Return;
+	}
+	r.min = mc->xy;
+	r.max = mc->xy;
+	do{
+		rc = canonrect(r);
+		drawgetrect(rc, 1);
+		readmouse(mc);
+		drawgetrect(rc, 0);
+		r.max = mc->xy;
+	}while(mc->buttons == but);
+
+    Return:
+	setcursor(mc, nil);
+	if(mc->buttons & (7^but)){
+		rc.min.x = rc.max.x = 0;
+		rc.min.y = rc.max.y = 0;
+		while(mc->buttons)
+			readmouse(mc);
+	}
+	return rc;
+}
+
+static
+void
+freetmp(void)
+{
+	freeimage(tmp[0]);
+	freeimage(tmp[1]);
+	freeimage(tmp[2]);
+	freeimage(tmp[3]);
+	freeimage(red);
+	tmp[0] = tmp[1] = tmp[2] = tmp[3] = red = nil;
+}
+
+static
+int
+max(int a, int b)
+{
+	if(a > b)
+		return a;
+	return b;
+}
+
+void
+drawgetrect(Rectangle rc, int up)
+{
+	int i;
+	Rectangle r, rects[4];
+
+	/*
+	 * BUG: if for some reason we have two of these going on at once
+	 * when we must grow the tmp buffers, we lose data.  Also if tmp
+	 * is unallocated and we ask to restore the screen, it would be nice
+	 * to complain, but we silently make a mess.
+	 */
+	if(up && tmp[0]!=nil)
+		if(Dx(tmp[0]->r)<Dx(rc) || Dy(tmp[2]->r)<Dy(rc))
+			freetmp();
+	if(tmp[0] == 0){
+		r = Rect(0, 0, max(Dx(display->screenimage->r), Dx(rc)), W);
+		tmp[0] = allocimage(display, r, screen->chan, 0, -1);
+		tmp[1] = allocimage(display, r, screen->chan, 0, -1);
+		r = Rect(0, 0, W, max(Dy(display->screenimage->r), Dy(rc)));
+		tmp[2] = allocimage(display, r, screen->chan, 0, -1);
+		tmp[3] = allocimage(display, r, screen->chan, 0, -1);
+		red = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DRed);
+		if(tmp[0]==0 || tmp[1]==0 || tmp[2]==0 || tmp[3]==0 || red==0){
+			freetmp();
+			drawerror(display, "getrect: allocimage failed");
+		}
+	}
+	brects(rc, rects);
+	if(!up){
+		for(i=0; i<4; i++)
+			draw(screen, rects[i], tmp[i], nil, ZP);
+		return;
+	}
+	for(i=0; i<4; i++){
+		draw(tmp[i], Rect(0, 0, Dx(rects[i]), Dy(rects[i])), screen, nil, rects[i].min);
+		draw(screen, rects[i], red, nil, ZP);
+	}
+}
--- /dev/null
+++ b/sys/src/libdraw/getsubfont.c
@@ -1,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * Default version: treat as file name
+ */
+
+Subfont*
+_getsubfont(Display *d, char *name)
+{
+	int dolock, fd;
+	Subfont *f;
+
+	/*
+	 * unlock display so i/o happens with display released, unless
+	 * user is doing his own locking, in which case this could break things.
+	 * _getsubfont is called only from string.c and stringwidth.c,
+	 * which are known to be safe to have this done.
+	 */
+	dolock = d != nil && d->locking == 0;
+	if(dolock)
+		unlockdisplay(d);
+
+	fd = open(name, OREAD|OCEXEC);
+	if(fd < 0) {
+		fprint(2, "getsubfont: can't open %s: %r\n", name);
+		f = nil;
+	} else {
+		f = readsubfont(d, name, fd, dolock);
+		if(f == nil)
+			fprint(2, "getsubfont: can't read %s: %r\n", name);
+		close(fd);
+	}
+
+	if(dolock)
+		lockdisplay(d);
+
+	return f;
+}
--- /dev/null
+++ b/sys/src/libdraw/icossin.c
@@ -1,0 +1,140 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<draw.h>
+
+/*
+ * Integer sine and cosine for integral degree argument.
+ * Tables computed by (sin,cos)(PI*d/180).
+ */
+static short sinus[91] = {
+	0,	/* 0 */
+	18,	/* 1 */
+	36,	/* 2 */
+	54,	/* 3 */
+	71,	/* 4 */
+	89,	/* 5 */
+	107,	/* 6 */
+	125,	/* 7 */
+	143,	/* 8 */
+	160,	/* 9 */
+	178,	/* 10 */
+	195,	/* 11 */
+	213,	/* 12 */
+	230,	/* 13 */
+	248,	/* 14 */
+	265,	/* 15 */
+	282,	/* 16 */
+	299,	/* 17 */
+	316,	/* 18 */
+	333,	/* 19 */
+	350,	/* 20 */
+	367,	/* 21 */
+	384,	/* 22 */
+	400,	/* 23 */
+	416,	/* 24 */
+	433,	/* 25 */
+	449,	/* 26 */
+	465,	/* 27 */
+	481,	/* 28 */
+	496,	/* 29 */
+	512,	/* 30 */
+	527,	/* 31 */
+	543,	/* 32 */
+	558,	/* 33 */
+	573,	/* 34 */
+	587,	/* 35 */
+	602,	/* 36 */
+	616,	/* 37 */
+	630,	/* 38 */
+	644,	/* 39 */
+	658,	/* 40 */
+	672,	/* 41 */
+	685,	/* 42 */
+	698,	/* 43 */
+	711,	/* 44 */
+	724,	/* 45 */
+	737,	/* 46 */
+	749,	/* 47 */
+	761,	/* 48 */
+	773,	/* 49 */
+	784,	/* 50 */
+	796,	/* 51 */
+	807,	/* 52 */
+	818,	/* 53 */
+	828,	/* 54 */
+	839,	/* 55 */
+	849,	/* 56 */
+	859,	/* 57 */
+	868,	/* 58 */
+	878,	/* 59 */
+	887,	/* 60 */
+	896,	/* 61 */
+	904,	/* 62 */
+	912,	/* 63 */
+	920,	/* 64 */
+	928,	/* 65 */
+	935,	/* 66 */
+	943,	/* 67 */
+	949,	/* 68 */
+	956,	/* 69 */
+	962,	/* 70 */
+	968,	/* 71 */
+	974,	/* 72 */
+	979,	/* 73 */
+	984,	/* 74 */
+	989,	/* 75 */
+	994,	/* 76 */
+	998,	/* 77 */
+	1002,	/* 78 */
+	1005,	/* 79 */
+	1008,	/* 80 */
+	1011,	/* 81 */
+	1014,	/* 82 */
+	1016,	/* 83 */
+	1018,	/* 84 */
+	1020,	/* 85 */
+	1022,	/* 86 */
+	1023,	/* 87 */
+	1023,	/* 88 */
+	1024,	/* 89 */
+	1024,	/* 90 */
+};
+
+void
+icossin(int deg, int *cosp, int *sinp)
+{
+	int sinsign, cossign;
+	short *stp, *ctp;
+
+	deg %= 360;
+	if(deg < 0)
+		deg += 360;
+	sinsign = 1;
+	cossign = 1;
+	stp = 0;
+	ctp = 0;
+	switch(deg/90){
+	case 2:
+		sinsign = -1;
+		cossign = -1;
+		deg -= 180;
+		/* fall through */
+	case 0:
+		stp = &sinus[deg];
+		ctp = &sinus[90-deg];
+		break;
+	case 3:
+		sinsign = -1;
+		cossign = -1;
+		deg -= 180;
+		/* fall through */
+	case 1:
+		deg = 180-deg;
+		cossign = -cossign;
+		stp = &sinus[deg];
+		ctp = &sinus[90-deg];
+		break;
+	}
+	*sinp = sinsign*stp[0];
+	*cosp = cossign*ctp[0];
+}
--- /dev/null
+++ b/sys/src/libdraw/icossin2.c
@@ -1,0 +1,261 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<draw.h>
+
+/*
+ * Sine and Cosine of arctangents, calculated by 
+ *   (sin(atan(index/100.0))*1024.+0.5)
+ *   (cos(atan(index/100.0))*1024.+0.5)
+ * To use, get rational tangent between 0<=tan<=1, scale by 100,
+ * and look up sin and cos, and use linear interpolation.  divide by 1024.
+ * Maximum error is 0.0020.  Without linear interpolation, it's 0.010.
+ */
+static
+short sinus[] = {
+	0,	/* 0.00 */
+	10,	/* 0.01 */
+	20,	/* 0.02 */
+	31,	/* 0.03 */
+	41,	/* 0.04 */
+	51,	/* 0.05 */
+	61,	/* 0.06 */
+	72,	/* 0.07 */
+	82,	/* 0.08 */
+	92,	/* 0.09 */
+	102,	/* 0.10 */
+	112,	/* 0.11 */
+	122,	/* 0.12 */
+	132,	/* 0.13 */
+	142,	/* 0.14 */
+	152,	/* 0.15 */
+	162,	/* 0.16 */
+	172,	/* 0.17 */
+	181,	/* 0.18 */
+	191,	/* 0.19 */
+	201,	/* 0.20 */
+	210,	/* 0.21 */
+	220,	/* 0.22 */
+	230,	/* 0.23 */
+	239,	/* 0.24 */
+	248,	/* 0.25 */
+	258,	/* 0.26 */
+	267,	/* 0.27 */
+	276,	/* 0.28 */
+	285,	/* 0.29 */
+	294,	/* 0.30 */
+	303,	/* 0.31 */
+	312,	/* 0.32 */
+	321,	/* 0.33 */
+	330,	/* 0.34 */
+	338,	/* 0.35 */
+	347,	/* 0.36 */
+	355,	/* 0.37 */
+	364,	/* 0.38 */
+	372,	/* 0.39 */
+	380,	/* 0.40 */
+	388,	/* 0.41 */
+	397,	/* 0.42 */
+	405,	/* 0.43 */
+	412,	/* 0.44 */
+	420,	/* 0.45 */
+	428,	/* 0.46 */
+	436,	/* 0.47 */
+	443,	/* 0.48 */
+	451,	/* 0.49 */
+	458,	/* 0.50 */
+	465,	/* 0.51 */
+	472,	/* 0.52 */
+	480,	/* 0.53 */
+	487,	/* 0.54 */
+	493,	/* 0.55 */
+	500,	/* 0.56 */
+	507,	/* 0.57 */
+	514,	/* 0.58 */
+	520,	/* 0.59 */
+	527,	/* 0.60 */
+	533,	/* 0.61 */
+	540,	/* 0.62 */
+	546,	/* 0.63 */
+	552,	/* 0.64 */
+	558,	/* 0.65 */
+	564,	/* 0.66 */
+	570,	/* 0.67 */
+	576,	/* 0.68 */
+	582,	/* 0.69 */
+	587,	/* 0.70 */
+	593,	/* 0.71 */
+	598,	/* 0.72 */
+	604,	/* 0.73 */
+	609,	/* 0.74 */
+	614,	/* 0.75 */
+	620,	/* 0.76 */
+	625,	/* 0.77 */
+	630,	/* 0.78 */
+	635,	/* 0.79 */
+	640,	/* 0.80 */
+	645,	/* 0.81 */
+	649,	/* 0.82 */
+	654,	/* 0.83 */
+	659,	/* 0.84 */
+	663,	/* 0.85 */
+	668,	/* 0.86 */
+	672,	/* 0.87 */
+	676,	/* 0.88 */
+	681,	/* 0.89 */
+	685,	/* 0.90 */
+	689,	/* 0.91 */
+	693,	/* 0.92 */
+	697,	/* 0.93 */
+	701,	/* 0.94 */
+	705,	/* 0.95 */
+	709,	/* 0.96 */
+	713,	/* 0.97 */
+	717,	/* 0.98 */
+	720,	/* 0.99 */
+	724,	/* 1.00 */
+	728,	/* 1.01 */
+};
+
+static
+short cosinus[] = {
+	1024,	/* 0.00 */
+	1024,	/* 0.01 */
+	1024,	/* 0.02 */
+	1024,	/* 0.03 */
+	1023,	/* 0.04 */
+	1023,	/* 0.05 */
+	1022,	/* 0.06 */
+	1022,	/* 0.07 */
+	1021,	/* 0.08 */
+	1020,	/* 0.09 */
+	1019,	/* 0.10 */
+	1018,	/* 0.11 */
+	1017,	/* 0.12 */
+	1015,	/* 0.13 */
+	1014,	/* 0.14 */
+	1013,	/* 0.15 */
+	1011,	/* 0.16 */
+	1010,	/* 0.17 */
+	1008,	/* 0.18 */
+	1006,	/* 0.19 */
+	1004,	/* 0.20 */
+	1002,	/* 0.21 */
+	1000,	/* 0.22 */
+	998,	/* 0.23 */
+	996,	/* 0.24 */
+	993,	/* 0.25 */
+	991,	/* 0.26 */
+	989,	/* 0.27 */
+	986,	/* 0.28 */
+	983,	/* 0.29 */
+	981,	/* 0.30 */
+	978,	/* 0.31 */
+	975,	/* 0.32 */
+	972,	/* 0.33 */
+	969,	/* 0.34 */
+	967,	/* 0.35 */
+	963,	/* 0.36 */
+	960,	/* 0.37 */
+	957,	/* 0.38 */
+	954,	/* 0.39 */
+	951,	/* 0.40 */
+	947,	/* 0.41 */
+	944,	/* 0.42 */
+	941,	/* 0.43 */
+	937,	/* 0.44 */
+	934,	/* 0.45 */
+	930,	/* 0.46 */
+	927,	/* 0.47 */
+	923,	/* 0.48 */
+	920,	/* 0.49 */
+	916,	/* 0.50 */
+	912,	/* 0.51 */
+	909,	/* 0.52 */
+	905,	/* 0.53 */
+	901,	/* 0.54 */
+	897,	/* 0.55 */
+	893,	/* 0.56 */
+	890,	/* 0.57 */
+	886,	/* 0.58 */
+	882,	/* 0.59 */
+	878,	/* 0.60 */
+	874,	/* 0.61 */
+	870,	/* 0.62 */
+	866,	/* 0.63 */
+	862,	/* 0.64 */
+	859,	/* 0.65 */
+	855,	/* 0.66 */
+	851,	/* 0.67 */
+	847,	/* 0.68 */
+	843,	/* 0.69 */
+	839,	/* 0.70 */
+	835,	/* 0.71 */
+	831,	/* 0.72 */
+	827,	/* 0.73 */
+	823,	/* 0.74 */
+	819,	/* 0.75 */
+	815,	/* 0.76 */
+	811,	/* 0.77 */
+	807,	/* 0.78 */
+	804,	/* 0.79 */
+	800,	/* 0.80 */
+	796,	/* 0.81 */
+	792,	/* 0.82 */
+	788,	/* 0.83 */
+	784,	/* 0.84 */
+	780,	/* 0.85 */
+	776,	/* 0.86 */
+	773,	/* 0.87 */
+	769,	/* 0.88 */
+	765,	/* 0.89 */
+	761,	/* 0.90 */
+	757,	/* 0.91 */
+	754,	/* 0.92 */
+	750,	/* 0.93 */
+	746,	/* 0.94 */
+	742,	/* 0.95 */
+	739,	/* 0.96 */
+	735,	/* 0.97 */
+	731,	/* 0.98 */
+	728,	/* 0.99 */
+	724,	/* 1.00 */
+	720,	/* 1.01 */
+};
+
+void
+icossin2(int x, int y, int *cosp, int *sinp)
+{
+	int sinsign, cossign, tan, tan10, rem;
+	short *stp, *ctp;
+
+	if(x == 0){
+		if(y >= 0)
+			*sinp = ICOSSCALE, *cosp = 0;
+		else
+			*sinp = -ICOSSCALE, *cosp = 0;
+		return;
+	}
+	sinsign = cossign = 1;
+	if(x < 0){
+		cossign = -1;
+		x = -x;
+	}
+	if(y < 0){
+		sinsign = -1;
+		y = -y;
+	}
+	if(y > x){
+		tan = 1000*x/y;
+		tan10 = tan/10;
+		stp = &cosinus[tan10];
+		ctp = &sinus[tan10];
+	}else{
+		tan = 1000*y/x;
+		tan10 = tan/10;
+		stp = &sinus[tan10];
+		ctp = &cosinus[tan10];
+	}
+	rem = tan-(tan10*10);
+	*sinp = sinsign*(stp[0]+(stp[1]-stp[0])*rem/10);
+	*cosp = cossign*(ctp[0]+(ctp[1]-ctp[0])*rem/10);
+}
--- /dev/null
+++ b/sys/src/libdraw/init.c
@@ -1,0 +1,456 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Display	*display;
+Font	*font;
+Image	*screen;
+
+static char deffontname[] = "*default*";
+Screen	*_screen;
+
+int	debuglockdisplay = 0;
+
+static void _closedisplay(Display*, int);
+
+/* note handler */
+static void
+drawshutdown(void)
+{
+	Display *d;
+
+	d = display;
+	if(d != nil){
+		display = nil;
+		_closedisplay(d, 1);
+	}
+}
+
+int
+geninitdraw(char *devdir, void(*error)(Display*, char*), char *fontname, char *label, char *windir, int ref)
+{
+	int fd, n;
+	Subfont *df;
+	char buf[128];
+
+	display = initdisplay(devdir, windir, error);
+	if(display == nil)
+		return -1;
+
+	/*
+	 * Set up default font
+	 */
+	df = getdefont(display);
+	display->defaultsubfont = df;
+	if(df == nil){
+    Error:
+		closedisplay(display);
+		display = nil;
+		return -1;
+	}
+	if(fontname == nil){
+		fd = open("/env/font", OREAD|OCEXEC);
+		if(fd >= 0){
+			n = read(fd, buf, sizeof(buf));
+			if(n>0 && n<sizeof buf-1){
+				buf[n] = 0;
+				fontname = buf;
+			}
+			close(fd);
+		}
+	}
+	/*
+	 * Build fonts with caches==depth of screen, for speed.
+	 * If conversion were faster, we'd use 0 and save memory.
+	 */
+	if(fontname == nil){
+		snprint(buf, sizeof buf, "%d %d\n0 %d\t%s\n", df->height, df->ascent,
+			df->n-1, deffontname);
+//BUG: Need something better for this	installsubfont("*default*", df);
+		font = buildfont(display, buf, deffontname);
+		if(font == nil)
+			goto Error;
+	}else{
+		font = openfont(display, fontname);	/* BUG: grey fonts */
+		if(font == nil)
+			goto Error;
+	}
+	display->defaultfont = font;
+
+	/*
+	 * Write label; ignore errors (we might not be running under rio)
+	 */
+	if(label != nil){
+		snprint(buf, sizeof buf, "%s/label", display->windir);
+		fd = open(buf, OREAD|OCEXEC);
+		if(fd >= 0){
+			read(fd, display->oldlabel, (sizeof display->oldlabel)-1);
+			close(fd);
+			fd = create(buf, OWRITE|OCEXEC, 0666);
+			if(fd >= 0){
+				write(fd, label, strlen(label));
+				close(fd);
+			}
+		}
+	}
+
+	snprint(buf, sizeof buf, "%s/winname", display->windir);
+	if(gengetwindow(display, buf, &screen, &_screen, ref) < 0)
+		goto Error;
+
+	atexit(drawshutdown);
+
+	return 1;
+}
+
+int
+initdraw(void(*error)(Display*, char*), char *fontname, char *label)
+{
+	static char dev[] = "/dev";
+
+	return geninitdraw(dev, error, fontname, label, dev, Refnone);
+}
+
+/*
+ * Attach, or possibly reattach, to window.
+ * If reattaching, maintain value of screen pointer.
+ */
+int
+gengetwindow(Display *d, char *winname, Image **winp, Screen **scrp, int ref)
+{
+	int n, fd;
+	char buf[64+1], obuf[64+1];
+	Image *image;
+	Rectangle r;
+
+	obuf[0] = 0;
+retry:
+	fd = open(winname, OREAD|OCEXEC);
+	if(fd<0 || (n=read(fd, buf, sizeof buf-1))<=0){
+		if(fd >= 0) close(fd);
+		strcpy(buf, "noborder");
+		image = d->image;
+	}else{
+		close(fd);
+		buf[n] = '\0';
+		image = namedimage(d, buf);
+		if(image == nil){
+			/*
+			 * theres a race where the winname can change after
+			 * we read it, so keep trying as long as the name
+			 * keeps changing.
+			 */
+			if(strcmp(buf, obuf) != 0){
+				strcpy(obuf, buf);
+				goto retry;
+			}
+		}
+	}
+	if(*winp != nil){
+		_freeimage1(*winp);
+		if((*scrp)->image != d->image)
+			freeimage((*scrp)->image);
+		freescreen(*scrp);
+		*scrp = nil;
+	}
+	if(image == nil){
+		*winp = nil;
+		d->screenimage = nil;
+		return -1;
+	}
+	d->screenimage = image;
+	*scrp = allocscreen(image, d->white, 0);
+	if(*scrp == nil){
+		*winp = nil;
+		d->screenimage = nil;
+		if(image != d->image)
+			freeimage(image);
+		return -1;
+	}
+	r = image->r;
+	if(strncmp(buf, "noborder", 8) != 0)
+		r = insetrect(r, Borderwidth);
+	*winp = _allocwindow(*winp, *scrp, r, ref, DWhite);
+	if(*winp == nil){
+		freescreen(*scrp);
+		*scrp = nil;
+		d->screenimage = nil;
+		if(image != d->image)
+			freeimage(image);
+		return -1;
+	}
+	d->screenimage = *winp;
+	return 1;
+}
+
+int
+getwindow(Display *d, int ref)
+{
+	char winname[128];
+
+	snprint(winname, sizeof winname, "%s/winname", d->windir);
+	return gengetwindow(d, winname, &screen, &_screen, ref);
+}
+
+#define	NINFO	12*12
+
+Display*
+initdisplay(char *dev, char *win, void(*error)(Display*, char*))
+{
+	char buf[128], info[NINFO+1], *t, isnew;
+	int n, datafd, ctlfd, reffd;
+	Display *disp;
+	Dir *dir;
+	Image *image;
+
+	fmtinstall('P', Pfmt);
+	fmtinstall('R', Rfmt);
+	if(dev == nil)
+		dev = "/dev";
+	if(win == nil)
+		win = "/dev";
+	if(strlen(dev)>sizeof buf-25 || strlen(win)>sizeof buf-25){
+		werrstr("initdisplay: directory name too long");
+		return nil;
+	}
+	t = strdup(win);
+	if(t == nil)
+		return nil;
+
+	sprint(buf, "%s/draw/new", dev);
+	ctlfd = open(buf, ORDWR|OCEXEC);
+	if(ctlfd < 0){
+    Error1:
+		free(t);
+		werrstr("initdisplay: %s: %r", buf);
+		return nil;
+	}
+	if((n=read(ctlfd, info, sizeof info)) < 12){
+    Error2:
+		close(ctlfd);
+		goto Error1;
+	}
+	if(n==NINFO+1)
+		n = NINFO;
+	info[n] = '\0';
+	isnew = 0;
+	if(n < NINFO)	/* this will do for now, we need something better here */
+		isnew = 1;
+	sprint(buf, "%s/draw/%d/data", dev, atoi(info+0*12));
+	datafd = open(buf, ORDWR|OCEXEC);
+	if(datafd < 0)
+		goto Error2;
+	sprint(buf, "%s/draw/%d/refresh", dev, atoi(info+0*12));
+	reffd = open(buf, OREAD|OCEXEC);
+	if(reffd < 0){
+    Error3:
+		close(datafd);
+		goto Error2;
+	}
+	disp = mallocz(sizeof(Display), 1);
+	if(disp == nil){
+    Error4:
+		close(reffd);
+		goto Error3;
+	}
+	image = nil;
+	if(0){
+    Error5:
+		free(image);
+		free(disp);
+		goto Error4;
+	}
+	if(n >= NINFO){
+		image = mallocz(sizeof(Image), 1);
+		if(image == nil)
+			goto Error5;
+		image->display = disp;
+		image->id = 0;
+		image->chan = strtochan(info+2*12);
+		image->depth = chantodepth(image->chan);
+		image->repl = atoi(info+3*12);
+		image->r.min.x = atoi(info+4*12);
+		image->r.min.y = atoi(info+5*12);
+		image->r.max.x = atoi(info+6*12);
+		image->r.max.y = atoi(info+7*12);
+		image->clipr.min.x = atoi(info+8*12);
+		image->clipr.min.y = atoi(info+9*12);
+		image->clipr.max.x = atoi(info+10*12);
+		image->clipr.max.y = atoi(info+11*12);
+	}
+
+	disp->_isnewdisplay = isnew;
+	disp->bufsize = iounit(datafd);
+	if(disp->bufsize <= 0)
+		disp->bufsize = 8000;
+	if(disp->bufsize < 512){
+		werrstr("iounit %d too small", disp->bufsize);
+		goto Error5;
+	}
+	disp->buf = malloc(disp->bufsize+5);	/* +5 for flush message */
+	if(disp->buf == nil)
+		goto Error5;
+
+	disp->image = image;
+	disp->dirno = atoi(info+0*12);
+	disp->fd = datafd;
+	disp->ctlfd = ctlfd;
+	disp->reffd = reffd;
+	disp->bufp = disp->buf;
+	disp->error = error;
+	disp->windir = t;
+	disp->devdir = strdup(dev);
+	qlock(&disp->qlock);
+	disp->white = allocimage(disp, Rect(0, 0, 1, 1), GREY1, 1, DWhite);
+	disp->black = allocimage(disp, Rect(0, 0, 1, 1), GREY1, 1, DBlack);
+	if(disp->white == nil || disp->black == nil){
+		free(disp->devdir);
+		free(disp->white);
+		free(disp->black);
+		goto Error5;
+	}
+	disp->opaque = disp->white;
+	disp->transparent = disp->black;
+	dir = dirfstat(ctlfd);
+	if(dir!=nil && dir->type=='i'){
+		disp->local = 1;
+		disp->dataqid = dir->qid.path;
+	}
+	if(dir!=nil && dir->qid.vers==1)	/* other way to tell */
+		disp->_isnewdisplay = 1;
+	free(dir);
+
+	return disp;
+}
+
+/*
+ * Call with d unlocked.
+ * Note that disp->defaultfont and defaultsubfont are not freed here.
+ */
+void
+closedisplay(Display *disp)
+{
+	_closedisplay(disp, 0);
+}
+
+static void
+_closedisplay(Display *disp, int isshutdown)
+{
+	int fd;
+	char buf[128];
+
+	if(disp == nil)
+		return;
+	if(disp == display)
+		display = nil;
+	if(disp->oldlabel[0]){
+		snprint(buf, sizeof buf, "%s/label", disp->windir);
+		fd = open(buf, OWRITE|OCEXEC);
+		if(fd >= 0){
+			write(fd, disp->oldlabel, strlen(disp->oldlabel));
+			close(fd);
+		}
+	}
+
+	/*
+	 * if we're shutting down, don't free all the resources.
+	 * if other procs are getting shot down by notes too,
+	 * one might get shot down while holding the malloc lock.
+	 * just let the kernel clean things up when we exit.
+	 */
+	if(isshutdown)
+		return;
+
+	free(disp->devdir);
+	free(disp->windir);
+	freeimage(disp->white);
+	freeimage(disp->black);
+	close(disp->fd);
+	close(disp->ctlfd);
+	/* should cause refresh slave to shut down */
+	close(disp->reffd);
+	qunlock(&disp->qlock);
+	free(disp);
+}
+
+void
+lockdisplay(Display *disp)
+{
+	if(debuglockdisplay){
+		/* avoid busy looping; it's rare we collide anyway */
+		while(!canqlock(&disp->qlock))
+			sleep(1000);
+	}else
+		qlock(&disp->qlock);
+}
+
+void
+unlockdisplay(Display *disp)
+{
+	qunlock(&disp->qlock);
+}
+
+void
+drawerror(Display *d, char *s)
+{
+	char err[ERRMAX];
+
+	if(d != nil && d->error != nil)
+		(*d->error)(d, s);
+	else{
+		errstr(err, sizeof err);
+		fprint(2, "draw: %s: %s\n", s, err);
+		exits(s);
+	}
+}
+
+static
+int
+doflush(Display *d)
+{
+	int n;
+
+	n = d->bufp-d->buf;
+	if(n <= 0)
+		return 1;
+
+	if(write(d->fd, d->buf, n) != n){
+		d->bufp = d->buf;	/* might as well; chance of continuing */
+		return -1;
+	}
+	d->bufp = d->buf;
+	return 1;
+}
+
+int
+flushimage(Display *d, int visible)
+{
+	if(d == nil)
+		return 0;
+	if(visible){
+		*d->bufp++ = 'v';	/* five bytes always reserved for this */
+		if(d->_isnewdisplay){
+			BPLONG(d->bufp, d->screenimage->id);
+			d->bufp += 4;
+		}
+	}
+	return doflush(d);
+}
+
+uchar*
+bufimage(Display *d, int n)
+{
+	uchar *p;
+
+	if(n<0 || n>d->bufsize){
+		werrstr("bad count in bufimage");
+		return nil;
+	}
+	if(d->bufp+n > d->buf+d->bufsize)
+		if(doflush(d) < 0)
+			return nil;
+	p = d->bufp;
+	d->bufp += n;
+	return p;
+}
+
--- /dev/null
+++ b/sys/src/libdraw/keyboard.c
@@ -1,0 +1,100 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.h>
+
+void
+closekeyboard(Keyboardctl *kc)
+{
+	if(kc == nil)
+		return;
+	close(kc->ctlfd);
+	close(kc->consfd);
+	kc->consfd = kc->ctlfd = -1;
+	threadint(kc->pid);
+}
+
+static
+void
+_ioproc(void *arg)
+{
+	int m, n, nerr;
+	char buf[20];
+	Rune r;
+	Keyboardctl *kc;
+
+	kc = arg;
+	threadsetname("kbdproc");
+	n = 0;
+	nerr = 0;
+	while(kc->consfd >= 0){
+		m = read(kc->consfd, buf+n, sizeof buf-n);
+		if(m <= 0){
+			yield();	/* if error is due to exiting, we'll exit here */
+			if(kc->consfd < 0)
+				break;
+			fprint(2, "keyboard: short read: %r\n");
+			if(m<0 || ++nerr>10)
+				threadexits("read error");
+			continue;
+		}
+		nerr = 0;
+		n += m;
+		while(n>0 && fullrune(buf, n)){
+			m = chartorune(&r, buf);
+			n -= m;
+			memmove(buf, buf+m, n);
+			if(send(kc->c, &r) < 0)
+				break;
+		}
+	}
+	chanfree(kc->c);
+	free(kc->file);
+	free(kc);
+}
+
+Keyboardctl*
+initkeyboard(char *file)
+{
+	Keyboardctl *kc;
+	char *t;
+
+	kc = mallocz(sizeof(Keyboardctl), 1);
+	if(kc == nil)
+		return nil;
+	if(file == nil)
+		file = "/dev/cons";
+	kc->file = strdup(file);
+	kc->consfd = open(file, ORDWR|OCEXEC);
+	t = malloc(strlen(file)+16);
+	if(kc->consfd<0 || t==nil){
+Error1:
+		free(kc);
+		return nil;
+	}
+	sprint(t, "%sctl", file);
+	kc->ctlfd = open(t, OWRITE|OCEXEC);
+	if(kc->ctlfd < 0){
+		fprint(2, "initkeyboard: can't open %s: %r\n", t);
+Error2:
+		close(kc->consfd);
+		free(t);
+		goto Error1;
+	}
+	if(ctlkeyboard(kc, "rawon") < 0){
+		fprint(2, "initkeyboard: can't turn on raw mode on %s: %r\n", t);
+		close(kc->ctlfd);
+		goto Error2;
+	}
+	free(t);
+	kc->c = chancreate(sizeof(Rune), 20);
+	kc->pid = proccreate(_ioproc, kc, 4096);
+	return kc;
+}
+
+int
+ctlkeyboard(Keyboardctl *kc, char *m)
+{
+	return write(kc->ctlfd, m, strlen(m));
+}
--- /dev/null
+++ b/sys/src/libdraw/line.c
@@ -1,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+void
+line(Image *dst, Point p0, Point p1, int end0, int end1, int radius, Image *src, Point sp)
+{
+	lineop(dst, p0, p1, end0, end1, radius, src, sp, SoverD);
+}
+
+void
+lineop(Image *dst, Point p0, Point p1, int end0, int end1, int radius, Image *src, Point sp, Drawop op)
+{
+	uchar *a;
+
+	_setdrawop(dst->display, op);
+
+	a = bufimage(dst->display, 1+4+2*4+2*4+4+4+4+4+2*4);
+	if(a == nil){
+		fprint(2, "image line: %r\n");
+		return;
+	}
+	a[0] = 'L';
+	BPLONG(a+1, dst->id);
+	BPLONG(a+5, p0.x);
+	BPLONG(a+9, p0.y);
+	BPLONG(a+13, p1.x);
+	BPLONG(a+17, p1.y);
+	BPLONG(a+21, end0);
+	BPLONG(a+25, end1);
+	BPLONG(a+29, radius);
+	BPLONG(a+33, src->id);
+	BPLONG(a+37, sp.x);
+	BPLONG(a+41, sp.y);
+}
--- /dev/null
+++ b/sys/src/libdraw/loadimage.c
@@ -1,0 +1,56 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+loadimage(Image *i, Rectangle r, uchar *data, int ndata)
+{
+	long dx, dy;
+	int n, bpl;
+	uchar *a;
+	int chunk;
+
+	chunk = i->display->bufsize - 64;
+
+	if(!rectinrect(r, i->r)){
+		werrstr("loadimage: bad rectangle");
+		return -1;
+	}
+	bpl = bytesperline(r, i->depth);
+	n = bpl*Dy(r);
+	if(n > ndata){
+		werrstr("loadimage: insufficient data");
+		return -1;
+	}
+	ndata = 0;
+	while(r.max.y > r.min.y){
+		dy = Dy(r);
+		dx = Dx(r);
+		if(dy*bpl > chunk)
+			dy = chunk/bpl;
+		if(dy <= 0){
+			dy = 1;
+			dx = ((chunk*dx)/bpl) & ~7;
+			n = bytesperline(Rect(r.min.x, r.min.y, r.min.x+dx, r.min.y+dy), i->depth);
+			if(loadimage(i, Rect(r.min.x+dx, r.min.y, r.max.x, r.min.y+dy), data+n, bpl-n) < 0)
+				return -1;
+		} else
+			n = dy*bpl;
+		a = bufimage(i->display, 21+n);
+		if(a == nil){
+			werrstr("loadimage: %r");
+			return -1;
+		}
+		a[0] = 'y';
+		BPLONG(a+1, i->id);
+		BPLONG(a+5, r.min.x);
+		BPLONG(a+9, r.min.y);
+		BPLONG(a+13, r.min.x+dx);
+		BPLONG(a+17, r.min.y+dy);
+		memmove(a+21, data, n);
+		ndata += dy*bpl;
+		data += dy*bpl;
+		r.min.y += dy;
+	}
+	return ndata;
+}
--- /dev/null
+++ b/sys/src/libdraw/makefile
@@ -1,0 +1,44 @@
+!include "..\mkconfig.nmk"
+!include "..\$(SYSHOST)\mkhost"
+!include "..\$(OBJDIR)\makefile"
+
+LIB=libimage.$A
+OFILES_C= \
+	alloc.$O\
+	arith.$O\
+	bezier.$O\
+	buildfont.$O\
+	bytesperline.$O\
+	creadimage.$O\
+	cursor.$O\
+	defont.$O\
+	draw.$O\
+	ellipse.$O\
+	font.$O\
+	freesubfont.$O\
+	getsubfont.$O\
+	init.$O\
+	line.$O\
+	loadimage.$O\
+	mkfont.$O\
+	openfont.$O\
+	poly.$O\
+	readimage.$O\
+	readsubfont.$O\
+	rectclip.$O\
+	replclipr.$O\
+	rgb.$O\
+	string.$O\
+	stringsubfont.$O\
+	stringwidth.$O\
+	subfont.$O\
+	subfontcache.$O\
+	subfontname.$O\
+	unloadimage.$O\
+	window.$O\
+	writeimage.$O\
+
+HFILES=\
+	../include/memimage.h
+
+!include <..\$(SYSTARG)\mksyslib.nmk>
--- /dev/null
+++ b/sys/src/libdraw/menuhit.c
@@ -1,0 +1,297 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+
+enum
+{
+	Margin = 4,		/* outside to text */
+	Border = 2,		/* outside to selection boxes */
+	Blackborder = 2,	/* width of outlining border */
+	Vspacing = 2,		/* extra spacing between lines of text */
+	Maxunscroll = 25,	/* maximum #entries before scrolling turns on */
+	Nscroll = 20,		/* number entries in scrolling part */
+	Scrollwid = 14,		/* width of scroll bar */
+	Gap = 4,			/* between text and scroll bar */
+};
+
+static	Image	*menutxt;
+static	Image	*back;
+static	Image	*high;
+static	Image	*bord;
+static	Image	*text;
+static	Image	*htext;
+
+static
+void
+menucolors(void)
+{
+	enum{
+		Cback,
+		Chigh,
+		Cbord,
+		Ctext,
+		Chtext,
+		Cmtext,
+		Ncols,
+	};
+	Theme th[Ncols] = {
+		[Cback] { "menuback",	0xEAFFEAFF },
+		[Chigh] { "menuhigh",	DDarkgreen },
+		[Cbord]	{ "menubord",	DMedgreen },
+		[Ctext]	{ "menutext",	DBlack },
+		[Chtext]{ "menuhtext",	0xEAFFEAFF },
+		[Cmtext]{ "menubar",	DDarkgreen },
+	};
+
+	readtheme(th, nelem(th), nil);
+	back = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cback].c);
+	high = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Chigh].c);
+	bord = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cbord].c);
+	text = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Ctext].c);
+	htext = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Chtext].c);
+	menutxt = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Cmtext].c);
+	if(back == nil || high == nil || bord == nil
+	|| text == nil || htext == nil || menutxt == nil)
+		goto Error;
+	return;
+
+    Error:
+	freeimage(back);
+	freeimage(high);
+	freeimage(bord);
+	freeimage(menutxt);
+	back = display->white;
+	high = display->black;
+	bord = display->black;
+	text = display->black;
+	htext = display->white;
+	menutxt = display->black;
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the rectangle, including its black edge, holding element i.
+ */
+static Rectangle
+menurect(Rectangle r, int i)
+{
+	if(i < 0)
+		return Rect(0, 0, 0, 0);
+	r.min.y += (font->height+Vspacing)*i;
+	r.max.y = r.min.y+font->height+Vspacing;
+	return insetrect(r, Border-Margin);
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the element number containing p.
+ */
+static int
+menusel(Rectangle r, Point p)
+{
+	if(!ptinrect(p, r))
+		return -1;
+	return (p.y-r.min.y)/(font->height+Vspacing);
+}
+
+static
+void
+paintitem(Image *m, Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore)
+{
+	char *item;
+	Rectangle r;
+	Point pt;
+
+	if(i < 0)
+		return;
+	r = menurect(textr, i);
+	if(restore){
+		draw(m, r, restore, nil, restore->r.min);
+		return;
+	}
+	if(save)
+		draw(save, save->r, m, nil, r.min);
+	item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
+	pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2;
+	pt.y = textr.min.y+i*(font->height+Vspacing);
+	draw(m, r, highlight? high : back, nil, pt);
+	string(m, pt, highlight? htext : text, pt, font, item);
+}
+
+/*
+ * menur is a rectangle holding all the highlightable text elements.
+ * track mouse while inside the box, return what's selected when button
+ * is raised, -1 as soon as it leaves box.
+ * invariant: nothing is highlighted on entry or exit.
+ */
+static int
+menuscan(Image *m, Menu *menu, int but, Mousectl *mc, Rectangle textr, int off, int lasti, Image *save)
+{
+	int i;
+
+	paintitem(m, menu, textr, off, lasti, 1, save, nil);
+	for(readmouse(mc); mc->buttons & (1<<(but-1)); readmouse(mc)){
+		i = menusel(textr, mc->xy);
+		if(i != -1 && i == lasti)
+			continue;
+		paintitem(m, menu, textr, off, lasti, 0, nil, save);
+		if(i == -1)
+			return i;
+		lasti = i;
+		paintitem(m, menu, textr, off, lasti, 1, save, nil);
+	}
+	return lasti;
+}
+
+static void
+menupaint(Image *m, Menu *menu, Rectangle textr, int off, int nitemdrawn)
+{
+	int i;
+
+	draw(m, insetrect(textr, Border-Margin), back, nil, ZP);
+	for(i = 0; i<nitemdrawn; i++)
+		paintitem(m, menu, textr, off, i, 0, nil, nil);
+}
+
+static void
+menuscrollpaint(Image *m, Rectangle scrollr, int off, int nitem, int nitemdrawn)
+{
+	Rectangle r;
+
+	draw(m, scrollr, back, nil, ZP);
+	r.min.x = scrollr.min.x;
+	r.max.x = scrollr.max.x;
+	r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
+	r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
+	if(r.max.y < r.min.y+2)
+		r.max.y = r.min.y+2;
+	border(m, r, 1, bord, ZP);
+	if(menutxt)
+		draw(m, insetrect(r, 1), menutxt, nil, ZP);
+}
+
+int
+menuhit(int but, Mousectl *mc, Menu *menu, Screen *scr)
+{
+	int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
+	int scrolling;
+	Rectangle r, menur, sc, textr, scrollr;
+	Image *b, *save, *backup;
+	Point pt;
+	char *item;
+
+	if(back == nil)
+		menucolors();
+	sc = screen->clipr;
+	replclipr(screen, 0, screen->r);
+	maxwid = 0;
+	for(nitem = 0;
+	    item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
+	    nitem++){
+		i = stringwidth(font, item);
+		if(i > maxwid)
+			maxwid = i;
+	}
+	if(menu->lasthit<0 || menu->lasthit>=nitem)
+		menu->lasthit = 0;
+	screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
+	if(nitem>Maxunscroll || nitem>screenitem){
+		scrolling = 1;
+		nitemdrawn = Nscroll;
+		if(nitemdrawn > screenitem)
+			nitemdrawn = screenitem;
+		wid = maxwid + Gap + Scrollwid;
+		off = menu->lasthit - nitemdrawn/2;
+		if(off < 0)
+			off = 0;
+		if(off > nitem-nitemdrawn)
+			off = nitem-nitemdrawn;
+		lasti = menu->lasthit-off;
+	}else{
+		scrolling = 0;
+		nitemdrawn = nitem;
+		wid = maxwid;
+		off = 0;
+		lasti = menu->lasthit;
+	}
+	r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
+	r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
+	r = rectaddpt(r, mc->xy);
+	pt = ZP;
+	if(r.max.x>screen->r.max.x)
+		pt.x = screen->r.max.x-r.max.x;
+	if(r.max.y>screen->r.max.y)
+		pt.y = screen->r.max.y-r.max.y;
+	if(r.min.x<screen->r.min.x)
+		pt.x = screen->r.min.x-r.min.x;
+	if(r.min.y<screen->r.min.y)
+		pt.y = screen->r.min.y-r.min.y;
+	menur = rectaddpt(r, pt);
+	textr.max.x = menur.max.x-Margin;
+	textr.min.x = textr.max.x-maxwid;
+	textr.min.y = menur.min.y+Margin;
+	textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
+	if(scrolling){
+		scrollr = insetrect(menur, Border);
+		scrollr.max.x = scrollr.min.x+Scrollwid;
+	}else
+		scrollr = Rect(0, 0, 0, 0);
+
+	if(scr){
+		b = allocwindow(scr, menur, Refbackup, DWhite);
+		if(b == nil)
+			b = screen;
+		backup = nil;
+	}else{
+		b = screen;
+		backup = allocimage(display, menur, screen->chan, 0, -1);
+		if(backup)
+			draw(backup, menur, screen, nil, menur.min);
+	}
+	draw(b, menur, back, nil, ZP);
+	border(b, menur, Blackborder, bord, ZP);
+	save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
+	r = menurect(textr, lasti);
+	if(pt.x || pt.y)
+		moveto(mc, divpt(addpt(r.min, r.max), 2));
+	menupaint(b, menu, textr, off, nitemdrawn);
+	if(scrolling)
+		menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
+	while(mc->buttons & (1<<(but-1))){
+		lasti = menuscan(b, menu, but, mc, textr, off, lasti, save);
+		if(lasti >= 0)
+			break;
+		while(!ptinrect(mc->xy, textr) && (mc->buttons & (1<<(but-1)))){
+			if(scrolling && ptinrect(mc->xy, scrollr)){
+				noff = ((mc->xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
+				noff -= nitemdrawn/2;
+				if(noff < 0)
+					noff = 0;
+				if(noff > nitem-nitemdrawn)
+					noff = nitem-nitemdrawn;
+				if(noff != off){
+					off = noff;
+					menupaint(b, menu, textr, off, nitemdrawn);
+					menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
+				}
+			}
+			readmouse(mc);
+		}
+	}
+	if(b != screen)
+		freeimage(b);
+	if(backup){
+		draw(screen, menur, backup, nil, menur.min);
+		freeimage(backup);
+	}
+	freeimage(save);
+	replclipr(screen, 0, sc);
+	flushimage(display, 1);
+	if(lasti >= 0){
+		menu->lasthit = lasti+off;
+		return menu->lasthit;
+	}
+	return -1;
+}
--- /dev/null
+++ b/sys/src/libdraw/mkfile
@@ -1,0 +1,79 @@
+</$objtype/mkfile
+
+LIB=/$objtype/lib/libdraw.a
+
+OFILES=\
+	alloc.$O\
+	allocimagemix.$O\
+	arith.$O\
+	badrect.$O\
+	bezier.$O\
+	border.$O\
+	buildfont.$O\
+	bytesperline.$O\
+	chan.$O\
+	cloadimage.$O\
+	computil.$O\
+	creadimage.$O\
+	debug.$O\
+	defont.$O\
+	draw.$O\
+	drawrepl.$O\
+	eenter.$O\
+	egetrect.$O\
+	ellipse.$O\
+	emenuhit.$O\
+	enter.$O\
+	event.$O\
+	fmt.$O\
+	font.$O\
+	freesubfont.$O\
+	getdefont.$O\
+	getrect.$O\
+	getsubfont.$O\
+	icossin.$O\
+	icossin2.$O\
+	init.$O\
+	keyboard.$O\
+	line.$O\
+	menuhit.$O\
+	mkfont.$O\
+	mouse.$O\
+	newwindow.$O\
+	openfont.$O\
+	poly.$O\
+	loadimage.$O\
+	readcolmap.$O\
+	readimage.$O\
+	readsubfont.$O\
+	readtheme.$O\
+	rectclip.$O\
+	replclipr.$O\
+	rgb.$O\
+	scroll.$O\
+	string.$O\
+	stringbg.$O\
+	stringsubfont.$O\
+	stringwidth.$O\
+	subfont.$O\
+	subfontcache.$O\
+	subfontname.$O\
+	unloadimage.$O\
+	window.$O\
+	writecolmap.$O\
+	writeimage.$O\
+	writesubfont.$O\
+
+HFILES=\
+	/sys/include/draw.h\
+	/sys/include/event.h\
+	/sys/include/mouse.h\
+	/sys/include/keyboard.h
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	${LIB:/$objtype/%=/386/%}\
+
+</sys/src/cmd/mksyslib
--- /dev/null
+++ b/sys/src/libdraw/mkfont.c
@@ -1,0 +1,55 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * Cobble fake font using existing subfont
+ */
+Font*
+mkfont(Subfont *subfont, Rune min)
+{
+	Font *font;
+	Cachefont *c;
+
+	font = malloc(sizeof(Font));
+	if(font == nil)
+		return nil;
+	memset(font, 0, sizeof(Font));
+	font->display = subfont->bits->display;
+	font->name = strdup("<synthetic>");
+	font->ncache = NFCACHE+NFLOOK;
+	font->nsubf = NFSUBF;
+	font->cache = malloc(font->ncache * sizeof(font->cache[0]));
+	font->subf = malloc(font->nsubf * sizeof(font->subf[0]));
+	if(font->name==nil || font->cache==nil || font->subf==nil){
+    Err:
+		free(font->name);
+		free(font->cache);
+		free(font->subf);
+		free(font->sub);
+		free(font);
+		return 0;
+	}
+	memset(font->cache, 0, font->ncache*sizeof(font->cache[0]));
+	memset(font->subf, 0, font->nsubf*sizeof(font->subf[0]));
+	font->height = subfont->height;
+	font->ascent = subfont->ascent;
+	font->age = 1;
+	font->sub = malloc(sizeof(Cachefont*));
+	if(font->sub == nil)
+		goto Err;
+	c = malloc(sizeof(Cachefont));
+	if(c == nil)
+		goto Err;
+	font->nsub = 1;
+	font->sub[0] = c;
+	c->min = min;
+	c->max = min+subfont->n-1;
+	c->offset = 0;
+	c->name = nil;	/* noticed by agefont() */
+	c->subfontname = nil;
+	font->subf[0].age = 0;
+	font->subf[0].cf = c;
+	font->subf[0].f = subfont;
+	return font;
+}
--- /dev/null
+++ b/sys/src/libdraw/mouse.c
@@ -1,0 +1,143 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+
+void
+moveto(Mousectl *m, Point pt)
+{
+	fprint(m->mfd, "m%d %d", pt.x, pt.y);
+	m->xy = pt;
+}
+
+void
+closemouse(Mousectl *mc)
+{
+	if(mc == nil)
+		return;
+	close(mc->mfd);
+	close(mc->cfd);
+	mc->mfd = mc->cfd = -1;
+	threadint(mc->pid);
+}
+
+int
+readmouse(Mousectl *mc)
+{
+	if(mc->image){
+		Display *d = mc->image->display;
+		if(d->bufp > d->buf)
+			flushimage(d, 1);
+	}
+	if(recv(mc->c, &mc->Mouse) < 0){
+		fprint(2, "readmouse: %r\n");
+		return -1;
+	}
+	return 0;
+}
+
+static
+void
+_ioproc(void *arg)
+{
+	int n, nerr, one;
+	char buf[1+5*12];
+	Mouse m;
+	Mousectl *mc;
+
+	mc = arg;
+	threadsetname("mouseproc");
+	memset(&m, 0, sizeof m);
+	nerr = 0;
+	while(mc->mfd >= 0){
+		n = read(mc->mfd, buf, sizeof buf);
+		if(n != 1+4*12){
+			yield();	/* if error is due to exiting, we'll exit here */
+			if(mc->mfd < 0)
+				break;
+			fprint(2, "mouse: bad count %d not 49: %r\n", n);
+			if(n<0 || ++nerr>10)
+				threadexits("read error");
+			continue;
+		}
+		nerr = 0;
+		switch(buf[0]){
+		case 'r':
+			one = 1;
+			if(nbsend(mc->resizec, &one) < 0)
+				continue;
+			/* fall through */
+		case 'm':
+			m.xy.x = atoi(buf+1+0*12);
+			m.xy.y = atoi(buf+1+1*12);
+			m.buttons = atoi(buf+1+2*12);
+			m.msec = (ulong)atoll(buf+1+3*12);
+			if(send(mc->c, &m) < 0)
+				continue;
+			/*
+			 * mc->Mouse is updated after send so it doesn't have wrong value if we block during send.
+			 * This means that programs should receive into mc->Mouse (see readmouse() above) if
+			 * they want full synchrony.
+			 */
+			mc->Mouse = m;
+			break;
+		}
+	}
+	free(mc->file);
+	chanfree(mc->c);
+	chanfree(mc->resizec);
+	free(mc);
+}
+
+Mousectl*
+initmouse(char *file, Image *i)
+{
+	Mousectl *mc;
+	char *t, *sl;
+
+	mc = mallocz(sizeof(Mousectl), 1);
+	if(file == nil)
+		file = "/dev/mouse";
+	mc->file = strdup(file);
+	mc->mfd = open(file, ORDWR|OCEXEC);
+	if(mc->mfd < 0){
+		free(mc);
+		return nil;
+	}
+	t = malloc(strlen(file)+16);
+	if (t == nil) {
+		close(mc->mfd);
+		free(mc);
+		return nil;
+	}
+	strcpy(t, file);
+	sl = utfrrune(t, '/');
+	if(sl)
+		strcpy(sl, "/cursor");
+	else
+		strcpy(t, "/dev/cursor");
+	mc->cfd = open(t, ORDWR|OCEXEC);
+	free(t);
+	mc->image = i;
+	mc->c = chancreate(sizeof(Mouse), 0);
+	mc->resizec = chancreate(sizeof(int), 1);
+	mc->pid = proccreate(_ioproc, mc, 4096);
+	return mc;
+}
+
+void
+setcursor(Mousectl *mc, Cursor *c)
+{
+	char curs[2*4+2*2*16];
+
+	if(c == nil)
+		write(mc->cfd, curs, 0);
+	else{
+		BPLONG(curs+0*4, c->offset.x);
+		BPLONG(curs+1*4, c->offset.y);
+		memmove(curs+2*4, c->clr, 2*2*16);
+		write(mc->cfd, curs, sizeof curs);
+	}
+}
--- /dev/null
+++ b/sys/src/libdraw/newwindow.c
@@ -1,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/* Connect us to new window, if possible */
+int
+newwindow(char *str)
+{
+	int fd;
+	char *wsys;
+	char buf[256];
+
+	wsys = getenv("wsys");
+	if(wsys == nil)
+		return -1;
+	fd = open(wsys, ORDWR);
+	if(fd < 0){
+		free(wsys);
+		return -1;
+	}
+	rfork(RFNAMEG);
+	unmount(wsys, "/dev");	/* drop reference to old window */
+	free(wsys);
+	if(str)
+		snprint(buf, sizeof buf, "new %s", str);
+	else
+		strcpy(buf, "new");
+	if(mount(fd, -1, "/mnt/wsys", MREPL, buf) == -1){
+		if(mount(fd, -1, "/dev", MBEFORE, buf) == -1){
+			close(fd);
+			return -1;
+		}
+	} else {
+		if(bind("/mnt/wsys", "/dev", MBEFORE) == -1)
+			return -1;
+	}
+	return 0;
+}
+
--- /dev/null
+++ b/sys/src/libdraw/openfont.c
@@ -1,0 +1,47 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static char*
+readfile(char *name)
+{
+	enum { HUNK = 8*1024, };
+	int f, n, r;
+	char *s, *p;
+
+	n = 0;
+	r = -1;
+	if((s = malloc(HUNK)) != nil){
+		if((f = open(name, OREAD|OCEXEC)) >= 0){
+			while((r = read(f, s+n, HUNK)) > 0){
+				n += r;
+				r = -1;
+				if((p = realloc(s, n+HUNK)) == nil)
+					break;
+				s = p;
+			}
+			close(f);
+		}
+	}
+	if(r < 0 || (p = realloc(s, n+1)) == nil){
+		free(s);
+		return nil;
+	}
+	p[n] = 0;
+	return p;
+}
+
+Font*
+openfont(Display *d, char *name)
+{
+	Font *fnt;
+	char *buf;
+
+	if((buf = readfile(name)) == nil){
+		werrstr("openfont: %r");
+		return nil;
+	}
+	fnt = buildfont(d, buf, name);
+	free(buf);
+	return fnt;
+}
--- /dev/null
+++ b/sys/src/libdraw/poly.c
@@ -1,0 +1,87 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static
+uchar*
+addcoord(uchar *p, int oldx, int newx)
+{
+	int dx;
+
+	dx = newx-oldx;
+	/* does dx fit in 7 signed bits? */
+	if((unsigned)(dx - -0x40) <= 0x7F)
+		*p++ = dx&0x7F;
+	else{
+		*p++ = 0x80 | (newx&0x7F);
+		*p++ = newx>>7;
+		*p++ = newx>>15;
+	}
+	return p;
+}
+
+static
+void
+dopoly(int cmd, Image *dst, Point *pp, int np, int end0, int end1, int radius, Image *src, Point *sp, Drawop op)
+{
+	uchar *a, *t, *u;
+	int i, ox, oy;
+
+	if(np == 0)
+		return;
+	t = malloc(np*2*3);
+	if(t == nil)
+		return;
+	u = t;
+	ox = oy = 0;
+	for(i=0; i<np; i++){
+		u = addcoord(u, ox, pp[i].x);
+		ox = pp[i].x;
+		u = addcoord(u, oy, pp[i].y);
+		oy = pp[i].y;
+	}
+
+	_setdrawop(dst->display, op);
+
+	a = bufimage(dst->display, 1+4+2+4+4+4+4+2*4+(u-t));
+	if(a == nil){
+		free(t);
+		fprint(2, "image poly: %r\n");
+		return;
+	}
+	a[0] = cmd;
+	BPLONG(a+1, dst->id);
+	BPSHORT(a+5, np-1);
+	BPLONG(a+7, end0);
+	BPLONG(a+11, end1);
+	BPLONG(a+15, radius);
+	BPLONG(a+19, src->id);
+	BPLONG(a+23, sp->x);
+	BPLONG(a+27, sp->y);
+	memmove(a+31, t, u-t);
+	free(t);
+}
+
+void
+poly(Image *dst, Point *p, int np, int end0, int end1, int radius, Image *src, Point sp)
+{
+	dopoly('p', dst, p, np, end0, end1, radius, src, &sp, SoverD);
+}
+
+void
+polyop(Image *dst, Point *p, int np, int end0, int end1, int radius, Image *src, Point sp, Drawop op)
+{
+	dopoly('p', dst, p, np, end0, end1, radius, src, &sp, op);
+}
+
+void
+fillpoly(Image *dst, Point *p, int np, int wind, Image *src, Point sp)
+{
+	dopoly('P', dst, p, np, wind, 0, 0, src, &sp, SoverD);
+}
+
+void
+fillpolyop(Image *dst, Point *p, int np, int wind, Image *src, Point sp, Drawop op)
+{
+	dopoly('P', dst, p, np, wind, 0, 0, src, &sp, op);
+}
--- /dev/null
+++ b/sys/src/libdraw/readcolmap.c
@@ -1,0 +1,49 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <bio.h>
+
+static ulong
+getval(char **p)
+{
+	ulong v;
+	char *q;
+
+	v = strtoul(*p, &q, 0);
+	v |= v<<8;
+	v |= v<<16;
+	*p = q;
+	return v;
+}
+
+void
+readcolmap(Display *d, RGB *colmap)
+{
+	int i;
+	char *p, *q;
+	Biobuf *b;
+	char buf[128];
+
+	USED(screen);
+
+	sprint(buf, "/dev/draw/%d/colormap", d->dirno);
+	b = Bopen(buf, OREAD|OCEXEC);
+	if(b == nil)
+		drawerror(d, "rdcolmap: can't open colormap device");
+
+	for(;;) {
+		p = Brdline(b, '\n');
+		if(p == 0)
+			break;
+		i = strtoul(p, &q, 0);
+		if(i < 0 || i > 255) {
+			fprint(2, "rdcolmap: bad index\n");
+			exits("bad");
+		}
+		p = q;
+		colmap[255-i].red = getval(&p);
+		colmap[255-i].green = getval(&p);
+		colmap[255-i].blue = getval(&p);
+	}
+	Bterm(b);
+}
--- /dev/null
+++ b/sys/src/libdraw/readimage.c
@@ -1,0 +1,133 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Image*
+readimage(Display *d, int fd, int dolock)
+{
+	char hdr[5*12+1];
+	int dy;
+	int new;
+	uint l, n;
+	int m, j, chunk;
+	int miny, maxy;
+	Rectangle r;
+	int ldepth;
+	ulong chan;
+	uchar *tmp;
+	Image *i;
+
+	if(readn(fd, hdr, 11) != 11)
+		return nil;
+	if(memcmp(hdr, "compressed\n", 11) == 0){
+		if(i = creadimage(d, fd, dolock))
+			goto Done;
+		return nil;
+	}
+		
+	if(readn(fd, hdr+11, 5*12-11) != 5*12-11)
+		return nil;
+	if(d != nil)
+		chunk = d->bufsize - 32;	/* a little room for header */
+	else
+		chunk = 8192;
+
+	/*
+	 * distinguish new channel descriptor from old ldepth.
+	 * channel descriptors have letters as well as numbers,
+	 * while ldepths are a single digit formatted as %-11d.
+	 */
+	new = 0;
+	for(m=0; m<10; m++){
+		if(hdr[m] != ' '){
+			new = 1;
+			break;
+		}
+	}
+	if(hdr[11] != ' '){
+		werrstr("readimage: bad format");
+		return nil;
+	}
+	if(new){
+		hdr[11] = '\0';
+		if((chan = strtochan(hdr)) == 0){
+			werrstr("readimage: bad channel string %s", hdr);
+			return nil;
+		}
+	}else{
+		ldepth = ((int)hdr[10])-'0';
+		if(ldepth<0 || ldepth>3){
+			werrstr("readimage: bad ldepth %d", ldepth);
+			return nil;
+		}
+		chan = drawld2chan[ldepth];
+	}
+
+	r.min.x = atoi(hdr+1*12);
+	r.min.y = atoi(hdr+2*12);
+	r.max.x = atoi(hdr+3*12);
+	r.max.y = atoi(hdr+4*12);
+	if(r.min.x>r.max.x || r.min.y>r.max.y){
+		werrstr("readimage: bad rectangle");
+		return nil;
+	}
+
+	miny = r.min.y;
+	maxy = r.max.y;
+
+	l = bytesperline(r, chantodepth(chan));
+	if(l > chunk)
+		chunk = l;
+	if(d != nil){
+		if(dolock)
+			lockdisplay(d);
+		i = allocimage(d, r, chan, 0, -1);
+		if(dolock)
+			unlockdisplay(d);
+		if(i == nil)
+			return nil;
+	}else{
+		i = mallocz(sizeof(Image), 1);
+		if(i == nil)
+			return nil;
+	}
+	tmp = malloc(chunk);
+	if(tmp == nil)
+		goto Err;
+	while(maxy > miny){
+		dy = maxy - miny;
+		if(dy*l > chunk)
+			dy = chunk/l;
+		n = dy*l;
+		m = readn(fd, tmp, n);
+		if(m != n){
+			werrstr("readimage: read count %d not %d: %r", m, n);
+   Err:
+			if(dolock)
+				lockdisplay(d);
+   Err1:
+ 			freeimage(i);
+			if(dolock)
+				unlockdisplay(d);
+			free(tmp);
+			return nil;
+		}
+		if(!new)	/* an old image: must flip all the bits */
+			for(j=0; j<chunk; j++)
+				tmp[j] ^= 0xFF;
+
+		if(d != nil){
+			if(dolock)
+				lockdisplay(d);
+			if(loadimage(i, Rect(r.min.x, miny, r.max.x, miny+dy), tmp, chunk) <= 0)
+				goto Err1;
+			if(dolock)
+				unlockdisplay(d);
+		}
+		miny += dy;
+	}
+	free(tmp);
+   Done:
+	setmalloctag(i, getcallerpc(&d));
+	return i;
+}
--- /dev/null
+++ b/sys/src/libdraw/readsubfont.c
@@ -1,0 +1,64 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Subfont*
+readsubfonti(Display*d, char *name, int fd, Image *ai, int dolock)
+{
+	char hdr[3*12+4+1];
+	int n;
+	uchar *p;
+	Fontchar *fc;
+	Subfont *f;
+	Image *i;
+
+	i = ai;
+	if(i == nil){
+		i = readimage(d, fd, dolock);
+		if(i == nil)
+			return nil;
+	}
+	p = nil;
+	if(readn(fd, hdr, 3*12) != 3*12){
+		werrstr("readsubfont: header read error: %r");
+		goto Err;
+	}
+	n = atoi(hdr);
+	if(n <= 0 || n > 0x7fff){
+		werrstr("readsubfont: bad fontchar count %d", n);
+		goto Err;
+	}
+	p = malloc(6*(n+1));
+	if(p == nil)
+		goto Err;
+	if(readn(fd, p, 6*(n+1)) != 6*(n+1)){
+		werrstr("readsubfont: fontchar read error: %r");
+		goto Err;
+	}
+	fc = malloc(sizeof(Fontchar)*(n+1));
+	if(fc == nil)
+		goto Err;
+	_unpackinfo(fc, p, n);
+	if(dolock)
+		lockdisplay(d);
+	f = allocsubfont(name, n, atoi(hdr+12), atoi(hdr+24), fc, i);
+	if(dolock)
+		unlockdisplay(d);
+	if(f == nil){
+		free(fc);
+		goto Err;
+	}
+	free(p);
+	return f;
+Err:
+	if(ai == nil)
+		freeimage(i);
+	free(p);
+	return nil;
+}
+
+Subfont*
+readsubfont(Display *d, char *name, int fd, int dolock)
+{
+	return readsubfonti(d, name, fd, nil, dolock);
+}
--- /dev/null
+++ b/sys/src/libdraw/readtheme.c
@@ -1,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <bio.h>
+
+int
+readtheme(Theme *col, int ncol, char *file)
+{
+	int i;
+	char *s, *v[3];
+	Biobuf *bf;
+
+	if(col == nil || ncol <= 0){
+		werrstr("no color to hand");
+		return -1;
+	}
+	for(i=0; i<ncol; i++)
+		if(col[i].name == nil){
+			werrstr("invalid color at index %d", i);
+			return -1;
+		}
+	if(file == nil)
+		file = "/dev/theme";
+	if((bf = Bopen(file, OREAD)) == nil)
+		return -1;
+	while((s = Brdline(bf, '\n')) != nil){
+		s[Blinelen(bf)-1] = 0;
+		if(tokenize(s, v, nelem(v)) <= 0)
+			continue;
+		for(i=0; i<ncol; i++)
+			if(strcmp(v[0], col[i].name) == 0)
+				col[i].c = strtoul(v[1], nil, 16)<<8 | 0xff;
+	}
+	Bterm(bf);
+	return 0;
+}
--- /dev/null
+++ b/sys/src/libdraw/rectclip.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+rectclip(Rectangle *rp, Rectangle b)		/* first by reference, second by value */
+{
+	Rectangle *bp = &b;
+	/*
+	 * Expand rectXrect() in line for speed
+	 */
+	if((rp->min.x<bp->max.x && bp->min.x<rp->max.x &&
+	    rp->min.y<bp->max.y && bp->min.y<rp->max.y)==0)
+		return 0;
+	/* They must overlap */
+	if(rp->min.x < bp->min.x)
+		rp->min.x = bp->min.x;
+	if(rp->min.y < bp->min.y)
+		rp->min.y = bp->min.y;
+	if(rp->max.x > bp->max.x)
+		rp->max.x = bp->max.x;
+	if(rp->max.y > bp->max.y)
+		rp->max.y = bp->max.y;
+	return 1;
+}
--- /dev/null
+++ b/sys/src/libdraw/replclipr.c
@@ -1,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+void
+replclipr(Image *i, int repl, Rectangle clipr)
+{
+	uchar *b;
+
+	b = bufimage(i->display, 22);
+	if(b == nil){
+		fprint(2, "replclipr: %r\n");
+		return;
+	}
+	b[0] = 'c';
+	BPLONG(b+1, i->id);
+	repl = repl!=0;
+	b[5] = repl;
+	BPLONG(b+6, clipr.min.x);
+	BPLONG(b+10, clipr.min.y);
+	BPLONG(b+14, clipr.max.x);
+	BPLONG(b+18, clipr.max.y);
+	i->repl = repl;
+	i->clipr = clipr;
+}
--- /dev/null
+++ b/sys/src/libdraw/rgb.c
@@ -1,0 +1,99 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * This original version, although fast and a true inverse of
+ * cmap2rgb, in the sense that rgb2cmap(cmap2rgb(c))
+ * returned the original color, does a terrible job for RGB
+ * triples that do not appear in the color map, so it has been
+ * replaced by the much slower version below, that loops
+ * over the color map looking for the nearest point in RGB
+ * space.  There is no visual psychology reason for that
+ * criterion, but it's easy to implement and the results are
+ * far more pleasing. 
+ *
+int
+rgb2cmap(int cr, int cg, int cb)
+{
+	int r, g, b, v, cv;
+
+	if(cr < 0)
+		cr = 0;
+	else if(cr > 255)
+		cr = 255;
+	if(cg < 0)
+		cg = 0;
+	else if(cg > 255)
+		cg = 255;
+	if(cb < 0)
+		cb = 0;
+	else if(cb > 255)
+		cb = 255;
+	r = cr>>6;
+	g = cg>>6;
+	b = cb>>6;
+	cv = cr;
+	if(cg > cv)
+		cv = cg;
+	if(cb > cv)
+		cv = cb;
+	v = (cv>>4)&3;
+	return ((((r<<2)+v)<<4)+(((g<<2)+b+v-r)&15));
+}
+*/
+
+int
+rgb2cmap(int cr, int cg, int cb)
+{
+	int i, r, g, b, sq;
+	ulong rgb;
+	int best, bestsq;
+
+	best = 0;
+	bestsq = 0x7FFFFFFF;
+	for(i=0; i<256; i++){
+		rgb = cmap2rgb(i);
+		r = (rgb>>16) & 0xFF;
+		g = (rgb>>8) & 0xFF;
+		b = (rgb>>0) & 0xFF;
+		sq = (r-cr)*(r-cr)+(g-cg)*(g-cg)+(b-cb)*(b-cb);
+		if(sq < bestsq){
+			bestsq = sq;
+			best = i;
+		}
+	}
+	return best;
+}
+
+int
+cmap2rgb(int c)
+{
+	int j, num, den, r, g, b, v, rgb;
+
+	r = c>>6;
+	v = (c>>4)&3;
+	j = (c-v+r)&15;
+	g = j>>2;
+	b = j&3;
+	den=r;
+	if(g>den)
+		den=g;
+	if(b>den)
+		den=b;
+	if(den==0) {
+		v *= 17;
+		rgb = (v<<16)|(v<<8)|v;
+	}
+	else{
+		num=17*(4*den+v);
+		rgb = ((r*num/den)<<16)|((g*num/den)<<8)|(b*num/den);
+	}
+	return rgb;
+}
+
+int
+cmap2rgba(int c)
+{
+	return (cmap2rgb(c)<<8)|0xFF;
+}
--- /dev/null
+++ b/sys/src/libdraw/scroll.c
@@ -1,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+mousescrollsize(int maxlines)
+{
+	static int lines, pcnt;
+	char *mss;
+
+	if(lines == 0 && pcnt == 0){
+		mss = getenv("mousescrollsize");
+		if(mss){
+			if(strchr(mss, '%') != nil)
+				pcnt = atof(mss);
+			else
+				lines = atoi(mss);
+			free(mss);
+		}
+		if(lines == 0 && pcnt == 0)
+			lines = 1;
+		if(pcnt>=100)
+			pcnt = 100;
+	}
+
+	if(lines)
+		return lines;
+	return pcnt * maxlines/100.0;	
+}
--- /dev/null
+++ b/sys/src/libdraw/string.c
@@ -1,0 +1,152 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+enum
+{
+	Max = 100
+};
+
+Point
+string(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s)
+{
+	return _string(dst, pt, src, sp, f, s, nil, 1<<24, dst->clipr, nil, ZP, SoverD);
+}
+
+Point
+stringop(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, Drawop op)
+{
+	return _string(dst, pt, src, sp, f, s, nil, 1<<24, dst->clipr, nil, ZP, op);
+}
+
+Point
+stringn(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, int len)
+{
+	return _string(dst, pt, src, sp, f, s, nil, len, dst->clipr, nil, ZP, SoverD);
+}
+
+Point
+stringnop(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, int len, Drawop op)
+{
+	return _string(dst, pt, src, sp, f, s, nil, len, dst->clipr, nil, ZP, op);
+}
+
+Point
+runestring(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r)
+{
+	return _string(dst, pt, src, sp, f, nil, r, 1<<24, dst->clipr, nil, ZP, SoverD);
+}
+
+Point
+runestringop(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, Drawop op)
+{
+	return _string(dst, pt, src, sp, f, nil, r, 1<<24, dst->clipr, nil, ZP, op);
+}
+
+Point
+runestringn(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, int len)
+{
+	return _string(dst, pt, src, sp, f, nil, r, len, dst->clipr, nil, ZP, SoverD);
+}
+
+Point
+runestringnop(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, int len, Drawop op)
+{
+	return _string(dst, pt, src, sp, f, nil, r, len, dst->clipr, nil, ZP, op);
+}
+
+Point
+_string(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, Rune *r, int len, Rectangle clipr, Image *bg, Point bgp, Drawop op)
+{
+	int m, n, wid, max, try;
+	ushort cbuf[Max], *c, *ec;
+	uchar *b;
+	char *subfontname;
+	char **sptr;
+	Rune **rptr, rune;
+	Subfont *sf;
+
+	if(s == nil){
+		s = "";
+		sptr = nil;
+	}else
+		sptr = &s;
+	if(r == nil){
+		r = (Rune*) L"";
+		rptr = nil;
+	}else
+		rptr = &r;
+	subfontname = nil;
+	sf = nil;
+	try = 0;
+	while((*s || *r) && len > 0){
+		max = Max;
+		if(len < max)
+			max = len;
+		if(subfontname){
+			freesubfont(sf);
+			if((sf=_getsubfont(f->display, subfontname)) == nil){
+				if(f->display->defaultfont == nil || f->display->defaultfont == f)
+					break;
+				f = f->display->defaultfont;
+			}
+		}
+		if((n = cachechars(f, sptr, rptr, cbuf, max, &wid, &subfontname)) <= 0){
+			if(n == 0){
+				if(++try > 10)
+					break;
+				continue;
+			}
+			if(*r)
+				r++;
+			else
+				s += chartorune(&rune, s);
+			len--;
+			continue;
+		}
+		try = 0;
+
+		_setdrawop(dst->display, op);
+
+		m = 47+2*n;
+		if(bg)
+			m += 4+2*4;
+		b = bufimage(dst->display, m);
+		if(b == nil){
+			fprint(2, "string: %r\n");
+			break;
+		}
+		if(bg)
+			b[0] = 'x';
+		else
+			b[0] = 's';
+		BPLONG(b+1, dst->id);
+		BPLONG(b+5, src->id);
+		BPLONG(b+9, f->cacheimage->id);
+		BPLONG(b+13, pt.x);
+		BPLONG(b+17, pt.y+f->ascent);
+		BPLONG(b+21, clipr.min.x);
+		BPLONG(b+25, clipr.min.y);
+		BPLONG(b+29, clipr.max.x);
+		BPLONG(b+33, clipr.max.y);
+		BPLONG(b+37, sp.x);
+		BPLONG(b+41, sp.y);
+		BPSHORT(b+45, n);
+		b += 47;
+		if(bg){
+			BPLONG(b, bg->id);
+			BPLONG(b+4, bgp.x);
+			BPLONG(b+8, bgp.y);
+			b += 12;
+		}
+		ec = &cbuf[n];
+		for(c=cbuf; c<ec; c++, b+=2)
+			BPSHORT(b, *c);
+		pt.x += wid;
+		bgp.x += wid;
+		agefont(f);
+		len -= n;
+	}
+	freesubfont(sf);
+	return pt;
+}
--- /dev/null
+++ b/sys/src/libdraw/stringbg.c
@@ -1,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Point
+stringbg(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, Image *bg, Point bgp)
+{
+	return _string(dst, pt, src, sp, f, s, nil, 1<<24, dst->clipr, bg, bgp, SoverD);
+}
+
+Point
+stringbgop(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, Image *bg, Point bgp, int op)
+{
+	return _string(dst, pt, src, sp, f, s, nil, 1<<24, dst->clipr, bg, bgp, op);
+}
+
+Point
+stringnbg(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, int len, Image *bg, Point bgp)
+{
+	return _string(dst, pt, src, sp, f, s, nil, len, dst->clipr, bg, bgp, SoverD);
+}
+
+Point
+stringnbgop(Image *dst, Point pt, Image *src, Point sp, Font *f, char *s, int len, Image *bg, Point bgp, int op)
+{
+	return _string(dst, pt, src, sp, f, s, nil, len, dst->clipr, bg, bgp, op);
+}
+
+Point
+runestringbg(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, Image *bg, Point bgp)
+{
+	return _string(dst, pt, src, sp, f, nil, r, 1<<24, dst->clipr, bg, bgp, SoverD);
+}
+
+Point
+runestringbgop(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, Image *bg, Point bgp, int op)
+{
+	return _string(dst, pt, src, sp, f, nil, r, 1<<24, dst->clipr, bg, bgp, op);
+}
+
+Point
+runestringnbg(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, int len, Image *bg, Point bgp)
+{
+	return _string(dst, pt, src, sp, f, nil, r, len, dst->clipr, bg, bgp, SoverD);
+}
+
+Point
+runestringnbgop(Image *dst, Point pt, Image *src, Point sp, Font *f, Rune *r, int len, Image *bg, Point bgp, int op)
+{
+	return _string(dst, pt, src, sp, f, nil, r, len, dst->clipr, bg, bgp, op);
+}
--- /dev/null
+++ b/sys/src/libdraw/stringsubfont.c
@@ -1,0 +1,65 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Point
+stringsubfont(Image *b, Point p, Image *color, Subfont *f, char *cs)
+{
+	int w, width;
+	uchar *s;
+	Rune c;
+	Fontchar *i;
+
+	s = (uchar*)cs;
+	for(; c=*s; p.x+=width){
+		width = 0;
+		if(c < Runeself)
+			s++;
+		else{
+			w = chartorune(&c, (char*)s);
+			if(w == 0){
+				s++;
+				continue;
+			}
+			s += w;
+		}
+		if(c >= f->n)
+			continue;
+		i = f->info+c;
+		width = i->width;
+		draw(b, Rect(p.x+i->left, p.y+i->top, p.x+i->left+(i[1].x-i[0].x), p.y+i->bottom),
+			color, f->bits, Pt(i->x, i->top));
+	}
+	return p;
+}
+
+Point
+strsubfontwidth(Subfont *f, char *cs)
+{
+	Rune c;
+	Point p;
+	uchar *s;
+	Fontchar *i;
+	int w, width;
+
+	p = Pt(0, f->height);
+	s = (uchar*)cs;
+	for(; c=*s; p.x+=width){
+		width = 0;
+		if(c < Runeself)
+			s++;
+		else{
+			w = chartorune(&c, (char*)s);
+			if(w == 0){
+				s++;
+				continue;
+			}
+			s += w;
+		}
+		if(c >= f->n)
+			continue;
+		i = f->info+c;
+		width = i->width;
+	}
+	return p;
+}
--- /dev/null
+++ b/sys/src/libdraw/stringwidth.c
@@ -1,0 +1,98 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+_stringnwidth(Font *f, char *s, Rune *r, int len)
+{
+	int wid, twid, n, max, try;
+	enum { Max = 64 };
+	ushort cbuf[Max];
+	Rune rune, **rptr;
+	char *subfontname, **sptr;
+	Subfont *sf;
+
+	if(s == nil){
+		s = "";
+		sptr = nil;
+	}else
+		sptr = &s;
+	if(r == nil){
+		r = L"";
+		rptr = nil;
+	}else
+		rptr = &r;
+	subfontname = nil;
+	sf = nil;
+	twid = 0;
+	try = 0;
+	while((*s || *r) && len > 0){
+		max = Max;
+		if(len < max)
+			max = len;
+		if(subfontname){
+			freesubfont(sf);
+			if((sf=_getsubfont(f->display, subfontname)) == nil){
+				if(f->display == nil || f->display->defaultfont == nil || f->display->defaultfont == f)
+					break;
+				f = f->display->defaultfont;
+			}
+		}
+		if((n = cachechars(f, sptr, rptr, cbuf, max, &wid, &subfontname)) <= 0){
+			if(n == 0){
+				if(++try > 10)
+					break;
+				continue;
+			}
+			if(*r)
+				r++;
+			else
+				s += chartorune(&rune, s);
+			len--;
+			continue;
+		}
+		try = 0;
+
+		agefont(f);
+		twid += wid;
+		len -= n;
+	}
+	freesubfont(sf);
+	return twid;
+}
+
+int
+stringnwidth(Font *f, char *s, int len)
+{
+	return _stringnwidth(f, s, nil, len);
+}
+
+int
+stringwidth(Font *f, char *s)
+{
+	return _stringnwidth(f, s, nil, 1<<24);
+}
+
+Point
+stringsize(Font *f, char *s)
+{
+	return Pt(_stringnwidth(f, s, nil, 1<<24), f->height);
+}
+
+int
+runestringnwidth(Font *f, Rune *r, int len)
+{
+	return _stringnwidth(f, nil, r, len);
+}
+
+int
+runestringwidth(Font *f, Rune *r)
+{
+	return _stringnwidth(f, nil, r, 1<<24);
+}
+
+Point
+runestringsize(Font *f, Rune *r)
+{
+	return Pt(_stringnwidth(f, nil, r, 1<<24), f->height);
+}
--- /dev/null
+++ b/sys/src/libdraw/subfont.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+Subfont*
+allocsubfont(char *name, int n, int height, int ascent, Fontchar *info, Image *i)
+{
+	Subfont *f;
+
+	assert(height != 0 /* allocsubfont */);
+
+	f = malloc(sizeof(Subfont));
+	if(f == nil)
+		return nil;
+	f->n = n;
+	f->height = height;
+	f->ascent = ascent;
+	f->info = info;
+	f->bits = i;
+	f->ref = 1;
+	if(name){
+		f->name = strdup(name);
+		if(f->name == nil){
+			free(f);
+			return nil;
+		}
+		installsubfont(name, f);
+	}else
+		f->name = nil;
+	return f;
+}
--- /dev/null
+++ b/sys/src/libdraw/subfontcache.c
@@ -1,0 +1,41 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * Easy versions of the cache routines; may be substituted by fancier ones for other purposes
+ */
+
+static char	*lastname;
+static Subfont	*lastsubfont;
+
+Subfont*
+lookupsubfont(Display *d, char *name)
+{
+	if(d && strcmp(name, "*default*") == 0)
+		return d->defaultsubfont;
+	if(lastname && strcmp(name, lastname)==0)
+	if(d==lastsubfont->bits->display){
+		lastsubfont->ref++;
+		return lastsubfont;
+	}
+	return 0;
+}
+
+void
+installsubfont(char *name, Subfont *subfont)
+{
+	free(lastname);
+	lastname = strdup(name);
+	lastsubfont = subfont;	/* notice we don't free the old one; that's your business */
+}
+
+void
+uninstallsubfont(Subfont *subfont)
+{
+	if(subfont == lastsubfont){
+		free(lastname);
+		lastname = 0;
+		lastsubfont = 0;
+	}
+}
--- /dev/null
+++ b/sys/src/libdraw/subfontname.c
@@ -1,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * Default version: convert to file name
+ */
+
+char*
+subfontname(char *cfname, char *fname, int maxdepth)
+{
+	char *t, *u, *tmp1, *tmp2;
+	int i;
+
+	t = strdup(cfname);  /* t is the return string */
+	if(strcmp(cfname, "*default*") == 0)
+		return t;
+	if(t[0] != '/'){
+		tmp2 = strdup(fname);
+		u = utfrrune(tmp2, '/');
+		if(u)
+			u[0] = 0;
+		else
+			strcpy(tmp2, ".");
+		tmp1 = smprint("%s/%s", tmp2, t);
+		free(tmp2);
+		free(t);
+		t = tmp1;
+	}
+
+	if(maxdepth > 8)
+		maxdepth = 8;
+
+	for(i=3; i>=0; i--){
+		if((1<<i) > maxdepth)
+			continue;
+		/* try i-bit grey */
+		tmp2 = smprint("%s.%d", t, i);
+		if(access(tmp2, AREAD) == 0) {
+			free(t);
+			return tmp2;
+		}
+		free(tmp2);
+	}
+
+	/* try default */
+	if(access(t, AREAD) == 0)
+		return t;
+
+	free(t);
+	return nil;
+}
--- /dev/null
+++ b/sys/src/libdraw/test.c
@@ -1,0 +1,10 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+
+void
+main(int argc, char **argv)
+{
+		print("%dn", wordsperline(Rect(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]), atoi(argv[4])), atoi(argv[5])));
+}
--- /dev/null
+++ b/sys/src/libdraw/unloadimage.c
@@ -1,0 +1,58 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+int
+unloadimage(Image *i, Rectangle r, uchar *data, int ndata)
+{
+	int bpl, n, chunk, dx, dy;
+	uchar *a, *start;
+	Display *d;
+
+	if(!rectinrect(r, i->r)){
+		werrstr("unloadimage: bad rectangle");
+		return -1;
+	}
+	bpl = bytesperline(r, i->depth);
+	if(ndata < bpl*Dy(r)){
+		werrstr("unloadimage: buffer too small");
+		return -1;
+	}
+	start = data;
+	d = i->display;
+	chunk = d->bufsize;
+	flushimage(d, 0);	/* make sure subsequent flush is for us only */
+	while(r.min.y < r.max.y){
+		dx = Dx(r);
+		dy = chunk/bpl;
+		if(dy <= 0){
+			dy = 1;
+			dx = ((chunk*dx)/bpl) & ~7;
+			n = bytesperline(Rect(r.min.x, r.min.y, r.min.x+dx, r.min.y+dy), i->depth);
+			if(unloadimage(i, Rect(r.min.x+dx, r.min.y, r.max.x, r.min.y+dy), data+n, bpl-n) < 0)
+				return -1;
+		} else {
+			if(dy > Dy(r))
+				dy = Dy(r);
+			n = bpl*dy;
+		}
+		a = bufimage(d, 1+4+4*4);
+		if(a == nil){
+			werrstr("unloadimage: %r");
+			return -1;
+		}
+		a[0] = 'r';
+		BPLONG(a+1, i->id);
+		BPLONG(a+5, r.min.x);
+		BPLONG(a+9, r.min.y);
+		BPLONG(a+13, r.min.x+dx);
+		BPLONG(a+17, r.min.y+dy);
+		if(flushimage(d, 0) < 0)
+			return -1;
+		if(read(d->fd, data, n) < 0)
+			return -1;
+		data += bpl*dy;
+		r.min.y += dy;
+	}
+	return data - start;
+}
--- /dev/null
+++ b/sys/src/libdraw/window.c
@@ -1,0 +1,210 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+typedef struct Memimage Memimage;
+
+static int	screenid;
+
+Screen*
+allocscreen(Image *image, Image *fill, int public)
+{
+	uchar *a;
+	Screen *s;
+	int id, try;
+	Display *d;
+
+	d = image->display;
+	if(d != fill->display){
+		werrstr("allocscreen: image and fill on different displays");
+		return nil;
+	}
+	s = malloc(sizeof(Screen));
+	if(s == nil)
+		return nil;
+	if(!screenid)
+		screenid = getpid();
+	for(try=0; try<25; try++){
+		/* loop until find a free id */
+		a = bufimage(d, 1+4+4+4+1);
+		if(a == nil)
+			break;
+		id = ++screenid & 0xffff;	/* old devdraw bug */
+		a[0] = 'A';
+		BPLONG(a+1, id);
+		BPLONG(a+5, image->id);
+		BPLONG(a+9, fill->id);
+		a[13] = public;
+		if(flushimage(d, 0) != -1)
+			goto Found;
+	}
+	free(s);
+	return nil;
+
+    Found:
+	s->display = d;
+	s->id = id;
+	s->image = image;
+	assert(s->image != nil && s->image->chan != 0);
+
+	s->fill = fill;
+	return s;
+}
+
+Screen*
+publicscreen(Display *d, int id, ulong chan)
+{
+	uchar *a;
+	Screen *s;
+
+	s = malloc(sizeof(Screen));
+	if(s == nil)
+		return nil;
+	a = bufimage(d, 1+4+4);
+	if(a == nil){
+Error:
+		free(s);
+		return nil;
+	}
+	a[0] = 'S';
+	BPLONG(a+1, id);
+	BPLONG(a+5, chan);
+	if(flushimage(d, 0) < 0)
+		goto Error;
+
+	s->display = d;
+	s->id = id;
+	s->image = nil;
+	s->fill = nil;
+	return s;
+}
+
+int
+freescreen(Screen *s)
+{
+	uchar *a;
+	Display *d;
+
+	if(s == nil)
+		return 0;
+	d = s->display;
+	a = bufimage(d, 1+4);
+	if(a == nil){
+		free(s);
+		return -1;
+	}
+	a[0] = 'F';
+	BPLONG(a+1, s->id);
+	free(s);
+	return 1;
+}
+
+Image*
+allocwindow(Screen *s, Rectangle r, int ref, ulong col)
+{
+	return _allocwindow(nil, s, r, ref, col);
+}
+
+Image*
+_allocwindow(Image *i, Screen *s, Rectangle r, int ref, ulong col)
+{
+	Display *d;
+
+	d = s->display;
+	i = _allocimage(i, d, r, d->screenimage->chan, 0, col, s->id, ref);
+	if(i == nil)
+		return nil;
+	i->screen = s;
+	i->next = s->display->windows;
+	s->display->windows = i;
+	return i;
+}
+
+static
+void
+topbottom(Image **w, int n, int top)
+{
+	int i;
+	uchar *b;
+	Display *d;
+
+	if(n < 0){
+    Ridiculous:
+		fprint(2, "top/bottom: ridiculous number of windows\n");
+		return;
+	}
+	if(n == 0)
+		return;
+	if(n > (w[0]->display->bufsize-100)/4)
+		goto Ridiculous;
+	/*
+	 * this used to check that all images were on the same screen.
+	 * we don't know the screen associated with images we acquired
+	 * by name.  instead, check that all images are on the same display.
+	 * the display will check that they are all on the same screen.
+	 */
+	d = w[0]->display;
+	for(i=1; i<n; i++)
+		if(w[i]->display != d){
+			fprint(2, "top/bottom: windows not on same screen\n");
+			return;
+		}
+
+	if(n==0)
+		return;
+	b = bufimage(d, 1+1+2+4*n);
+	if(b == nil)
+		return;
+	b[0] = 't';
+	b[1] = top;
+	BPSHORT(b+2, n);
+	for(i=0; i<n; i++)
+		BPLONG(b+4+4*i, w[i]->id);
+}
+
+void
+bottomwindow(Image *w)
+{
+	if(w->screen != nil)
+		topbottom(&w, 1, 0);
+}
+
+void
+topwindow(Image *w)
+{
+	if(w->screen != nil)
+		topbottom(&w, 1, 1);
+}
+
+void
+bottomnwindows(Image **w, int n)
+{
+	topbottom(w, n, 0);
+}
+
+void
+topnwindows(Image **w, int n)
+{
+	topbottom(w, n, 1);
+}
+
+int
+originwindow(Image *w, Point log, Point scr)
+{
+	uchar *b;
+	Point delta;
+
+	b = bufimage(w->display, 1+4+2*4+2*4);
+	if(b == nil)
+		return 0;
+	b[0] = 'o';
+	BPLONG(b+1, w->id);
+	BPLONG(b+5, log.x);
+	BPLONG(b+9, log.y);
+	BPLONG(b+13, scr.x);
+	BPLONG(b+17, scr.y);
+	delta = subpt(log, w->r.min);
+	w->r = rectaddpt(w->r, delta);
+	w->clipr = rectaddpt(w->clipr, delta);
+	return 1;
+}
--- /dev/null
+++ b/sys/src/libdraw/writecolmap.c
@@ -1,0 +1,37 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * This code (and the devdraw interface) will have to change
+ * if we ever get bitmaps with ldepth > 3, because the
+ * colormap will have to be written in chunks
+ */
+
+void
+writecolmap(Display *d, RGB *m)
+{
+	int i, n, fd;
+	char buf[64], *t;
+	ulong r, g, b;
+
+	sprint(buf, "/dev/draw/%d/colormap", d->dirno);
+	fd = open(buf, OWRITE|OCEXEC);
+	if(fd < 0)
+		drawerror(d, "writecolmap: open colormap failed");
+	t = malloc(8192);
+	if (t == nil)
+		drawerror(d, "writecolmap: no memory");
+	n = 0;
+	for(i = 0; i < 256; i++) {
+		r = m[i].red>>24;
+		g = m[i].green>>24;
+		b = m[i].blue>>24;
+		n += sprint(t+n, "%d %lud %lud %lud\n", 255-i, r, g, b);
+	}
+	i = write(fd, t, n);
+	free(t);
+	close(fd);
+	if(i != n)
+		drawerror(d, "writecolmap: bad write");
+}
--- /dev/null
+++ b/sys/src/libdraw/writeimage.c
@@ -1,0 +1,213 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+#define	HSHIFT	3	/* HSHIFT==5 runs slightly faster, but hash table is 64x bigger */
+#define	NHASH	(1<<(HSHIFT*NMATCH))
+#define	HMASK	(NHASH-1)
+#define	hupdate(h, c)	((((h)<<HSHIFT)^(c))&HMASK)
+typedef struct Hlist Hlist;
+struct Hlist{
+	uchar *s;
+	Hlist *next, *prev;
+};
+
+int
+writeimage(int fd, Image *i, int dolock)
+{
+	uchar *outbuf, *outp, *eout;		/* encoded data, pointer, end */
+	uchar *loutp;				/* start of encoded line */
+	Hlist *hash;				/* heads of hash chains of past strings */
+	Hlist *chain, *hp;			/* hash chain members, pointer */
+	Hlist *cp;				/* next Hlist to fall out of window */
+	int h;					/* hash value */
+	uchar *line, *eline;			/* input line, end pointer */
+	uchar *data, *edata;			/* input buffer, end pointer */
+	ulong n;				/* length of input buffer */
+	ulong nb;				/* # of bytes returned by unloadimage */
+	int bpl;				/* input line length */
+	int offs, runlen;			/* offset, length of consumed data */
+	uchar dumpbuf[NDUMP];			/* dump accumulator */
+	int ndump;				/* length of dump accumulator */
+	int miny, dy;				/* y values while unloading input */
+	int chunk, ncblock;
+	Rectangle r;
+	uchar *p, *q, *s, *es, *t;
+	char hdr[11+5*12+1];
+	char cbuf[20];
+
+	chunk = i->display->bufsize - 32;	/* a little room for header */
+	r = i->r;
+	bpl = bytesperline(r, i->depth);
+	ncblock = _compblocksize(r, i->depth);
+	if(ncblock > chunk){
+		sprint(hdr, "%11s %11d %11d %11d %11d ",
+			chantostr(cbuf, i->chan), r.min.x, r.min.y, r.max.x, r.max.y);
+		if(write(fd, hdr, 5*12) != 5*12)
+			return -1;
+		data = malloc(bpl);
+		for(miny = r.min.y; miny != r.max.y; miny++){
+			if(dolock)
+				lockdisplay(i->display);
+			nb = unloadimage(i, Rect(r.min.x, miny, r.max.x, miny+1), data, bpl);
+			if(dolock)
+				unlockdisplay(i->display);
+			if(nb != bpl)
+				goto ErrOut0;
+			if(write(fd, data, nb) != nb)
+				goto ErrOut0;
+		}
+		free(data);
+		return 0;
+	}
+
+	n = Dy(r)*bpl;
+	data = malloc(n);
+	if(data == 0){
+	ErrOut0:
+		free(data);
+		return -1;
+	}
+	for(miny = r.min.y; miny != r.max.y; miny += dy){
+		dy = r.max.y-miny;
+		if(dy*bpl > chunk)
+			dy = chunk/bpl;
+		if(dy <= 0)
+			dy = 1;
+		if(dolock)
+			lockdisplay(i->display);
+		nb = unloadimage(i, Rect(r.min.x, miny, r.max.x, miny+dy),
+			data+(miny-r.min.y)*bpl, dy*bpl);
+		if(dolock)
+			unlockdisplay(i->display);
+		if(nb != dy*bpl)
+			goto ErrOut0;
+	}
+
+	outbuf = malloc(ncblock);
+	hash = malloc(NHASH*sizeof(Hlist));
+	chain = malloc(NMEM*sizeof(Hlist));
+	if(outbuf == 0 || hash == 0 || chain == 0){
+	ErrOut:
+		free(outbuf);
+		free(hash);
+		free(chain);
+		goto ErrOut0;
+	}
+	sprint(hdr, "compressed\n%11s %11d %11d %11d %11d ",
+		chantostr(cbuf, i->chan), r.min.x, r.min.y, r.max.x, r.max.y);
+	if(write(fd, hdr, 11+5*12) != 11+5*12)
+		goto ErrOut;
+	edata = data+n;
+	eout = outbuf+ncblock;
+	line = data;
+	r.max.y = r.min.y;
+	while(line != edata){
+		memset(hash, 0, NHASH*sizeof(Hlist));
+		memset(chain, 0, NMEM*sizeof(Hlist));
+		cp = chain;
+		h = 0;
+		outp = outbuf;
+		for(n = 0; n != NMATCH; n++)
+			h = hupdate(h, line[n]);
+		loutp = outbuf;
+		while(line != edata){
+			ndump = 0;
+			eline = line+bpl;
+			for(p = line; p != eline; ){
+				if(eline-p < NRUN)
+					es = eline;
+				else
+					es = p+NRUN;
+				q = 0;
+				runlen = 0;
+				for(hp = hash[h].next; hp; hp = hp->next){
+					s = p + runlen;
+					if(s >= es)
+						continue;
+					t = hp->s + runlen;
+					for(; s >= p; s--)
+						if(*s != *t--)
+							goto matchloop;
+					t += runlen+2;
+					s += runlen+2;
+					for(; s < es; s++)
+						if(*s != *t++)
+							break;
+					n = s-p;
+					if(n > runlen){
+						runlen = n;
+						q = hp->s;
+						if(n == NRUN)
+							break;
+					}
+			matchloop: ;
+				}
+				if(runlen < NMATCH){
+					if(ndump == NDUMP){
+						if(eout-outp < ndump+1)
+							goto Bfull;
+						*outp++ = ndump-1+128;
+						memmove(outp, dumpbuf, ndump);
+						outp += ndump;
+						ndump = 0;
+					}
+					dumpbuf[ndump++] = *p;
+					runlen = 1;
+				}
+				else{
+					if(ndump != 0){
+						if(eout-outp < ndump+1)
+							goto Bfull;
+						*outp++ = ndump-1+128;
+						memmove(outp, dumpbuf, ndump);
+						outp += ndump;
+						ndump = 0;
+					}
+					offs = p-q-1;
+					if(eout-outp < 2)
+						goto Bfull;
+					*outp++ = ((runlen-NMATCH)<<2) + (offs>>8);
+					*outp++ = offs&255;
+				}
+				for(q = p+runlen; p != q; p++){
+					if(cp->prev)
+						cp->prev->next = 0;
+					cp->next = hash[h].next;
+					cp->prev = &hash[h];
+					if(cp->next)
+						cp->next->prev = cp;
+					cp->prev->next = cp;
+					cp->s = p;
+					if(++cp == &chain[NMEM])
+						cp = chain;
+					if(edata-p > NMATCH)
+						h = hupdate(h, p[NMATCH]);
+				}
+			}
+			if(ndump != 0){
+				if(eout-outp < ndump+1)
+					goto Bfull;
+				*outp++ = ndump-1+128;
+				memmove(outp, dumpbuf, ndump);
+				outp += ndump;
+			}
+			line = eline;
+			loutp = outp;
+			r.max.y++;
+		}
+	Bfull:
+		if(loutp == outbuf)
+			goto ErrOut;
+		n = loutp-outbuf;
+		sprint(hdr, "%11d %11ld ", r.max.y, n);
+		write(fd, hdr, 2*12);
+		write(fd, outbuf, n);
+		r.min.y = r.max.y;
+	}
+	free(data);
+	free(outbuf);
+	free(hash);
+	free(chain);
+	return 0;
+}
--- /dev/null
+++ b/sys/src/libdraw/writesubfont.c
@@ -1,0 +1,45 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+static
+void
+packinfo(Fontchar *fc, uchar *p, int n)
+{
+	int j;
+
+	for(j=0;  j<=n;  j++){
+		p[0] = fc->x;
+		p[1] = fc->x>>8;
+		p[2] = fc->top;
+		p[3] = fc->bottom;
+		p[4] = fc->left;
+		p[5] = fc->width;
+		fc++;
+		p += 6;
+	}
+}
+
+int
+writesubfont(int fd, Subfont *f)
+{
+	char hdr[3*12+1];
+	uchar *data;
+	int nb;
+
+	sprint(hdr, "%11d %11d %11d ", f->n, f->height, f->ascent);
+	if(write(fd, hdr, 3*12) != 3*12){
+   Err:
+		werrstr("writesubfont: bad write: %r");
+		return -1;
+	}
+	nb = 6*(f->n+1);
+	data = malloc(nb);
+	if(data == nil)
+		return -1;
+	packinfo(f->info, data, f->n);
+	if(write(fd, data, nb) != nb)
+		goto Err;
+	free(data);
+	return 0;
+}