Plan 9 from Bell Labs’s /usr/web/sources/extra/i/layout.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include "i.h"

typedef struct Fontinfo Fontinfo;
typedef struct Colornode Colornode;
typedef struct Source Source;
typedef struct Shtml Shtml;
typedef struct Simage Simage;
typedef struct Sources Sources;

struct Fontinfo
{
	char*	name;
	Font*	f;
	int		spw;		// width of a space in this font
};

// Seems better to use a slightly smaller font in Controls, to match other browsers
enum { CtlFnt = FntR*NumSize+Small };

// color stuff.  have hash table mapping RGB values to Image for that color
struct Colornode
{
	int			rgb;
	Image*		im;
	Colornode*	next;
};

// Source of info for page (html, image, etc.)
struct Source
{
	Source*		next;			// in list of source for a page
	ByteSource*	bs;
	int			redirects;
	int			tag;			// Shtmltag or Simagetag
};


struct Shtml
{
	Source*		next;			// in list of source for a page
	ByteSource*	bs;
	int			redirects;
	int			tag;			// Shtmltag
	ItemSource*	itsrc;
};


struct Simage
{
	Source*		next;			// in list of source for a page
	ByteSource*	bs;
	int			redirects;
	int			tag;			// Simagetag
	CImage*		ci;
	Itemlist*		itl;
	ImageSource*	imsrc;
};

enum { Shtmltag, Simagetag };


struct Sources
{
	Source*	srcs;
	int	tot;
	int	done;
};

enum {
	NCOLHASH = 19,	// 19 checked for standard colors: only 1 collision
	TABPIX = 30,		// number of pixels in a tab
	CAPSEP = 5,		// number of pixels separating tab from caption
	SCRBREADTH = 20,	// scrollbar breadth (normal)
	SCRFBREADTH = 14,	// scrollbar breadth (inside child frame or select control)
	FRMARGIN = 10,	// default margin around frames
	RULESP = 7,		// extra space before and after rules
	CBOXWID = 14,		// check box width
	CBOXHT = 12,		// check box height
	ENTVMARGIN = 4,	// vertical margin inside entry box
	ENTHMARGIN = 6,	// horizontal margin inside entry box
	SELMARGIN = 4,	// margin inside select control
	BUTMARGIN = 4,	// margin inside button control
	PBOXWID = 16,		// progress box width
	PBOXHT = 16,		// progress box height
	SELBG = 0x00FFFF,	// aqua
	CTRLMASK = 0x1F,
	DEL = 0x7F,
	TAB = '\t',
	CR = '\n',
	TABLEMAXTARGET = 2000,	// targetwidth to get max width of table cell
	TABLEFLOATTARGET = 600,	// targetwidth for floating tables
};

// triangle shapes
enum { TRIup, TRIdown, TRIleft, TRIright };

Fontinfo	fonts[NumFnt]= {
	{"/lib/font/bit/lucidasans/unicode.6.font", nil, 0},
	{"/lib/font/bit/lucidasans/unicode.7.font", nil, 0},
	{"/lib/font/bit/lucidasans/unicode.8.font", nil, 0},
	{"/lib/font/bit/lucidasans/unicode.10.font", nil, 0},
	{"/lib/font/bit/lucidasans/unicode.13.font", nil, 0},
	{"/lib/font/bit/lucidasans/italicunicode.6.font", nil, 0},
	{"/lib/font/bit/lucidasans/italicunicode.7.font", nil, 0},
	{"/lib/font/bit/lucidasans/italicunicode.8.font", nil, 0},
	{"/lib/font/bit/lucidasans/italicunicode.10.font", nil, 0},
	{"/lib/font/bit/lucidasans/italicunicode.13.font", nil, 0},
	{"/lib/font/bit/lucidasans/boldunicode.6.font", nil, 0},
	{"/lib/font/bit/lucidasans/boldunicode.7.font", nil, 0},
	{"/lib/font/bit/lucidasans/boldunicode.8.font", nil, 0},
	{"/lib/font/bit/lucidasans/boldunicode.10.font", nil, 0},
	{"/lib/font/bit/lucidasans/boldunicode.13.font", nil, 0},
	{"/lib/font/bit/lucidasans/typeunicode.6.font", nil, 0},
	{"/lib/font/bit/lucidasans/typeunicode.7.font", nil, 0},
	{"/lib/font/bit/lucidasans/typeunicode.9.font", nil, 0},
	{"/lib/font/bit/lucidasans/typeunicode.12.font", nil, 0},
	{"/lib/font/bit/lucidasans/typeunicode.16.font", nil, 0}
};

Colornode*	colorhashtab[19];

uchar	wordchar[0xA0]= {
	['0']  1, ['1']  1, ['2']  1, ['3']  1, ['4']  1,
	['5']  1, ['6']  1, ['7']  1, ['8']  1, ['9']  1,
	['A']  1, ['B']  1, ['C']  1, ['D']  1, ['E']  1, ['F']  1,
	['G']  1, ['H']  1, ['I']  1, ['J']  1, ['K']  1, ['L']  1,
	['M']  1, ['N']  1, ['O']  1, ['P']  1, ['Q']  1, ['R']  1,
	['S']  1, ['T']  1, ['U']  1, ['V']  1, ['W']  1, ['X']  1,
	['Y']  1, ['Z']  1,
	['a']  1, ['b']  1, ['c']  1, ['d']  1, ['e']  1, ['f']  1,
	['g']  1, ['h']  1, ['i']  1, ['j']  1, ['k']  1, ['l']  1,
	['m']  1, ['n']  1, ['o']  1, ['p']  1, ['q']  1, ['r']  1,
	['s']  1, ['t']  1, ['u']  1, ['v']  1, ['w']  1, ['x']  1,
	['y']  1, ['z']  1,
	['_']  1,
	['\'']  1, ['"']  1, ['.']  1, [',']  1, ['(']  1, [')']  1
};

int	dbglay = 0;
int	dbgtab = 0;

static int	linespace = 0;
static int	lineascent = 0;
static int	charspace = 0;
static int	spspace = 0;
static int	ctllinespace = 0;
static int	ctllineascent = 0;
static int	ctlcharspace = 0;
static int	ctlspspace = 0;
static int	frameid = 0;

static int	addsubords(Sources* sources, Docinfo* di, Rune* auth);
static Sources* newsources(void);
static void	addsource(Sources* srcs, Source* s);
static Source*	newshtml(ByteSource* bs, ItemSource* is);
static Source*	newsimage(ByteSource* bs, CImage* ci, ImageSource* is);
static Source*	copysource(Source* s);
static Source*	findbs(Sources* srcs, ByteSource* bs);
static void	startimreq(Simage* s, Rune* auth);
static void	createvscroll(Frame* f);
static void	createhscroll(Frame* f);
static void	fixframegeom(Frame* f);
static void	haveimage(Frame* f, CImage* ci, Itemlist* itl);
static Lay* sublayout(Frame* f, int targetwidth, uchar just, Background bg, Item* content);
static void	relayout(Frame* f, Lay* lay, int targetwidth, int just);
static void	appenditems(Frame* f, Lay* lay, Item* items);
static void	fixgeom(Frame* f, Lay* lay, Line* l);
static void	fixlinegeom(Frame* f, Lay* lay, Line* l);
static int	pastbrk(Lay* lay, int y, int state);
static void	appendline(Line* lprev, Line* l);
static void	changelines(Line* l, Line* lend);
static Font*	getfont(int num);
static void	measure(Frame* fr, Item* items);
static void	setimagedims(Iimage* i);
static void	updatelgeom(int* pH, int* pA, Item* it);
static int	trybreak(Item* bit, int availw, int* piw, int noneok);
static int	breakstring(Rune* s, int sw, Font* fnt, int availw, int noneok, Rune** ps1, int* pw1, Rune** ps2, int* pw2);
static int	wrapstring(Font* fnt, Rune* s, int availw, Rune*** plines, int** plinestarts);
static int	breakpoint(Rune* s, int slen, int i, int incr);
static int	tryw(Font* fnt, Rune* s, int i, Rune** pss);
static int	floatw(int ymin, int ymax, Ifloat* flist, uchar side);
static void	fixfloatxy(Lay* lay, int y, Ifloat* f);
static void	fixfloatsafter(Lay* lay, Line* l, List* flist);
static int	floatclry(Ifloat* flist, uchar side, int y);
static void	sizetable(Frame* f, Table* tab, int availwidth);
static void	tableparams(Table* tab, int* hsp, int* vsp, int* pad, int* bd, int* cbd, int* hsep, int* vsep);
static int	cellwidth(Table* tab, Tablecell* c, int hsep);
static int	cellheight(Table* tab, Tablecell* c, int vsep);
static int	widthcalc(Table* tab, int* w, int hsep, int domax);
static void	layframeset(Frame* f, Kidinfo* ki);
static int	frdimens(Dimen* dims, int n, int t, int** parr);
static Item*	lastitem(Item* it);
static void	checktabsize(Frame* f, Itable* t, int availw);
static int	widthfromspec(Dimen wspec, int availw);
static void	checkffsize(Frame* f, Item* i, Formfield* ff);
static void	drawall(Frame* f);
static void	drawlay(Frame* f, Lay* lay, Point origin);
static void	drawline(Frame* f, Point layorigin, Line* l, Lay* lay);
static void	drawimg(Frame* f, Point iorigin, Iimage* i);
static void	drawtable(Frame* f, Lay* parentlay, Point torigin, Table* tab);
static void	markchanges(Loc* loc);
static Image*	colorimage(int rgb);
static Colornode*	newcolornode(int rgb, Image* im, Colornode* next);
static void	fillbg(Frame* f, Rectangle r);
static void	drawtriangle(Image* im, Rectangle r, int kind, int style);
static void	flushc(Frame* f);
static Loc*	framefind(Loc* loc, Frame* f, Point p, Item* it);
static Loc*	layfind(Loc* loc, Frame* f, Lay* lay, Point origin, Point p, Item* it);
static Loc*	linefind(Loc* loc, Frame* f, Line* l, Point o, Point p, Item* it);
static Loc*	tablefind(Loc* loc, Frame* f, Itable* ti, Point torigin, Point p, Item* it);
static void	entrydelrange(Centry* e, int istart, int iend);
static void	entrysetfromsnarf(Centry* e);
static int	entrywrapcalc(Centry* e, int** plinestarts, int* ptopline, int* pcursline);
static void	entryscroll(Centry* e);
static void	animproc(void *arg);

void
layoutinit()
{
	Font*	fnt;

	// make sure default and control fonts are loaded
	getfont(DefFnt);
	fnt = fonts[DefFnt].f;
	linespace = fnt->height;
	lineascent = fnt->ascent;
	charspace = runestringwidth(fnt, L"a");
	spspace = fonts[DefFnt].spw;
	getfont(CtlFnt);
	fnt = fonts[CtlFnt].f;
	ctllinespace = fnt->height;
	ctllineascent = fnt->ascent;
	ctlcharspace = runestringwidth(fnt, L"a");
	ctlspspace = fonts[CtlFnt].spw;
}

// Use bsmain to fill frame f, and free bsmain.
// Return buffer containing source in *pbuf and return its length.
//
// An important invariant is that we keep
// track of every ByteSource returned by startreq, and
// ensure that we freebs each of them.
int
layout(Frame* f, ByteSource* bsmain, uchar** pbuf)
{
	Sources*	sources;
	Header*	hdr;
	Rune*	auth;
	uchar*	ans;
	int		anslen;
	Docinfo*	di;
	Lay*	l;
	int	anyanim;
	ItemSource*	itsrc;
	ImageSource*	imsrc;
	CImage*	ci;
	CImage*	newci;
	Item*	it;
	Iimage*	ii;
	Itemlist*	itl;
	Source*	news;
	Simage*	sim;
	Shtml*	sh;
	Source*	s;
	int	use;
	int	error;
	Rune*	challenge;
	ParsedUrl*	newurl;
	int	n;
	int	ret;
	MaskedImage*	mim;
	ByteSource*	bs;
	Source*	src;
	int	freeit;
	void*	animargs[1];

	dbglay = config.dbg['l'];
	dbgtab = config.dbg['t'];
	if(pbuf)
		*pbuf = nil;
	if(dbgev)
		logtime("LAYOUT", 0);
	sources = newsources();
	hdr = bsmain->hdr;
	auth = bsmain->req->auth;
	ans = nil;
	anslen = 0;
	di = newdocinfo();
	resetframe(f);
	f->doc = di;
	di->frameid = f->id;
	di->src = hdr->actual;
	di->base = hdr->base;
	di->refresh = hdr->refresh;
	di->chset = hdr->chset;
	if(f->framebd != 0) {
		f->cr = insetrect(f->r, 2);
		drawborder(f->cim, f->cr, 2, DarkGrey);
	}
	fillbg(f, f->cr);
	flushc(f);
	if(f->flags&FRvscroll)
		createvscroll(f);
	if(f->flags&FRhscroll)
		createhscroll(f);
	l = newlay(Dx(f->cr), ALleft, f->marginw, di->background);
	f->layout = l;
	anyanim = 0;
	if(hdr->mtype == TextHtml || hdr->mtype == TextPlain) {
		itsrc = newitemsource(bsmain, di, hdr->mtype);
		addsource(sources, newshtml(bsmain, itsrc));
	}
	else {
		if(!supported(hdr->mtype)) {
			trace("Need to implement something: source isn't supported image type\n");
			return 0;
		}
		imsrc = newimagesource(bsmain, 0, 0);
		ci = newcimage(bsmain->req->url, 0, 0);
		s = newsimage(bsmain, ci, imsrc);
		addsource(sources, s);
		it = newiimage(nil, nil, ALbottom, 0, 0, 0, 0, 0, 0, nil);
		ii = (Iimage*)it;
		ii->ci = ci;
		di->images = ii;
		appenditems(f, l, it);
		((Simage*)s)->itl = newitemlist(it, nil);
	}
	while(sources->done < sources->tot) {
		if(dbgev)
			logtime("LAYOUT GETSOMETHING", 0);
		bs = waitreq();
		if(bs == nil)
			break;		/* abort condition propagated by netget */
		src = findbs(sources, bs);
		// if(src == nil)
		//	continue;
		assert(src != nil);
		freeit = 0;
		if(bs->err) {
			if(dbglay)
				trace("error getting something: %S\n", errphrase(bs->err));
			freeit = 1;
		}
		else {
			if(bs->hdr != nil && !bs->seenhdr) {
				use = hdraction(bs, 0, src->redirects, &error, &challenge, &newurl);
				if(challenge != nil) {
					trace("Need to implement authorization credential dialog\n");
					error = ERRauthfailed;
					use = 0;
				}
				if(error && dbglay)
					trace("subordinate error: %S\n", errphrase(error));
				if(newurl != nil) {
					// redirect
					freeit = 1;
					switch(src->tag) {
					case Shtmltag:
						trace("unexpected redirect of subord\n");
						break;
					case Simagetag:
						sim = (Simage*)src;
						newci = newcimage(newurl, sim->ci->width, sim->ci->height);
						for(itl = sim->itl; itl != nil; itl = itl->next) {
							it = itl->val;
							assert(it->tag == Iimagetag);
							((Iimage*)it)->ci = newci;
						}
						news = newsimage(nil, newci, nil);
						((Simage*)news)->itl = sim->itl;
						addsource(sources, news);
						startimreq((Simage*)news, auth);
						break;
					}
				}
				if(!use)
					freeit = 1;
			}
			if(!freeit && bs->edata > bs->lim) {
				switch(src->tag) {
				case Shtmltag:
					sh = (Shtml*)src;
					it = getitems(sh->itsrc);
					if(bs == bsmain) {
						if(di->kidinfo != nil) {
							if(sh->itsrc->kidstk == nil) {
								layframeset(f, di->kidinfo);
								flushimage(display, 1);
								freeit = 1;
							}
						}
						else {
							l->background = di->background;
							anyanim |= addsubords(sources, di, auth);
							if(it != nil) {
								appenditems(f, l, it);
								fixframegeom(f);
								if(dbgev)
									logtime("LAYOUT_DRAWALL", 0);
								drawall(f);
							}
						}
					}
					else {
						trace("Need to implement embedded html objects\n");
						freeit = 1;
					}
					break;
				case Simagetag:
					sim = (Simage*)src;
					ret = getmim(sim->imsrc, &mim);
					if(ret == Mimerror) {
						freeit = 1;
					}
					else if(ret != Mimnone) {
						if(sim->ci->mims == nil) {
							sim->ci->mims = (MaskedImage**)emalloc(sizeof(MaskedImage*));
							sim->ci->mimslen = 1;
							sim->ci->mims[0] = mim;
							sim->ci->width = sim->imsrc->width;
							sim->ci->height = sim->imsrc->height;
							if(ret == Mimdone && config.imagelvl <= ImgNoAnim)
								freeit = 1;
						}
						else {
							n = sim->ci->mimslen;
							if(mim != sim->ci->mims[n - 1]) {
								sim->ci->mims = (MaskedImage**)erealloc(sim->ci->mims, (n+1)*sizeof(MaskedImage*));
								sim->ci->mims[sim->ci->mimslen++] = mim;
								anyanim = 1;
							}
						}
						if(sim->ci->mims[0] == mim)
							haveimage(f, sim->ci, sim->itl);
						if(bs->lim == bs->hdr->length)
							imcacheadd(sim->ci);
					}
					break;
				}
			}
			if(!freeit && bs->hdr != nil && bs->lim == bs->hdr->length)
				freeit = 1;
		}
		if(freeit) {
			if(bs == bsmain) {
				ans = bs->data;
				anslen = bs->edata;
			}
			freebs(bs);
			src->bs = nil;
			sources->done++;
		}
	}
	if(anyanim && config.imagelvl > ImgNoAnim) {
		animargs[0] = f;
		proccreate(animproc, animargs, STACKSIZE);
	}
	if(dbgev)
		logtime("LAYOUT_END", 0);
	if(pbuf)
		*pbuf = ans;
	return anslen;
}

// return value is 1 if found any existing images needed animation
static int
addsubords(Sources* sources, Docinfo* di, Rune* auth)
{
	int	anyanim;
	List*	newsims;
	int	iciw;
	int	icih;
	Source*	s;
	Simage*	sim;
	CImage*	cachedci;
	Iimage*	i;

	anyanim = 0;
	if(config.imagelvl == ImgNone)
		return anyanim;
	newsims = nil;
	for(i = di->images; i != nil; i = i->nextimage) {
		if(i->ci->mims == nil) {
			cachedci = imcachelook(i->ci);
			if(cachedci != nil) {
				i->ci = cachedci;
				if(i->imwidth == 0)
					i->imwidth = i->ci->width;
				if(i->imheight == 0)
					i->imheight = i->ci->height;
				anyanim |= (cachedci->mimslen > 1);
				if(i == di->backgrounditem)
					di->background.image = i->ci;
			}
			else {
				for(s = sources->srcs; s != nil; s = s->next) {
					if(s->tag == Simagetag) {
						sim = (Simage*)s;
						if(cimagematch(sim->ci, i->ci)) {
							sim->itl = newitemlist((Item*)i, sim->itl);
							// want all items on list to share same ci;
							// want most-specific dimension specs
							iciw = i->ci->width;
							icih = i->ci->height;
							i->ci = sim->ci;
							if(sim->ci->width == 0 && sim->ci->height == 0) {
								sim->ci->width = iciw;
								sim->ci->height = icih;
							}
							break;
						}
					}
				}
				if(s == nil) {
					// didn't find existing Source for this image
					s = newsimage(nil, i->ci, nil);
					((Simage*)s)->itl = newitemlist((Item*)i, nil);
					newsims = newlist((int)s, newsims);
					addsource(sources, s);
				}
			}
		}
	}
	for( ; newsims != nil; newsims = newsims->next) {
		startimreq((Simage*)(newsims->val), auth);
	}
	return anyanim;
}

static void
startimreq(Simage* s, Rune* auth)
{
	ByteSource*	bs;

	if(dbgev)
		logtime("LAYOUT STARTREQ", 0);
	bs = startreq(newreqinfo(s->ci->src, HGet, nil, 0, auth, FTself));
	s->bs = bs;
	s->imsrc = newimagesource(bs, s->ci->width, s->ci->height);
}

static void
createvscroll(Frame* f)
{
	int	breadth;
	int	length;

	breadth = SCRBREADTH;
	if(f->parent != nil)
		breadth = SCRFBREADTH;
	length = Dy(f->cr);
	if(f->flags&FRhscroll)
		length -= breadth;
	f->vscr = newscroll(f, 1, length, breadth);
	f->vscr->r = rectaddpt(f->vscr->r, f->cr.min);
	f->cr.min.x += breadth;
	if(Dx(f->cr) <= 2*f->marginw)
		trace("botch: frame too small for layout");
	else
		drawctl(f->vscr, 0);
}

static void
createhscroll(Frame* f)
{
	int	breadth;
	int	length;
	int	x;

	breadth = SCRBREADTH;
	if(f->parent != nil)
		breadth = SCRFBREADTH;
	length = Dx(f->cr);
	x = f->cr.min.x;
	f->hscr = newscroll(f, 0, length, breadth);
	f->hscr->r = rectaddpt(f->hscr->r, Pt(x, f->cr.max.y - breadth));
	f->cr.max.y -= breadth;
	if(Dy(f->cr) <= 2*f->marginh)
		trace("botch: frame too small for layout");
	else
		drawctl(f->hscr, 0);
}

// Call after a change to f.layout or f.viewr.min to fix totalr and viewr
// (We are to leave viewr.min unchanged, if possible, as
// user might be scrolling).
static void
fixframegeom(Frame* f)
{
	Lay*	l;
	int	crwidth;
	int	crheight;
	int	crchanged;
	int	n;

	l = f->layout;
	if(dbglay)
		trace("fixframegeom, layout width=%d, height=%d\n", l->width, l->height);
	f->totalr.max = Pt(l->width, l->height);
	crwidth = Dx(f->cr);
	crheight = Dy(f->cr);
	crchanged = 0;
	n = l->height + l->margin - crheight;
	if(n > 0 && f->vscr == nil && (f->flags&FRvscrollauto)) {
		createvscroll(f);
		crchanged = 1;
		crwidth = Dx(f->cr);
	}
	if(f->viewr.min.y > n)
		f->viewr.min.y = max(0, n);
	n = l->width + l->margin - crwidth;
	if(n > 0 && f->hscr == nil && (f->flags&FRhscrollauto)) {
		createhscroll(f);
		crchanged = 1;
		crheight = Dy(f->cr);
	}
	if(crchanged) {
		relayout(f, l, crwidth, l->just);
		fixframegeom(f);
		return ;
	}
	if(f->viewr.min.x > n)
		f->viewr.min.x = max(0, n);
	f->viewr.max.x = min(f->viewr.min.x + crwidth, l->width);
	f->viewr.max.y = min(f->viewr.min.y + crheight, l->height);
	if(f->vscr != nil)
		scrollset(f->vscr, f->viewr.min.y, f->viewr.max.y, f->totalr.max.y, 5);
	if(f->hscr != nil)
		scrollset(f->hscr, f->viewr.min.x, f->viewr.max.x, f->totalr.max.x, 5);
}

// The items its within f are Iimage items,
// and its image, ci, now has at least a ci->mims[0], which may be partially
// or fully filled.
static void
haveimage(Frame* f, CImage* ci, Itemlist* itl)
{
	int	dorelayout;
	Image*	im;
	Ifloat*	fit;
	Item*	locit;
	int	k;
	Loc*	loc;
	Iimage*	i;
	Item*	it;

	if(dbgev)
		logtime("HAVEIMAGE", 0);
	if(dbglay)
		trace("\nHAVEIMAGE src=%S w=%d h=%d\n", ci->src->url, ci->width, ci->height);
	for(dorelayout = 0; itl != nil; itl = itl->next) {
		it = itl->val;
		switch(it->tag) {
		case Iimagetag:
			i = (Iimage*)it;
			if(it == (Item*)f->doc->backgrounditem && f->doc->background.image == nil) {
				im = ci->mims[0]->im;
				im->repl = 1;
				im->clipr = Rect(-0x3FFFFFFF, 0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF);
				f->doc->background.image = i->ci;
			}
			else {
				// If i->imwidth and i->imheight are not both 0, the HTML specified the dimens.
				// If one of them is 0, the other is to be scaled by the same factor;
				// we have to relay the line in that case too.
				if(i->imwidth == 0 || i->imheight == 0) {
					i->imwidth = ci->width;
					i->imheight = ci->height;
					setimagedims(i);
					loc = findloc(f, ZP, it);
					// sometimes the image was added to doc image list, but
					// never made it to layout (e.g., because html bug prevented
					// a table from being added).
					// also, script-created images won't have items
					if(loc != nil) {
						markchanges(loc);
						dorelayout = 1;
						// Floats are assumed to be premeasured, so if there
						// are any floats in the loc list, remeasure them
						for(k = loc->n - 1; k > 0; k--) {
							if(loc->le[k].kind == LEitem) {
								locit = loc->le[k].item;
								switch(locit->tag) {
								case Ifloattag:
									fit = (Ifloat*)locit;
									switch(fit->item->tag) {
									case Iimagetag:
										fit->height = fit->item->height;
										break;
									case Itabletag:
										checktabsize(f, (Itable*)fit->item, TABLEFLOATTARGET);
										break;
									}
									break;
								}
							}
						}
					}
				}
			}
			if(dbg > 1)
				trace("\nhaveimage item: %I", it);
			break;
		}
	}
	if(dorelayout) {
		relayout(f, f->layout, f->layout->targetwidth, f->layout->just);
		fixframegeom(f);
	}
	drawall(f);
	if(dbgev)
		logtime("HAVEIMAGE_END", 0);
}

// For first layout of subelements, such as table cells.
// After this, content items will be dispersed throughout resulting lay.
// Return the new layout.
static Lay*
sublayout(Frame* f, int targetwidth, uchar just, Background bg, Item* content)
{
	Lay*	l;

	if(dbglay)
		trace("sublayout, targetwidth=%d\n", targetwidth);
	l = newlay(targetwidth, just, 0, bg);
	appenditems(f, l, content);
	l->flags &= ~Lchanged;
	if(dbglay)
		trace("after sublayout, width=%d\n", l->width);
	return l;
}

// Relayout of lay, given a new target width or if something changed inside
// or if the global justification for the layout changed.
// Floats are hard: for now, just relay everything with floats temporarily
// moved way down, if there are any floats.
static void
relayout(Frame* f, Lay* lay, int targetwidth, int just)
{
	int	changeall;
	Ifloat*	ff;

	if(dbglay)
		trace("relayout, targetwidth=%d, old target=%d, changed=%d\n", targetwidth, lay->targetwidth, (lay->flags&Lchanged) != (uchar)0);
	changeall = (lay->targetwidth != targetwidth || lay->just != just);
	if(!changeall && !(lay->flags&Lchanged))
		return ;
	if(lay->floats != nil) {
		for(ff = lay->floats; ff != nil; ff = ff->nextfloat)
			ff->y = 0x6fffffff;
		changeall = 1;
	}
	lay->targetwidth = targetwidth;
	lay->just = just;
	lay->width = 0;
	if(changeall)
		changelines(lay->start->next, lay->end);
	fixgeom(f, lay, lay->start->next);
	lay->flags &= ~Lchanged;
	if(dbglay)
		trace("after relayout, width=%d\n", lay->width);
}

// Measure and append the items to the end of layout lay,
// and fix the geometry.
static void
appenditems(Frame* f, Lay* lay, Item* items)
{
	Item*	it;
	Line*	lprev;
	Line*	l;
	Item*	lit;
	Item*	nexti;

	measure(f, items);
	if(dbglay)
		printitems(items, "appenditems, after measturetexts");
	it = items;
	if(it == nil)
		return ;
	lprev = lay->end->prev;
	lit = lastitem(lprev->items);
	if(lit == nil || (it->state&IFbrk)) {
		// start a new line after existing last line
		l = newline();
		appendline(lprev, l);
		l->items = it;
	}
	else {
		// start appending items to existing last line
		l = lprev;
		lit->next = it;
	}
	l->flags |= Lchanged;
	while(it != nil) {
		nexti = it->next;
		if(nexti == nil || (nexti->state&IFbrk)) {
			it->next = nil;
			fixgeom(f, lay, l);
			if(nexti == nil)
				break;
			// now there may be multiple lines containing the
			// items from l, but the one after the last is lay.end
			l = newline();
			appendline(lay->end->prev, l);
			l->flags |= Lchanged;
			it = nexti;
			l->items = it;
		}
		else
			it = nexti;
	}
}

// Fix up the geometry of line l and successors.
// Assume geometry of previous line is correct.
static void
fixgeom(Frame* f, Lay* lay, Line* l)
{
	while(l != nil) {
		fixlinegeom(f, lay, l);
		l = l->next;
	}
	lay->height = lay->end->pos.y;
}

// Fix geom for one line.
// This may change the overall lay->width, if there is no way
// to fit the line into the target width. 
static void
fixlinegeom(Frame* f, Lay* lay, Line* l)
{
	Line*	lprev;
	int	y;
	Item*	it;
	int	state;
	int	lineh;
	int	lfloatw;
	int	rfloatw;
	int	wrapping;
	int	hang;
	int	linehang;
	int	hangtogo;
	int	indent;
	int	just;
	int	right;
	int	lwid;
	int	w;
	int	linea;
	Item*	lastit;
	List*	nextfloats;
	int	anystuff;
	Item*	rest;
	int	x;
	int	xright;
	int	n;
	int	justw;
	Item*	spaceit;
	int	newlfloatw;
	int	newrfloatw;
	int	kindspec;
	int	avail;
	Ifloat*	ifloat;
	Itable*	itable;
	Irule*	irule;
	Itext*	itext;
	int	oldy;
	int	takeit;
	int	noneok;
	int	iw;
	Line*	nextl;
	Item*	nit;
	int	checkw;
	Item*	qi;
	Item*	li;
	Item*	i;

	lprev = l->prev;
	y = lprev->pos.y + lprev->height;
	it = l->items;
	state = it->state;
	if(dbglay > 1) {
		trace("\nfixlinegeom start, y=prev.y+prev.height=%d+%d=%d, changed=%d\n",
			l->prev->pos.y, lprev->height, y, l->flags&Lchanged);
		if(dbglay > 2)
			printitems(it, "items");
		else
			trace("first item: %I", it);
	}
	if(state&IFbrk) {
		y = pastbrk(lay, y, state);
		if(dbglay > 1 && y != lprev->pos.y + lprev->height)
			trace("after pastbrk, line y is now %d\n", y);
	}
	l->pos.y = y;
	lineh = max(l->height, linespace);
	lfloatw = floatw(y, y + lineh, lay->floats, ALleft);
	rfloatw = floatw(y, y + lineh, lay->floats, ALright);
	if(!(l->flags&Lchanged)) {
		// possibly adjust lay.width
		n = (lay->width - rfloatw) - (l->pos.x - lay->margin + l->width);
		if(n < 0)
			lay->width += -n;
		return ;
	}
	wrapping = state&IFwrap;
	hang = (state&IFhangmask)*TABPIX/10;
	linehang = hang;
	hangtogo = hang;
	indent = ((state&IFindentmask) >> IFindentshift)*TABPIX;
	just = (state&(IFcjust|IFrjust));
	if(just == 0 && lay->just != ALleft) {
		if(lay->just == ALcenter)
			just = IFcjust;
		else if(lay->just == ALright)
			just = IFrjust;
	}
	right = max(lay->targetwidth, lay->width);
	lwid = right - (lfloatw + rfloatw + indent);
	if(lwid < 0) {
		lay->width = lfloatw + rfloatw + indent;
		right = lay->width;
		lwid = 0;
	}
	lwid += hang;
	if(dbglay > 1) {
		trace("fixlinegeom, now y=%d, lfloatw=%d, rfloatw=%d, indent=%d, hang=%d, lwid=%d\n",
				y, lfloatw, rfloatw, indent, hang, lwid);
	}
	l->pos.y = y;
	w = 0;
	lineh = 0;
	linea = 0;
	lastit = nil;
	nextfloats = nil;
	anystuff = 0;
	while(it != nil) {
		if(dbglay > 2)
			trace("fixlinegeom loop head, w=%d, loop item: %I\n", w, it);
		state = it->state;
		if(anystuff && (state&IFbrk))
			break;
		checkw = 1;
		if(hang && !(state&IFhangmask)) {
			lwid -= hang;
			hang = 0;
			if(hangtogo > 0) {
				// insert a null spacer item
				spaceit = newispacer(ISPgeneral);
				spaceit->width = hangtogo;
				if(lastit != nil) {
					spaceit->state = lastit->state&~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
					lastit->next = spaceit;
				}
				else
					lastit = spaceit;
				spaceit->next = it;
			}
		}
		switch(it->tag) {
		case Ifloattag:
			ifloat = (Ifloat*)it;
			if(anystuff)
				// float will go after this line
				nextfloats = newlist((int)ifloat, nextfloats);
			else {
				// add float beside current line, adjust widths
				fixfloatxy(lay, y, ifloat);
				// TODO: only do following if y and/or height changed
				changelines(l->next, lay->end);
				newlfloatw = floatw(y, y + 1, lay->floats, ALleft);
				newrfloatw = floatw(y, y + 1, lay->floats, ALright);
				lwid -= (newlfloatw - lfloatw) + (newrfloatw - rfloatw);
				if(lwid < 0) {
					lay->width += -lwid;
					right = lay->width;
					lwid = 0;
				}
				lfloatw = newlfloatw;
				rfloatw = newrfloatw;
				if(dbglay > 1)
					trace("float added to current line, i->y=%d, side=%d, h=%d, lfloatw=%d, rfloatw=%d, lwid=%d, lay.width=%d, right=%d\n",
						ifloat->y, ifloat->side, ifloat->item->height,
						lfloatw, rfloatw, lwid, lay->width, right);
			}
			checkw = 0;
			break;
		case Itabletag:
			// When just doing layout for cell dimensions, don't
			// want a "100%" spec to make the table really wide
			itable = (Itable*)it;
			kindspec = 0;
			if(lay->targetwidth == TABLEMAXTARGET && dimenkind(itable->table->width) == Dpercent) {
				kindspec = itable->table->width.kindspec;
				itable->table->width = makedimen(Dnone, 0);
			}
			checktabsize(f, itable, lwid - w);
			if(kindspec != 0)
				itable->table->width.kindspec = kindspec;
			break;
		case Iruletag:
			irule = (Irule*)it;
			avail = lwid - w;
			// When just doing layout for cell dimensions, don't
			// want a "100%" spec to make the rule really wide
			if(lay->targetwidth == TABLEMAXTARGET)
				avail = min(10, avail);
			irule->width = widthfromspec(irule->wspec, avail);
			break;
		case Iformfieldtag:
			checkffsize(f, it, ((Iformfield*)it)->formfield);
			break;
		}
		if(checkw) {
			iw = it->width;
			if(wrapping && w + iw > lwid) {
				// it doesn't fit; see if it can be broken
				// noneok = (anystuff || lfloatw != 0 || rfloatw != 0) && !(state&IFnobrk);
				noneok = anystuff && !(state&IFnobrk);
				takeit = trybreak(it, lwid - w, &iw, noneok);
				if(!takeit) {
					if(lastit == nil) {
						// Nothing added because one of the float widths
						// is nonzero, and not enough room for anything else.
						// Move y down until there's more room and try again.
						assert(lfloatw != 0 || rfloatw != 0);
						oldy = y;
						y = pastbrk(lay, y, IFcleft|IFcright);
						if(dbglay > 1)
							trace("moved y past %d, now y=%d\n", oldy, y);
						assert(y > oldy);
						// Do the move down by artificially increasing the
						// height of the previous line
						lprev->height += y - oldy;
						fixlinegeom(f, lay, l);
						return ;
					}
					break;
				}
			}
			w += iw;
			if(hang)
				hangtogo -= w;
			updatelgeom(&lineh, &linea, it);
			if(!anystuff) {
				anystuff = 1;
				// don't count an ordinary space as 'stuff' if wrapping
				if(it->tag == Itexttag && wrapping && ((Itext*)it)->s[0] == ' ')
					anystuff = 0;
			}
		}
		lastit = it;
		it = it->next;
		if(it == nil) {
			// perhaps next lines items can now fit on this line
			nextl = l->next;
			nit = nextl->items;
			if(nextl != lay->end && !(nit->state&IFbrk)) {
				lastit->next = nit;
				// remove nextl
				l->next = nextl->next;
				l->next->prev = l;
				it = nit;
			}
		}
	}
	// line is complete, next line will start with it (or it is nil)
	rest = it;
	assert(lastit != nil);
	lastit->next = nil;
	l->width = w;
	x = lfloatw + indent - linehang;
	i = l->items;
	switch(i->tag) {
	case Itexttag:
		itext = (Itext*)i;
		if(itext->s[0] == ' ')
			x -= fonts[itext->fnt].spw;
		break;
	case Iruletag:
		irule = (Irule*)i;
		if(irule->align == ALcenter)
			just = IFcjust;
		else if(irule->align == ALright)
			just = IFrjust;
		break;
	case Ifloattag:
		ifloat = (Ifloat*)i;
		if(ifloat->next != nil) {
			qi = ifloat->next;
			if(qi->tag == Itexttag) {
				itext = (Itext*)qi;
				if(itext->s[0] == ' ')
					x -= fonts[itext->fnt].spw;
			}
		}
		break;
	}
	xright = x + w;
	n = (lay->width - rfloatw) - xright;
	if(n < 0) {
		lay->width += -n;
	}

	// except when doing the maximum needed cell width, justification
	// is supposed to be within max(lay.width, targetwidth),
	// but also, if this is lay for frame itself, justify within f.targetwidth to match other browsers.
	if(lay == f->layout)
		justw = lay->targetwidth;
	else if(lay->targetwidth == TABLEMAXTARGET)
		justw = lay->width;
	else
		justw = max(lay->width, lay->targetwidth);
	n = (justw - rfloatw) - xright;
	if(n > 0 && just) {
		if(just&IFcjust)
			x += n/2;
		else
			x += n;
		if(x + w + rfloatw > lay->width)
			lay->width = x + w + rfloatw;
	}
	if(dbglay > 1) {
		trace("line geometry fixed, (x,y)=(%d,%d), w=%d, h=%d, a=%d, lfloatw=%d, rfloatw=%d, lay.width=%d\n",
			x, l->pos.y, w, lineh, linea, lfloatw, rfloatw, lay->width);
		if(dbglay > 2)
			printitems(l->items, "final line items");
	}
	l->pos.x = x + lay->margin;
	l->height = lineh;
	l->ascent = linea;
	l->flags &= ~Lchanged;
	if(nextfloats != nil)
		fixfloatsafter(lay, l, nextfloats);
	if(rest != nil) {
		nextl = l->next;
		if(nextl == lay->end || (nextl->items->state&IFbrk)) {
			nextl = newline();
			appendline(l, nextl);
		}
		li = lastitem(rest);
		li->next = nextl->items;
		nextl->items = rest;
		nextl->flags |= Lchanged;
	}
}

// Return y coord after y due to a break.
static int
pastbrk(Lay* lay, int y, int state)
{
	int	nextralines;
	int	ynext;

	nextralines = 0;
	if(state&IFbrksp)
		nextralines = 1;
	ynext = y;
	if(state&IFcleft)
		ynext = floatclry(lay->floats, ALleft, ynext);
	if(state&IFcright)
		ynext = max(ynext, floatclry(lay->floats, ALright, ynext));
	ynext += nextralines*linespace;
	return ynext;
}

// Add line l after lprev (and before lprev's current successor)
static void
appendline(Line* lprev, Line* l)
{
	l->next = lprev->next;
	l->prev = lprev;
	l->next->prev = l;
	lprev->next = l;
}

// Mark lines l up to but not including lend as changed
static void
changelines(Line* l, Line* lend)
{
	for(; l != lend; l = l->next)
		l->flags |= Lchanged;
}

// Return a Font* for font number num = (style*NumSize + size)
static Font*
getfont(int num)
{
	Font*	f;

	f = fonts[num].f;
	if(f == nil) {
		f = openfont(display, fonts[num].name);
		if(f == nil) {
			if(num == DefFnt) {
				trace("can't open default font\n");
assert(0);
				exits("font problem");
			}
			else {
				if(warn)
					trace("warning: substituting default for font %s\n", fonts[num].name);
				f = fonts[DefFnt].f;
			}
		}
		fonts[num].f = f;
		fonts[num].spw = runestringwidth(f, L" ");
	}
	return f;
}

// Set the width, height and ascent fields of all items, getting any necessary fonts.
// Some widths and heights depend on the available width on the line, and may be
// wrong until checked during fixlinegeom.
// Don't do tables here at all (except floating tables).
// Configure Controls for form fields.
static void
measure(Frame* fr, Item* items)
{
	Item*	it;
	Font*	f;
	int	a;
	int	h;
	Control*	c;
	Item*	i;
	Itext*	t;
	Irule*	r;
	Iformfield*	ff;
	Ifloat*	fl;

	for(it = items; it != nil; it = it->next) {
		switch(it->tag) {
		case Itexttag:
			t = (Itext*)it;
			f = getfont(t->fnt);
			it->width = runestringwidth(f, t->s);
			a = f->ascent;
			h = f->height;
			if(t->voff != Voffbias) {
				a -= (t->voff) - Voffbias;
				if(a > h)
					h = a;
			}
			it->height = h;
			it->ascent = a;
			break;
		case Iruletag:
			r = (Irule*)it;
			it->height = r->size + 2*RULESP;
			it->ascent = r->size + RULESP;
			break;
		case Iimagetag:
			setimagedims((Iimage*)it);
			break;
		case Iformfieldtag:
			ff = (Iformfield*)it;
			c = newff(fr, ff->formfield);
			if(c != nil) {
				ff->formfield->ctlid = addcontrol(fr, c);
				it->width = Dx(c->r);
				it->height = Dy(c->r);
				it->ascent = it->height;
				switch(c->tag) {
				case Centrytag:
					it->ascent = lineascent + ENTVMARGIN;
					break;
				case Cselecttag:
					it->ascent = lineascent + SELMARGIN;
					break;
				case Cbuttontag:
					if(((Cbutton*)c)->dorelief)
						it->ascent -= BUTMARGIN;
					break;
				}
			}
			break;
		case Ifloattag:
			// Leave w at zero, so it doesn't contribute to line width in normal way
			// (Can find its width in t.item.width).
			fl = (Ifloat*)it;
			i = fl->item;
			switch(i->tag) {
			case Iimagetag:
				setimagedims((Iimage*)i);
				it->height = i->height;
				break;
			case Itabletag:
				checktabsize(fr,(Itable*)i, TABLEFLOATTARGET);
				break;
			default:
				assert(0);
				break;
			}
			it->ascent = it->height;
			break;
		case Ispacertag:
			switch(((Ispacer*)it)->spkind) {
			case ISPvline:
				it->height = linespace;
				it->ascent = lineascent;
				break;
			case ISPhspace:
				it->width = spspace;
				break;
			}
			break;
		}
	}
}

// Set the dimensions of an image item
static void
setimagedims(Iimage* i)
{
	Font*	f;

	i->width = i->imwidth + 2*(i->hspace + i->border);
	i->height = i->imheight + 2*(i->vspace + i->border);
	i->ascent = i->height - (i->vspace + i->border);
	if(config.imagelvl == ImgNone && i->altrep != nil) {
		f = fonts[DefFnt].f;
		i->width = max(i->width, runestringwidth(f, i->altrep));
		i->height = max(i->height, f->height);
		i->ascent = f->ascent;
	}
}

// Line geometry function:
// Given current line height (*pH) and ascent (distance from top to baseline) (*pA),
// and an item, see if that item changes height and ascent.
// Return (H', A') in *pH and *pA, the updated line height and ascent.
static void
updatelgeom(int* pH, int* pA, Item* it)
{
	int	h;
	int	a;
	uchar	atype;
	int	d;
	int	Hnew;
	int	Anew;
	int	hhalf;
	int	H;
	int	A;

	H = *pH;
	A = *pA;
	h = it->height;
	a = it->ascent;
	atype = ALbaseline;
	switch(it->tag) {
	case Iimagetag:
		atype = ((Iimage*)it)->align;
		break;
	case Itabletag:
		atype = ALtop;
		break;
	case Ifloattag:
		return;
	}
	d = h - a;
	Hnew = H;
	Anew = A;
	switch(atype) {
	case ALbaseline:
	case ALbottom:
		if(a > A) {
			Anew = a;
			Hnew += (Anew - A);
		}
		if(d > Hnew - Anew)
			Hnew = Anew + d;
		break;
	case ALtop:
		if(h > H)
			Hnew = h;
		break;
	case ALmiddle:
	case ALcenter:
		hhalf = h/2;
		if(hhalf > A)
			Anew = hhalf;
		if(hhalf > H - Anew)
			Hnew = Anew + hhalf;
		break;
	}
	*pH = Hnew;
	*pA = Anew;
}

// Try breaking item bit to make it fit in availw.
// If that is possible, change bit to be the part that fits
// and insert the rest between bit and bit.next.
// *piw is the current width of bit.
// If noneok is 0, break off the minimum size word
// even if it exceeds availw.
// Return 1 if supposed to take bit, and iw' = new width of bit in *piw
static int
trybreak(Item* bit, int availw, int* piw, int noneok)
{
	Item*	itn;
	Itext*	t;
	int		iw;
	Rune*	s1;
	Rune*	s2;
	int		w1;
	int		w2;

	iw = *piw;
	if(iw <= 0)
		return 1;
	if(availw < 0) {
		if(noneok)
			return 0;
		else
			availw = 0;
	}
	if(bit->tag == Itexttag) {
		t = (Itext*)bit;
		assert(t->s != nil);
		if(breakstring(t->s, iw, fonts[t->fnt].f, availw, noneok, &s1, &w1, &s2, &w2) < 0)
			return !noneok;
		if(w1 == 0)
			return 0;
		// here we split bit into two pieces at i
		assert(s1 != nil && s2 != nil);
		itn = newitext(s2, t->fnt, t->fg, t->voff, t->ul);
		itn->width = w2;
		itn->height = t->height;
		itn->ascent = t->ascent;
		itn->anchorid = t->anchorid;
		itn->state = t->state&~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
		itn->next = t->next;
		t->next = itn;
		t->s = s1;
		t->width = w1;
		*piw = w1;
		return 1;
	}
	return !noneok;
}

// s has width sw when drawn in fnt.
// Break s into s1 and s2 so that s1 fits in availw.
// If noneok is true, it is ok for s1 to be nil, otherwise might
// have to return an s1 that overflows availw somewhat.
// Return vals in *ps1, *pw1, *ps2, *pw2 where w1 and w2
// are widths of s1 and s2.
// Also, return -1 if no break is possible within s,
// so the caller needs to take all or nothing.
// Assume caller has already checked that sw > availw.
static int
breakstring(Rune* s, int sw, Font* fnt, int availw, int noneok,
	Rune** ps1, int* pw1, Rune** ps2, int* pw2)
{
	int slen;
	int i;
	int oldi;
	int ww;
	int oldww;
	Rune* ss;
	Rune* oldss;

	slen = Strlen(s);
	if(slen < 2)
		return -1;
	// Use linear interpolation to guess break point.
	// We know avail < iw by conditions of trybreak call.
	i = slen*availw/sw - 1;
	if(i < 0)
		i = 0;
	i = breakpoint(s, slen, i, -1);
	ww = tryw(fnt, s, i, &ss);
	if(ww > availw) {
		while(ww > availw) {
			i = breakpoint(s, slen, i - 1, -1);
			if(i <= 0)
				break;
			ww = tryw(fnt, s, i, &ss);
		}
	}
	else {
		oldi = i;
		oldss = ss;
		oldww = ww;
		while(ww < availw) {
			oldi = i;
			oldss = ss;
			oldww = ww;
			i = breakpoint(s, slen, i + 1, 1);
			if(i >= slen)
				break;
			ww = tryw(fnt, s, i, &ss);
		}
		i = oldi;
		ss = oldss;
		ww = oldww;
	}
	if(i <= 0 || i >= slen) {
		if(noneok) {
			*ps1 = nil;
			*pw1 = 0;
			*ps2 = s;
			*pw2 = sw;
			return 0;
		}
		i = breakpoint(s, slen, 1, 1);
		if(i >= slen)
			return -1;
		ww = tryw(fnt, s, i, &ss);
	}
	*ps1 = ss;
	*pw1 = ww;
	*ps2 = Strndup(s+i, slen-i);
	*pw2 = sw-ww;
	return 1;
}

// If can break between s[i-1] and s[i], return i.
// Else move i in direction incr until this is true.
// (Might end up returning 0 or len s).
static int
breakpoint(Rune* s, int slen, int i, int incr)
{
	int	ans;
	int	ci;
	int	di;

	ans = 0;
	while(i > 0 && i < slen) {
		ci = s[i];
		di = s[i - 1];
		if((ci >= 0xA0 || wordchar[ci]) && (di >= 0xA0 || wordchar[di]))
			i += incr;
		else {
			ans = i;
			break;
		}
	}
	if(i == slen)
		ans = slen;
	return ans;
}

// Return (s[0:i], width of that slice in font fnt)
static int
tryw(Font* fnt, Rune* s, int i, Rune** pss)
{
	if(i == 0) {
		*pss = nil;
		return 0;
	}
	*pss = Strndup(s, i);
	return runestringwidth(fnt, *pss);
}

// Return max width of a float that overlaps [ymin, ymax) on given side.
// Floats are in reverse order of addition, so each float's y is <= that of
// preceding floats in list.  Floats from both sides are intermixed.
static int
floatw(int ymin, int ymax, Ifloat* flist, uchar side)
{
	int	ans;
	int	w;
	Ifloat*	fl;
	int	fymin;
	int	fymax;

	ans = 0;
	for(fl = flist; fl != nil; fl = fl->nextfloat) {
		if(fl->side != side)
			continue;
		fymin = fl->y;
		fymax = fymin + fl->item->height;
		if((fymin <= ymin && ymin < fymax) || (ymin <= fymin && fymin < ymax)) {
			w = fl->x;
			if(side == ALleft)
				w += fl->item->width;
			if(ans < w)
				ans = w;
		}
	}
	return ans;
}

// Float f is to be at vertical position >= y.
// Fix its (x,y) pos and add it to lay.floats, if not already there.
static void
fixfloatxy(Lay* lay, int y, Ifloat* f)
{
	Ifloat*	flist;
	Ifloat*	x;

	f->y = y;
	flist = lay->floats;
	if(!f->infloats) {
		// only take previous floats into account for width
		while(flist != nil) {
			x = flist;
			flist = flist->nextfloat;
			if(x == f)
				break;
		}
	}
	f->x = floatw(y, y + f->item->height, flist, f->side);
	// for right-side floats, x is distance from right edge,
	// which includes f's width
	if(f->side == ALright)
		f->x += f->item->width;
	if(dbglay > 1)
		trace("fixfloatxy, now float is: %I\n", (Item*)f);
	if(!f->infloats) {
		f->nextfloat = lay->floats;
		lay->floats = f;
		f->infloats = 1;
	}
}

// Floats in flist are to go after line l.
static void
fixfloatsafter(Lay* lay, Line* l, List* flist)
{
	int	y;
	List*	itl;
	Ifloat*	ifloat;

	y = l->pos.y + l->height;
	for(itl = revlist(flist); flist != nil; flist = flist->next) {
		ifloat = (Ifloat*)(itl->val);
		assert(ifloat->tag == Ifloattag);
		fixfloatxy(lay, y, ifloat);
	}
	// TODO: only change if y and/or height changed
	changelines(l->next, lay->end);
}

// If there's a float on given side that starts on or before y and
// ends after y, return ending y of that float, else return original y.
// Assume float list is bottom up.
static int
floatclry(Ifloat* fl, uchar side, int y)
{
	int	flymax;

	for(; fl != nil; fl = fl->nextfloat) {
		if(fl->side == side) {
			if(fl->y <= y) {
				flymax = fl->y + fl->item->height;
				if(flymax <= y)
					return y;
				else
					return flymax;
			}
		}
	}
	return y;
}

// Do preliminaries to laying out table tab in target width linewidth,
// setting total height and width.
static void
sizetable(Frame* f, Table* tab, int availwidth)
{
	int	hsp;
	int	vsp;
	int	pad;
	int	bd;
	int	cbd;
	int	hsep;
	int	vsep;
	int	totw;
	int*	colmaxw;
	int	maxw;
	int	ci;
	int	ri;
	int	toth;
	int	tw;
	Tablecell*	c;
	Lay*	clay;
	int	d;
	int	wd;
	int*	colminw;
	int	minw;
	int	w;
	uchar	al;
	Tablerow*	row;
	int	h;
	int	a;
	int	n;
	int	spanht;
	int	i;
	int	ht;
	Lay*	caplay;

	if(dbgtab)
		trace("sizetable %d, availwidth=%d, nrow=%d, ncol=%d, changed=%x, tab.availw=%d\n",
			tab->tableid, availwidth, tab->nrow, tab->ncol, tab->flags&Lchanged, tab->availw);
	if(tab->ncol == 0 || tab->nrow == 0)
		return ;
	if(tab->availw == availwidth && !(tab->flags&Lchanged))
		return ;
	tableparams(tab, &hsp, &vsp, &pad, &bd, &cbd, &hsep, &vsep);
	totw = widthfromspec(tab->width, availwidth);
	// reduce totw by spacing, padding, and rule widths
	// to leave amount left for contents
	totw -= (tab->ncol - 1)*hsep + 2*(hsp + bd + pad + cbd);

	if(totw <= 0)
		totw = 1;
	if(dbgtab)
		trace("\nsizetable %d, totw=%d, hsp=%d, vsp=%d, pad=%d, bd=%d, cbd=%d, hsep=%d, vsep=%d\n",
			tab->tableid, totw, hsp, vsp, pad, bd, cbd, hsep, vsep);
	for(c = tab->cells; c != nil; c = c->next) {
		clay = c->lay;
		if(clay == nil || !(clay->flags&Lchanged)) {
			c->minw = -1;
			tw = TABLEMAXTARGET;
			if(dimenkind(c->wspec) != Dnone)
				tw = widthfromspec(c->wspec, totw);

			// When finding max widths, want to lay out using ALleft alignment,
			// because we don't yet know final width for proper justification.
			// If the max widths are accepted, we'll redo those needing other justification.
			if(clay == nil) {
				if(dbglay)
					trace("Initial layout for cell %d.%d\n", tab->tableid, c->cellid);
				clay = c->lay = sublayout(f, tw, ALleft, c->background, c->content);
				c->content = nil;
			}
			else {
				if(dbglay)
					trace("Relayout (for max) for cell %d.%d\n", tab->tableid, c->cellid);
				relayout(f, clay, tw, ALleft);
			}
			clay->flags |= Lchanged;	// for min test, below
			c->maxw = clay->width;
			if(dbgtab)
				trace("sizetable %d for cell %d max layout done, targw=%d, c.maxw=%d\n",
					tab->tableid, c->cellid, tw, c->maxw);
			if(dimenkind(c->wspec) == Dpixels) {
				// Other browsers don't make the following adjustment for
				// percentage and relative widths
				if(c->maxw <= tw)
					c->maxw = tw;
				if(dbgtab)
					trace("after spec adjustment, c.maxw=%d\n", c->maxw);
			}
		}
	}

	// calc max column widths
	colmaxw = (int*)emallocz(tab->ncol * sizeof(int));
	maxw = widthcalc(tab, colmaxw, hsep, 1);
	if(dbgtab)
		trace("sizetable %d maxw=%d, totw=%d\n", tab->tableid, maxw, totw);
	if(maxw <= totw) {
		// trial layouts are fine,
		// but if table width was specified, add more space
		d = 0;
		if(totw > maxw && dimenkind(tab->width) != Dnone)
			d = (totw - maxw)/tab->ncol;
		for(ci = 0; ci < tab->ncol; ci++) {
			tab->cols[ci].width = colmaxw[ci] + d;
			if(dbgtab)
				trace("sizetable %d tab.cols[%d].width = %d\n", tab->tableid, ci, colmaxw[ci]);
		}
	}
	else {
		// calc min column widths and  apportion out
		// differences
		if(dbgtab)
			trace("sizetable %d, availwidth %d, need min widths too\n", tab->tableid, availwidth);
		for(c = tab->cells; c != nil; c = c->next) {
			clay = c->lay;
			assert(clay != nil);
			if(c->minw == -1 || !(clay->flags&Lchanged)) {
				if(dbglay)
					trace("Relayout (for min) for cell %d.%d\n", tab->tableid, c->cellid);
				relayout(f, clay, 1, ALleft);
				c->minw = clay->width;
				if(dbgtab)
					trace("sizetable %d for cell %d min layout done, c.min=%d\n",
						tab->tableid, c->cellid, clay->width);
			}
		}
		colminw = (int*)emallocz(tab->ncol * sizeof(int));
		minw = widthcalc(tab, colminw, hsep, 0);
		w = totw - minw;
		d = maxw - minw;
		if(dbgtab)
			trace("sizetable %d minw=%d, w=%d, d=%d\n", tab->tableid, minw, w, d);
		for(ci = 0; ci < tab->ncol; ci++) {
			if(w < 0 || d < 0)
				wd = colminw[ci];
			else
				wd = colminw[ci] + (colmaxw[ci] - colminw[ci])*w/d;
			if(dbgtab)
				trace("sizetable %d col[%d].width = %d\n", tab->tableid, ci, wd);
			tab->cols[ci].width = wd;
		}
		if(dbgtab)
			trace("sizetable %d, availwidth %d, doing final layouts\n", tab->tableid, availwidth);
	}

	// now have col widths; set actual cell dimensions
	// and relayout (note: relayout will do no work if the target width
	// and just haven't changed from last layout)
	for(c = tab->cells; c != nil; c = c->next) {
		clay = c->lay;
		wd = cellwidth(tab, c, hsep);
		if(dbgtab)
			trace("sizetable %d for cell %d, clay.width=%d, cellwidth=%d\n",
				tab->tableid, c->cellid, clay->width, wd);
		if(dbglay)
			trace("Relayout (final) for cell %d.%d\n", tab->tableid, c->cellid);
		relayout(f, clay, wd, c->align.halign);
		if(dbgtab)
			trace("sizetable %d for cell %d, final width %d, got width %d, height %d\n",
				tab->tableid, c->cellid, wd, clay->width, clay->height);
	}

	// set row heights and ascents
	// first pass: ignore cells with rowspan > 1
	for(ri = 0; ri < tab->nrow; ri++) {
		row = &tab->rows[ri];
		h = 0;
		a = 0;
		for(c = row->cells; c != nil; c = c->nextinrow) {
			clay = c->lay;
			if(c->rowspan > 1 || clay == nil)
				continue;
			al = c->align.valign;
			if(al == ALnone)
				al = tab->rows[c->row].align.valign;
			if(al == ALbaseline) {
				n = c->ascent;
				if(n > a) {
					h += (n - a);
					a = n;
				}
				n = clay->height - c->ascent;
				if(n > h - a)
					h = a + n;
			}
			else {
				n = clay->height;
				if(n > h)
					h = n;
			}
		}
		row->height = h;
		row->ascent = a;
	}
	// second pass: take care of rowspan > 1
	// (this algorithm isn't quite right -- it might add more space
	// than is needed in the presence of multiple overlapping rowspans)
	for(c = tab->cells; c != nil; c = c->next) {
		if(c->rowspan > 1) {
			spanht = 0;
			for(i = 0; i < c->rowspan && c->row + i < tab->nrow; i++)
				spanht += tab->rows[c->row + i].height;
			clay = c->lay;
			if(clay == nil)
				continue;
			ht = clay->height - (c->rowspan - 1)*vsep;
			if(ht > spanht) {
				// add extra space to last spanned row
				i = c->row + c->rowspan - 1;
				if(i >= tab->nrow)
					i = tab->nrow - 1;
				tab->rows[i].height += ht - spanht;
				if(dbgtab)
					trace("sizetable %d, row %d height %d\n",
						tab->tableid, i, tab->rows[i].height);
			}
		}
	}
	// get total width, heights, and col x / row y positions
	totw = bd + hsp + cbd + pad;
	for(ci = 0; ci < tab->ncol; ci++) {
		tab->cols[ci].pos.x = totw;
		if(dbgtab)
			trace("sizetable %d, col %d at x=%d\n", tab->tableid, ci, totw);
		totw += tab->cols[ci].width + hsep;
	}
	totw = totw - (cbd + pad) + bd;
	toth = bd + vsp + cbd + pad;
	// first time: move tab.caption items into layout
	if(tab->caption != nil) {
		// lay caption with ALleft; drawing will center it over the table width
		tab->caption_lay = sublayout(f, availwidth, ALleft, f->layout->background, tab->caption);
		caplay = tab->caption_lay;
		tab->caph = caplay->height + CAPSEP;
		tab->caption = nil;
	}
	else if(tab->caption_lay != nil) {
		caplay = tab->caption_lay;
		if(tab->availw != availwidth || (caplay->flags&Lchanged) != (uchar)0) {
			relayout(f, caplay, availwidth, ALleft);
			tab->caph = caplay->height + CAPSEP;
		}
	}
	if(tab->caption_place == ALtop)
		toth += tab->caph;
	for(ri = 0; ri < tab->nrow; ri++) {
		tab->rows[ri].pos.y = toth;
		if(dbgtab)
			trace("sizetable %d, row %d at y=%d\n", tab->tableid, ri, toth);
		toth += tab->rows[ri].height + vsep;
	}
	toth = toth - (cbd + pad) + bd;
	if(tab->caption_place == ALbottom)
		toth += tab->caph;
	tab->totw = totw;
	tab->toth = toth;
	tab->availw = availwidth;
	tab->flags &= ~Lchanged;
	if(dbgtab)
		trace("\ndone sizetable %d, availwidth %d, totw=%d, toth=%d\n\n",
			tab->tableid, availwidth, totw, toth);
}

// Calculate various table spacing parameters
static void
tableparams(Table* tab, int* hsp, int* vsp, int* pad, int* bd, int* cbd, int* hsep, int* vsep)
{
	*bd = tab->border;
	*hsp = tab->cellspacing;
	*vsp = *hsp;
	*pad = tab->cellpadding;
	*cbd = (tab->border)? 1 : 0;
	*hsep = 2*(*cbd + *pad) + *hsp;
	*vsep = 2*(*cbd + *pad) + *vsp;
}

// return cell width, taking multicol spanning into account
static int
cellwidth(Table* tab, Tablecell* c, int hsep)
{
	int	wd;
	int	i;

	if(c->colspan == 1)
		return tab->cols[c->col].width;
	wd = (c->colspan - 1)*hsep;
	for(i = 0; i < c->colspan && c->col + i < tab->ncol; i++)
		wd += tab->cols[c->col + i].width;
	return wd;
}

// return cell height, taking multirow spanning into account
static int
cellheight(Table* tab, Tablecell* c, int vsep)
{
	int	ht;
	int	i;

	if(c->rowspan == 1)
		return tab->rows[c->row].height;
	ht = (c->rowspan - 1)*vsep;
	for(i = 0; i < c->rowspan && c->row + i < tab->nrow; i++)
		ht += tab->rows[c->row + i].height;
	return ht;
}

// Calculate the column widths w as the max of the cells
// maxw or minw (as domax is 1 or 0).
// Return the total of all w.
// (hseps were accounted for by the adjustment that got
// totw from availwidth).
// hsep is amount of free space available between columns
// where there is multicolumn spanning.
// This is a two-pass algorithm.  The first pass ignores
// cells that span multiple columns.  The second pass
// sees if those multispanners need still more space, and
// if so, apportions the space out.
static int
widthcalc(Table* tab, int* w, int hsep, int domax)
{
	int	anyspan;
	int	totw;
	int	pass;
	int	curw;
	int	iend;
	int	i;
	int	diff;
	Tablecell*	c;
	int	cwd;
	int	ri;
	int	ci;

	anyspan = 0;
	totw = 0;
	for(pass = 1; pass <= 2; pass++) {
		if(pass == 2 && !anyspan)
			break;
		totw = 0;
		for(ci = 0; ci < tab->ncol; ci++) {
			for(ri = 0; ri < tab->nrow; ri++) {
				c = tab->grid[ri][ci];
				if(c == nil)
					continue;
				cwd = domax? c->maxw : c->minw;
				if(pass == 1) {
					if(c->colspan > 1) {
						anyspan = 1;
						continue;
					}
					if(cwd > w[ci])
						w[ci] = cwd;
				}
				else {
					if(c->colspan == 1 || !(ci == c->col && ri == c->row))
						continue;
					curw = 0;
					iend = ci + c->colspan;
					if(iend > tab->ncol)
						iend = tab->ncol;
					for(i = ci; i < iend; i++)
						curw += w[i];
				
					// padding between spanned cols is free
					cwd -= hsep*(c->colspan - 1);
					diff = cwd - curw;
					if(diff <= 0)
						continue;
					for(i = ci; i < iend; i++) {
						if(curw == 0)
							w[i] = diff/c->colspan;
						else
							w[i] += diff*w[i]/curw;
					}
				}
			}
			totw += w[ci];
		}
	}
	return totw;
}

static void
layframeset(Frame* f, Kidinfo* ki)
{
	int	fwid;
	int	fht;
	int	nrow;
	int*	rowh;
	int	ncol;
	int*	colw;
	Kidinfo*	l;
	int	y;
	int	i;
	Framelist*	al;
	Rectangle	r;
	Kidinfo*	kidki;
	Frame*	kidf;
	int	x;
	int	j;

	fwid = Dx(f->cr);
	fht = Dy(f->cr);
	if(dbglay)
		trace("layframeset, configuring frame %d wide by %d high\n", fwid, fht);
	nrow = frdimens(ki->rows, ki->nrows, fht, &rowh);
	ncol = frdimens(ki->cols, ki->ncols, fwid, &colw);
	l = ki->kidinfos;
	y = f->cr.min.y;
	for(i = 0; i < nrow; i++) {
		x = f->cr.min.x;
		for(j = 0; j < ncol; j++) {
			if(l == nil)
				return;
			r = Rect(x, y, x + colw[j], y + rowh[i]);
			if(dbglay)
				trace("kid gets rect (%d,%d)(%d,%d)\n", r.min.x, r.min.y, r.max.x, r.max.y);
			kidki = l;
			l = l->next;
			kidf = newkid(f, kidki, r);
			if(!kidki->isframeset)
				f->kids = newframelist(kidf, f->kids);
			if(kidf->framebd != 0) {
				kidf->cr = insetrect(kidf->r, 2);
				drawborder(kidf->cim, kidf->cr, 2, DarkGrey);
			}
			if(kidki->isframeset) {
				layframeset(kidf, kidki);
				for(al = kidf->kids; al != nil; al = al->next)
					f->kids = newframelist(al->val, f->kids);
			}
			x += colw[j];
		}
		y += rowh[i];
	}
}

// Use the dimension specs in dims to allocate total space t.
// Return number of dimens, and put an array of allocated space in *parr.
static int
frdimens(Dimen* dims, int n, int t, int** parr)
{
	int	totpix;
	int	totpcnt;
	int	totrel;
	int	i;
	double	spix;
	double	spcnt;
	int	min_relu;
	double	relu;
	int	tt;
	int*	x;
	int	v;
	int	kind;
	int	trest;
	int	totpixrel;
	double	vr;

	if(n == 1) {
		*parr = x = (int*)emalloc(sizeof(int));
		x[0] = t;
		return 1;
	}
	totpix = 0;
	totpcnt = 0;
	totrel = 0;
	for(i = 0; i < n; i++) {
		v = dimenspec(dims[i]);
		kind = dimenkind(dims[i]);
		if(v < 0) {
			v = 0;
			dims[i] = makedimen(kind, v);
		}
		switch(kind) {
		case Dpixels:
			totpix += v;
			break;
		case Dpercent:
			totpcnt += v;
			break;
		case Drelative:
			totrel += v;
			break;
		case Dnone:
			totrel++;
			break;
		}
	}
	spix = 1.0;
	spcnt = 1.0;
	min_relu = 0;
	if(totrel > 0)
		min_relu = 30;	// allow for scrollbar (14) and a bit
	relu = (double)min_relu;
	tt = totpix + (t*totpcnt/100) + totrel*min_relu;
	// want
	//  t ==  totpix*spix + (totpcnt/100)*spcnt*t + totrel*relu
	if(tt < t) {
		if(totrel == 0) {
			if(totpcnt != 0)
				// spix==1.0, relu==0, solve for spcnt
				spcnt = (double)((t - totpix)*100)/(double)(t*totpcnt);
			else
				// relu==0, totpcnt==0, solve for spix
				spix = (double)t/(double)totpix;
		}
		else
			// spix=1.0, spcnt=1.0, solve for relu
			relu += (double)(t - tt)/(double)totrel;
	}
	else {
		// need to contract one or more of spix, spcnt, and have relu==min_relu
		totpixrel = totpix + totrel*min_relu;
		if(totpixrel < t) {
			// spix==1.0, solve for spcnt
			spcnt = (double)((t - totpixrel)*100)/(double)(t*totpcnt);
		}
		else {
			// let spix==spcnt, solve
			trest = t - totrel*min_relu;
			if(trest > 0) {
				spcnt = (double)trest/(double)(totpix + (t*totpcnt/100));
			}
			else {
				spcnt = (double)t/(double)tt;
				relu = 0.0;
			}
			spix = spcnt;
		}
	}
	x = (int*)emalloc(n * sizeof(int));
	tt = 0;
	for(i = 0; i < n - 1; i++) {
		vr = (double)dimenspec(dims[i]);
		switch(dimenkind(dims[i])) {
		case Dpixels:
			vr = vr*spix;
			break;
		case Dpercent:
			vr = vr*(double)t*spcnt/100.0;
			break;
		case Drelative:
			vr = vr*relu;
			break;
		case Dnone:
			vr = relu;
			break;
		}
		x[i] = (int)(vr+.5);
		tt += x[i];
	}
	x[n - 1] = t - tt;
	*parr = x;
	return n;
}

// Return last item of list of items, or nil if no items
static Item*
lastitem(Item* it)
{
	Item*	ans;

	ans = it;
	for(; it != nil; it = it->next)
		ans = it;
	return ans;
}

// Lay out table if availw changed or tab changed
static void
checktabsize(Frame* f, Itable* t, int availw)
{
	Table*	tab;

	tab = t->table;
	if(availw != tab->availw || (tab->flags&Lchanged)) {
		sizetable(f, tab, availw);
		t->width = tab->totw + 2*tab->border;
		t->height = tab->toth + 2*tab->border;
		t->ascent = t->height;
	}
}

static int
widthfromspec(Dimen wspec, int availw)
{
	int	w;
	int	spec;

	w = availw;
	spec = dimenspec(wspec);
	switch(dimenkind(wspec)) {
	case Dpixels:
		w = spec;
		break;
	case Dpercent:
		w = spec*w/100;
		break;
	}
	return w;
}

// An image may have arrived for an image input field
static void
checkffsize(Frame* f, Item* i, Formfield* ff)
{
	int	w;
	int	h;
	Control*	c;
	Cbutton*	b;
	Iimage*	imi;

	if(ff->ftype == Fimage) {
		assert(ff->image->tag == Iimagetag);
		imi = (Iimage*)ff->image;
		if(imi->ci->mims != nil && ff->ctlid >= 0) {
			c = f->controls[ff->ctlid];
			if(c->tag == Cbuttontag) {
				b = (Cbutton*)c;
				if(b->pic == nil) {
					b->pic = imi->ci->mims[0]->im;
					b->picmask = imi->ci->mims[0]->mask;
					w = Dx(b->pic->r);
					h = Dy(b->pic->r);
					b->r.max.x = b->r.min.x + w;
					b->r.max.y = b->r.min.y + h;
					i->width = w;
					i->height = h;
					i->ascent = h;
				}
			}
		}
	}
}

static void
drawall(Frame* f)
{
	Point	origin;

	pushclipr(f->cr);
	fillbg(f, f->cr);
	origin = lptosp(f, Pt(0, 0));
	if(dbglay > 1)
		trace("drawall, cr=(%d,%d,%d,%d), viewr=(%d,%d,%d,%d), origin=(%d,%d)\n",
			f->cr.min.x, f->cr.min.y, f->cr.max.x, f->cr.max.y,
			f->viewr.min.x, f->viewr.min.y, f->viewr.max.x, f->viewr.max.y,
			origin.x, origin.y);
	if(f->layout != nil)
		drawlay(f, f->layout, origin);
	popclipr();
	flushc(f);
}

static void
drawlay(Frame* f, Lay* lay, Point origin)
{
	Line*	l;

	for(l = lay->start->next; l != lay->end; l = l->next)
		drawline(f, origin, l, lay);
}

// Draw line l in frame f, assuming that content's (0,0)
// aligns with layorigin in f->cim.
static void
drawline(Frame* f, Point layorigin, Line* l, Lay* lay)
{
	Image*	im;
	Point	o;
	int	x;
	int	y;
	Item*	it;
	Font*	fnt;
	int	yy;
	Image*	fgi;
	Control*	ctl;
	Point	dims;
	Point	p;
	Formfield*	ff;
	int	xx;
	Itext*	i;
	Irule*	ir;
	Iimage*	ii;
	Ifloat*	ifl;

	im = f->cim;
	o = addpt(layorigin, l->pos);
	x = o.x;
	y = o.y;
	for(it = l->items; it != nil; it = it->next) {
		switch(it->tag) {
		case Itexttag:
			i = (Itext*)it;
			fnt = fonts[i->fnt].f;
			yy = y + l->ascent - fnt->ascent + (i->voff) - Voffbias;
			fgi = colorimage(i->fg);
			runestring(im, Pt(x, yy), fgi, ZP, fnt, i->s);
			if(i->ul != ULnone) {
				if(i->ul == ULmid)
					yy += 2*i->ascent/3;
				else
					yy += i->height - 1;
				draw(im, Rect(x, yy, x + i->width, yy + 1), fgi, nil, ZP);
			}
			break;
		case Iruletag:
			ir = (Irule*)it;
			yy = y + RULESP;
			draw(im, Rect(x, yy, x + ir->width, yy + ir->size), display->black, nil, ZP);
			break;
		case Iimagetag:
			ii = (Iimage*)it;
			yy = y;
			if(ii->align == ALbottom)
				yy += l->ascent - ii->imheight;
			else if(ii->align == ALmiddle)
				yy += l->ascent - (ii->imheight/2);
			drawimg(f, Pt(x, yy), ii);
			break;
		case Iformfieldtag:
			ff = ((Iformfield*)it)->formfield;
			if(ff->ctlid >= 0) {
				ctl = f->controls[ff->ctlid];
				dims = subpt(ctl->r.max, ctl->r.min);
				yy = y + l->ascent - it->ascent;
				p = Pt(x, yy);
				ctl->r = Rpt(p, addpt(p, dims));
				drawctl(ctl, 0);
			}
			break;
		case Itabletag:
			drawtable(f, lay, Pt(x, y), ((Itable*)it)->table);
			break;
		case Ifloattag:
			ifl = (Ifloat*)it;
			xx = layorigin.x + lay->margin;
			if(ifl->side == ALright) {
				xx -= ifl->x;
				// for main layout of frame, floats hug
				// right edge of frame, not layout
				// (other browsers do that)
				if(f->layout == lay)
					xx += lay->targetwidth;
				else
					xx += lay->width;
			}
			else
				xx += ifl->x;
			switch(ifl->item->tag) {
			case Iimagetag:
				ii = (Iimage*)ifl->item;
				drawimg(f, Pt(xx, layorigin.y + ifl->y + (ii->border + ii->vspace)), ii);
				break;
			case Itabletag:
				drawtable(f, lay, Pt(xx, layorigin.y + ifl->y), ((Itable*)ifl->item)->table);
				break;
			}
			break;
		}
		x += it->width;
	}
}

static void
drawimg(Frame* f, Point iorigin, Iimage* i)
{
	CImage*	ci;
	Image*	im;
	Canimimage*	ac;
	Control*	c;
	Point	dims;
	MaskedImage*	mim;
	int	bdcol;
	Rectangle	r;
	Font*	fnt;
	int	yy;
	int	xx;
	int	col;
	Image*	fgi;

	ci = i->ci;
	im = f->cim;
	iorigin.x += i->hspace + i->border;
	// y coord is already adjusted for border and vspace
	if(ci->mims != nil) {
		r = Rpt(iorigin, addpt(iorigin, Pt(i->imwidth, i->imheight)));
		if(i->ctlid >= 0) {
			// animated
			c = f->controls[i->ctlid];
			dims = subpt(c->r.max, c->r.min);
			c->r = Rpt(iorigin, addpt(iorigin, dims));
			if(c->tag == Canimimagetag) {
				ac = (Canimimage*)c;
				ac->redraw = 1;
				ac->bg = f->layout->background;
			}
			drawctl(c, 0);
		}
		else {
			mim = ci->mims[0];
			iorigin = addpt(iorigin, mim->origin);
			draw(im, r, mim->im, mim->mask, ZP);
		}
		if(i->border) {
			if(i->anchorid != 0)
				bdcol = f->doc->link;
			else
				bdcol = Black;
			drawborder(im, r, i->border, bdcol);
		}
	}
	else if(config.imagelvl == ImgNone && i->altrep != nil) {
		fnt = fonts[DefFnt].f;
		yy = iorigin.y + (i->imheight - fnt->height)/2;
		xx = iorigin.x + (i->width - runestringwidth(fnt, i->altrep))/2;
		if(i->anchorid != 0)
			col = f->doc->link;
		else
			col = DarkGrey;
		fgi = colorimage(col);
		runestring(im, Pt(xx, yy), fgi, ZP, fnt, i->altrep);
	}
}

static void
drawtable(Frame* f, Lay* parentlay, Point torigin, Table* tab)
{
	Image*	im;
	int	hsp;
	int	vsp;
	int	pad;
	int	bd;
	int	cbd;
	int	hsep;
	int	vsep;
	int	x;
	int	y;
	int	capy;
	int	boxy;
	Image*	bgi;
	int	n;
	Tablecell*	c;
	Lay*	clay;
	int	cx;
	int	cy;
	int	wd;
	int	ht;
	Lay*	caplay;
	int	capx;

	if(tab->ncol == 0 || tab->nrow == 0)
		return ;
	im = f->cim;
	tableparams(tab, &hsp, &vsp, &pad, &bd, &cbd, &hsep, &vsep);
	x = torigin.x;
	y = torigin.y;
	capy = y;
	boxy = y;
	if(tab->caption_place == ALbottom)
		capy = y + tab->toth - tab->caph + vsp;
	else
		boxy = y + tab->caph;
	if(tab->background.image != parentlay->background.image ||
	   tab->background.color != parentlay->background.color) {
		bgi = colorimage(tab->background.color);
		draw(im, Rect(x, boxy, x + tab->totw, boxy + tab->toth - tab->caph), bgi, nil, ZP);
	}
	if(bd != 0)
		drawborder(im, Rect(x+bd, boxy+bd, x+tab->totw-bd, boxy+tab->toth-tab->caph-bd), 1, Black);
	for(c = tab->cells; c != nil; c = c->next) {
		clay = c->lay;
		if(clay == nil)
			continue;
		cx = x + tab->cols[c->col].pos.x;
		cy = y + tab->rows[c->row].pos.y;
		wd = cellwidth(tab, c, hsep);
		ht = cellheight(tab, c, vsep);
		if(c->background.color != tab->background.color) {
			bgi = colorimage(c->background.color);
			draw(im, Rect(cx-pad, cy-pad, cx+wd+pad, cy+ht+pad), bgi, nil, ZP);
		}
		if(bd != 0)
			drawborder(im, Rect(cx-pad+1, cy-pad+1, cx+wd+pad-1, cy+ht+pad-1), 1, Black);
		if(c->align.valign != ALtop && c->align.valign != ALbaseline) {
			n = ht - clay->height;
			if(c->align.valign == ALmiddle)
				cy += n/2;
			else if(c->align.valign == ALbottom)
				cy += n;
		}
		if(dbgtab)
			trace("drawtable %d cell %d at (%d,%d)\n", tab->tableid, c->cellid, cx, cy);
		drawlay(f, clay, Pt(cx, cy));
	}
	if(tab->caption_lay != nil) {
		caplay = tab->caption_lay;
		capx = x;
		if(caplay->width < tab->totw)
			capx += (tab->totw - caplay->width)/2;
		drawlay(f, caplay, Pt(capx, capy));
	}
}

// Draw border of width n just outside r, using src color
void
drawborder(Image* im, Rectangle r, int n, int color)
{
	int	x;
	int	y;
	int	xr;
	int	ybi;
	Image*	src;

	x = r.min.x - n;
	y = r.min.y - n;
	xr = r.max.x + n;
	ybi = r.max.y;
	src = colorimage(color);
	draw(im, Rect(x, y, xr, y + n), src, nil, ZP);			// top
	draw(im, Rect(x, ybi, xr, ybi + n), src, nil, ZP);		// bottom
	draw(im, Rect(x, y + n, x + n, ybi), src, nil, ZP);		// left
	draw(im, Rect(xr - n, y + n, xr, ybi), src, nil, ZP);	// right
}

// Draw relief border just outside r, width 2 border,
// colors white/lightgrey/darkgrey/black
// to give raised relief (if raised != 0) or sunken.
void
drawrelief(Image* im, Rectangle r, int raised)
{
	int	x;
	int	x1;
	int	x2;
	int	xr;
	int	xr1;
	int	xr2;
	int	y;
	int	y1;
	int	y2;
	int	yb;
	int	yb1;
	int	yb2;
	Image*	tlo;
	Image*	tli;
	Image*	bro;
	Image*	bri;

	// ((x,y),(xr,yb)) == r
	x = r.min.x;
	x1 = x - 1;
	x2 = x - 2;
	xr = r.max.x;
	xr1 = xr + 1;
	xr2 = xr + 2;
	y = r.min.y;
	y1 = y - 1;
	y2 = y - 2;
	yb = r.max.y;
	yb1 = yb + 1;
	yb2 = yb + 2;

	// colors for top/left outside, top/left inside, bottom/right outside, bottom/right inside
	if(raised) {
		tlo = colorimage(Grey);
		tli = colorimage(White);
		bro = colorimage(Black);
		bri = colorimage(DarkGrey);
	}
	else {
		tlo = colorimage(DarkGrey);
		tli = colorimage(Black);
		bro = colorimage(White);
		bri = colorimage(Grey);
	}
	draw(im, Rect(x2, y2, xr1, y1), tlo, nil, ZP);		// top outside
	draw(im, Rect(x1, y1, xr, y), tli, nil, ZP);			// top inside
	draw(im, Rect(x2, y1, x1, yb1), tlo, nil, ZP);		// left outside
	draw(im, Rect(x1, y, x, yb), tli, nil, ZP);			// left inside
	draw(im, Rect(xr, y1, xr1, yb), bri, nil, ZP);		// right inside
	draw(im, Rect(xr1, y, xr2, yb1), bro, nil, ZP);		// right outside
	draw(im, Rect(x1, yb, xr1, yb1), bri, nil, ZP);		// bottom inside
	draw(im, Rect(x, yb1, xr2, yb2), bro, nil, ZP);		// bottom outside
}

// Fill r with color
void
drawfill(Image* im, Rectangle r, int color)
{
	draw(im, r, colorimage(color), nil, ZP);
}

// Draw string in default font at p
void
drawstring(Image* im, Point p, Rune* s)
{
	runestring(im, p, colorimage(Black), ZP, fonts[DefFnt].f, s);
}

// Return (width, height) of string in default font
Point
measurestring(Rune* s)
{
	Font*	f;

	f = fonts[DefFnt].f;
	return Pt(runestringwidth(f, s), f->height);
}

// Mark as "changed" everything with change flags on the loc path
static void
markchanges(Loc* loc)
{
	int	i;
	Item*	it;
	Tablecell*	c;
	Lay*	clay;

	for(i = 0; i < loc->n; i++) {
		switch(loc->le[i].kind) {
		case LEframe:
			loc->le[i].frame->layout->flags |= Lchanged;
			break;
		case LEline:
			loc->le[i].line->flags |= Lchanged;
			break;
		case LEitem:
			it = loc->le[i].item;
			switch(it->tag) {
			case Itabletag:
				((Itable*)it)->table->flags |= Lchanged;
				break;
			case Ifloattag:
				// whole layout will be redone if layout changes
				// and there are any floats
				break;
			}
			break;
		case LEtablecell:
			c = loc->le[i].tcell;
			clay = c->lay;
			if(clay != nil)
				clay->flags |= Lchanged;
			break;
		}
	}
}

static Image*
colorimage(int rgb)
{
	Image*	im;
	int	hv;
	Colornode*	xhd;
	Colornode*	x;

	if(rgb == Black)
		return display->black;
	else if(rgb == White)
		return display->white;
	else {
		hv = rgb%NCOLHASH;
		xhd = colorhashtab[hv];
		x = xhd;
		while(x != nil && x->rgb != rgb)
			x = x->next;
		if(x == nil) {
			im = allocimage(display, Rect(0,0,1,1), screen->chan, 1, (rgb<<8)|0xFF);
			if(im == nil) {
				trace("can't allocate color! %x, chan %d\n", rgb, screen->chan);
				im = display->black;
			}
			x = newcolornode(rgb, im, xhd);
			colorhashtab[hv] = x;
		}
		return x->im;
	}
}

static Colornode*
newcolornode(int rgb, Image* im, Colornode* next)
{
	Colornode* c;

	c = (Colornode*)emalloc(sizeof(Colornode));
	c->rgb = rgb;
	c->im = im;
	c->next = next;
	return c;
}

// Use f->background.image (if not nil) or f->background.color to fill r (in cim coord system)
// with background color.
static void
fillbg(Frame* f, Rectangle r)
{
	Image*	bgi;
	CImage*	bgci;

	bgci = f->doc->background.image;
	bgi = nil;
	if(bgci != nil && bgci->mims != nil)
		bgi = bgci->mims[0]->im;
	if(bgi == nil)
		bgi = colorimage(f->doc->background.color);
	draw(f->cim, r, bgi, nil, f->viewr.min);
}

// Draw triangle pointing in direction given by kind (TRlup, etc.)
// Assume r is a square
static void
drawtriangle(Image* im, Rectangle r, int kind, int style)
{
	int	b;
	int	b2;
	int	bm2;
	Point	p[3];
	Image*	col012;
	Image*	col20;
	Image*	d;
	Image*	l;
	int	i;
	Image*	t;

	drawfill(im, r, Grey);
	b = r.max.x - r.min.x;
	if(b < 4)
		return ;
	b2 = b/2;
	bm2 = b - ReliefBd;
	d = colorimage(DarkGrey);
	l = colorimage(White);
	col012 = d;
	col20 = l;
	switch(kind) {
	case TRIup:
		p[0] = Pt(b2, ReliefBd);
		p[1] = Pt(bm2, bm2);
		p[2] = Pt(ReliefBd, bm2);
		break;
	case TRIdown:
		p[0] = Pt(b2, bm2);
		p[1] = Pt(ReliefBd, ReliefBd);
		p[2] = Pt(bm2, ReliefBd);
		col012 = l;
		col20 = d;
		break;
	case TRIleft:
		p[0] = Pt(bm2, ReliefBd);
		p[1] = Pt(bm2, bm2);
		p[2] = Pt(ReliefBd, b2);
		break;
	case TRIright:
		p[0] = Pt(ReliefBd, bm2);
		p[1] = Pt(ReliefBd, ReliefBd);
		p[2] = Pt(bm2, b2);
		col012 = l;
		col20 = d;
		break;
	}
	if(style == ReliefSunk) {
		t = col012;
		col012 = col20;
		col20 = t;
	}
	for(i = 0; i < 3; i++)
		p[i] = addpt(p[i], r.min);
	fillpoly(im, p, 3, ~0, colorimage(Grey), ZP);
	line(im, p[0], p[1], 0, 0, ReliefBd/2, col012, ZP);
	line(im, p[1], p[2], 0, 0, ReliefBd/2, col012, ZP);
	line(im, p[2], p[0], 0, 0, ReliefBd/2, col20, ZP);
}

static void
flushc(Frame* f)
{
	USED(f);
	flushimage(display, 1);
}

Frame*
newframe()
{
	Frame*	f;

	f = (Frame*)emallocz(sizeof(Frame));
	f->parent = nil;
	f->cim = nil;
	f->r = Rect(0, 0, 0, 0);
	resetframe(f);
	return f;
}

Framelist*
newframelist(Frame* f, Framelist* l)
{
	Framelist* fl;

	fl = (Framelist*)emalloc(sizeof(Framelist));
	fl->val = f;
	fl->next = l;
	return fl;
}

Frame*
newkid(Frame* parent, Kidinfo* ki, Rectangle r)
{
	Frame*	f;

	f = (Frame*)emallocz(sizeof(Frame));
	f->parent = parent;
	f->cim = parent->cim;
	f->r = r;
	resetframe(f);
	f->src = ki->src;
	f->name = ki->name;
	f->marginw = ki->marginw;
	f->marginh = ki->marginh;
	f->framebd = ki->framebd;
	f->flags = ki->flags;
	return f;
}

// Note: f->parent, f->cim and f->r should not be reset
// And if f->parent is true, don't reset params set in frameset.
void
resetframe(Frame* f)
{
	f->id = ++frameid;
	f->doc = nil;
	if(f->parent == nil) {
		f->src = nil;
		f->name = nil;
		f->marginw = FRMARGIN;
		f->marginh = FRMARGIN;
		f->framebd = 0;
		f->flags = FRvscroll|FRhscrollauto;
	}
	f->layout = nil;
	f->controls = nil;
	f->controlslen = 0;
	f->controlid = 0;
	f->cr = f->r;
	f->viewr = Rect(0, 0, 0, 0);
	f->totalr = f->viewr;
	f->vscr = nil;
	f->hscr = nil;
	f->kids = nil;
}

int
addcontrol(Frame* f, Control* c)
{
	int	ans;

	if(f->controlslen <= f->controlid) {
		f->controlslen += 30;
		f->controls = (Control**)erealloc(f->controls, f->controlslen * sizeof(Control*));
	}
	f->controls[f->controlid] = c;
	ans = f->controlid++;
	return ans;
}

void
xscroll(Frame* f, int kind, int val)
{
	int	newx;

	newx = f->viewr.min.x;
	switch(kind) {
	case CAscrollpage:
		newx += val*(Dx(f->cr)*8/10);
		break;
	case CAscrollline:
		newx += val*Dx(f->cr)/10;
		break;
	case CAscrolldelta:
		newx += val*f->totalr.max.x/Dx(f->cr);
		break;
	case CAscrollabs:
		newx = val;
		break;
	}
	newx = max(0, min(newx, f->totalr.max.x));
	if(newx != f->viewr.min.x) {
		f->viewr.min.x = newx;
		fixframegeom(f);
		drawall(f);
	}
}

// Don't actually scroll by "page" and "line",
// But rather, 80% and 10%, which give more
// context in the first case, and more motion
// in the second.
void
yscroll(Frame* f, int kind, int val)
{
	int	newy;

	newy = f->viewr.min.y;
	switch(kind) {
	case CAscrollpage:
		newy += val*(Dy(f->cr)*8/10);
		break;
	case CAscrollline:
		newy += val*Dy(f->cr)/10;
		break;
	case CAscrolldelta:
		newy += val*f->totalr.max.y/Dy(f->cr);
		break;
	case CAscrollabs:
		newy = val;
		break;
	}
	newy = max(0, min(newy, f->totalr.max.y));
	if(newy != f->viewr.min.y) {
		f->viewr.min.y = newy;
		fixframegeom(f);
		drawall(f);
	}
}

// Convert layout coords (where (0,0) is top left of layout)
// to screen coords (i.e., coord system of mouse, f->cr, etc.)
Point
sptolp(Frame* f, Point sp)
{
	return addpt(f->viewr.min, subpt(sp, f->cr.min));
}

// Reverse translation of sptolp
Point
lptosp(Frame* f, Point lp)
{
	return addpt(lp, subpt(f->cr.min, f->viewr.min));
}

// Return Loc of Item or Scrollbar containing p (p in screen coords)
// or item it, if that is not nil.
Loc*
findloc(Frame* f, Point p, Item* it)
{
	return framefind(newloc(), f, p, it);
}

// Find it (if non-nil) or place where p is (known to be inside f's layout).
static Loc*
framefind(Loc* loc, Frame* f, Point p, Item* it)
{
	Frame*	kf;
	Loc*	try;
	Framelist*	fl;
	Lay*	lay;

	addloc(loc, LEframe, f->r.min);
	loc->le[loc->n - 1].frame = f;
	if(it == nil) {
		if(f->vscr != nil && ptinrect(p, f->vscr->r)) {
			addloc(loc, LEcontrol, f->vscr->r.min);
			loc->le[loc->n - 1].control = f->vscr;
			loc->pos = subpt(p, f->vscr->r.min);
			return loc;
		}
		if(f->hscr != nil && ptinrect(p, f->hscr->r)) {
			addloc(loc, LEcontrol, f->hscr->r.min);
			loc->le[loc->n - 1].control = f->hscr;
			loc->pos = subpt(p, f->hscr->r.min);
			return loc;
		}
	}
	if(it != nil || ptinrect(p, f->cr)) {
		lay = f->layout;
		if(f->kids != nil) {
			for(fl = f->kids; fl != nil; fl = fl->next) {
				kf = fl->val;
				try = framefind(loc, kf, p, it);
				if(try != nil)
					return try;
			}
		}
		else if(lay != nil)
			return layfind(loc, f, lay, lptosp(f, ZP), p, it);
	}
	return nil;
}

// Find it (if non-nil) or place where p is (known to be inside f's layout).
// p (in screen coords), lay offset by origin also in screen coords
static Loc*
layfind(Loc* loc, Frame* f, Lay* lay, Point origin, Point p, Item* it)
{
	Ifloat*	flist;
	Line*	l;
	Loc*	lloc;
	Ifloat*	fl;
	int	fymin;
	int	fymax;
	int	inside;
	int	xx;
	Point	fp;
	int	match;
	Point	o;

	for(flist = lay->floats; flist != nil; flist = flist->nextfloat) {
		fl = flist;
		fymin = fl->y + origin.y;
		fymax = fymin + fl->item->height;
		inside = 0;
		xx = 0;
		if(it != nil || (fymin <= p.y && p.y < fymax)) {
			xx = origin.x + lay->margin;
			if(fl->side == ALright) {
				if(lay == f->layout)
					xx += Dx(f->cr) - fl->x;
				else
					xx += lay->width - fl->x;
			}
			else
				xx += fl->x;
			if(p.x >= xx && p.x < xx + fl->item->width)
				inside = 1;
		}
		fp = Pt(xx, fymin);
		match = 0;
		if(it != nil) {
			switch(fl->item->tag) {
			case Itabletag:
				addloc(loc, LEitem, fp);
				loc->le[loc->n - 1].item = (Item*)fl;
				loc->pos = subpt(p, fp);
				lloc = tablefind(loc, f, (Itable*)(fl->item), fp, p, it);
				if(lloc != nil)
					return lloc;
				break;
			case Iimagetag:
				match = (it == (Item*)fl || it == fl->item);
				break;
			}
		}
		if(match || inside) {
			addloc(loc, LEitem, fp);
			loc->le[loc->n - 1].item = (Item*)fl;
			loc->pos = subpt(p, fp);
			if(it == fl->item) {
				addloc(loc, LEitem, fp);
				loc->le[loc->n - 1].item = fl->item;
			}
			if(inside) {
				if(fl->item->tag == Itabletag)
					loc = tablefind(loc, f, (Itable*)(fl->item), fp, p, it);
			}
			return loc;
		}
	}
	for(l = lay->start; l != nil; l = l->next) {
		o = addpt(origin, l->pos);
		if(it != nil || (o.y <= p.y && p.y < o.y + l->height)) {
			lloc = linefind(loc, f, l, o, p, it);
			if(lloc != nil)
				return lloc;
			if(it == nil && o.y + l->height >= p.y)
				break;
		}
	}
	return nil;
}

// p (in screen coords), line at o, also in screen coords
static Loc*
linefind(Loc* loc, Frame* f, Line* l, Point o, Point p, Item* it)
{
	int	x;
	int	y;
	Item*	i;
	Font*	fnt;
	Loc*	lloc;
	Itext*	ti;
	Iimage*	ii;
	Iformfield*	fi;
	int	yy;
	int	h;

	addloc(loc, LEline, o);
	loc->le[loc->n - 1].line = l;
	x = o.x;
	y = o.y;
	for(i = l->items; i != nil; i = i->next) {
		if(it != nil || (x <= p.x && p.x < x + i->width)) {
			yy = y;
			h = 0;
			switch(i->tag) {
			case Itexttag:
				ti = (Itext*)i;
				fnt = fonts[ti->fnt].f;
				yy += l->ascent - fnt->ascent + (ti->voff) - Voffbias;
				h = fnt->height;
				break;
			case Iruletag:
				h = ((Irule*)i)->size;
				break;
			case Iimagetag:
				ii = (Iimage*)i;
				yy = y;
				if(ii->align == ALbottom)
					yy += l->ascent - ii->imheight;
				else if(ii->align == ALmiddle)
					yy += l->ascent - (ii->imheight/2);
				h = ii->imheight;
				break;
			case Iformfieldtag:
				fi = (Iformfield*)i;
				h = fi->height;
				yy += l->ascent - fi->ascent;
				if(it != nil) {
					if(it == fi->formfield->image) {
						addloc(loc, LEitem, Pt(x, yy));
						loc->le[loc->n - 1].item = i;
						addloc(loc, LEitem, Pt(x, yy));
						loc->le[loc->n - 1].item = it;
						loc->pos = ZP;
						return loc;
					}
				}
				else if(yy < p.y && p.y < yy + h && fi->formfield->ctlid >= 0) {
					addloc(loc, LEcontrol, Pt(x, yy));
					loc->le[loc->n - 1].control = f->controls[fi->formfield->ctlid];
					loc->pos = subpt(p, Pt(x, yy));
					return loc;
				}
				break;
			case Itabletag:
				lloc = tablefind(loc, f, (Itable*)i, Pt(x, y), p, it);
				if(lloc != nil)
					return lloc;
				break;
			}
			if(it == i || (it == nil && yy <= p.y && p.y < yy + h)) {
				addloc(loc, LEitem, Pt(x, yy));
				loc->le[loc->n - 1].item = i;
				loc->pos = subpt(p, Pt(x, yy));
				return loc;
			}
			if(it == nil)
				return nil;
		}
		x += i->width;
		if(it == nil && x >= p.x)
			break;
	}
	loc->n--;
	return nil;
}

static Loc*
tablefind(Loc* loc, Frame* f, Itable* ti, Point torigin, Point p, Item* it)
{
	Table*	t;
	int	hsp;
	int	vsp;
	int	pad;
	int	bd;
	int	cbd;
	int	hsep;
	int	vsep;
	Lay*	caplay;
	int	capy;
	Loc*	lloc;
	int	n;
	Tablecell*	c;
	Lay*	clay;
	int	cx;
	int	cy;
	int	wd;
	int	ht;

	addloc(loc, LEitem, torigin);
	loc->le[loc->n - 1].item = (Item*)ti;
	t = ti->table;
	tableparams(t, &hsp, &vsp, &pad, &bd, &cbd, &hsep, &vsep);
	caplay = t->caption_lay;
	if(caplay != nil) {
		capy = torigin.y;
		if(t->caption_place == ALbottom)
			capy += t->toth - t->caph + vsp;
		lloc = layfind(loc, f, caplay, Pt(torigin.x, capy), p, it);
		if(lloc != nil)
			return lloc;
	}
	for(c = t->cells; c != nil; c = c->next) {
		clay = c->lay;
		if(clay == nil)
			continue;
		cx = torigin.x + t->cols[c->col].pos.x;
		cy = torigin.y + t->rows[c->row].pos.y;
		wd = cellwidth(t, c, hsep);
		ht = cellheight(t, c, vsep);
		if(it == nil && !ptinrect(p, Rect(cx, cy, cx + wd, cy + ht)))
			continue;
		if(c->align.valign != ALtop && c->align.valign != ALbaseline) {
			n = ht - clay->height;
			if(c->align.valign == ALmiddle)
				cy += n/2;
			else if(c->align.valign == ALbottom)
				cy += n;
		}
		addloc(loc, LEtablecell, Pt(cx, cy));
		loc->le[loc->n - 1].tcell = c;
		lloc = layfind(loc, f, clay, Pt(cx, cy), p, it);
		if(lloc != nil)
			return lloc;
		loc->n--;
		if(it == nil)
			return nil;
	}
	loc->n--;
	return nil;
}

Control*
newff(Frame* f, Formfield* ff)
{
	Control*	ans;
	int	nh;
	int	nv;
	Image*	pic;
	Image*	picmask;
	Rune*	lab;
	Iimage*	i;
	Option*	o;
	Option*	ao;
	int	k;
	int	nvis;
	int	n;
	int	linewrap;

	ans = nil;
	switch(ff->ftype) {
	case Ftext:
	case Fpassword:
	case Ftextarea:
		nh = ff->size;
		nv = 1;
		linewrap = 0;
		if(ff->ftype == Ftextarea) {
			nh = ff->cols;
			nv = ff->rows;
			linewrap = 1;
		}
		ans = newentry(f, nh, nv, linewrap);
		entryset(ans, ff->value);
		break;
	case Fcheckbox:
	case Fradio:
		ans = newcheckbox(f, ff->ftype == Fradio);
		if(ff->flags&FFchecked)
			ans->flags |= CFactive;
		break;
	case Fsubmit:
	case Fimage:
	case Freset:
	case Fbutton:
		if(ff->image == nil)
			ans = newbutton(f, nil, nil, ff->value, nil, 0, 1);
		else {
			if(ff->image->tag == Iimagetag) {
				i = (Iimage*)(ff->image);
				pic = nil;
				picmask = nil;
				if(i->ci->mims != nil) {
					pic = i->ci->mims[0]->im;
					picmask = i->ci->mims[0]->mask;
				}
				lab = nil;
				if((config).imagelvl == ImgNone) {
					lab = i->altrep;
					i = nil;
				}
				ans = newbutton(f, pic, picmask, lab, i, 0, 0);
			}
		}
		break;
	case Fselect:
		n = listlen((List*)ff->options);
		if(n > 0) {
			ao = (Option*)emalloc(n * sizeof(Option));
			o = ff->options;
			for(k = 0; k < n; k++) {
				ao[k].selected = o->selected;
				ao[k].value = o->value;
				ao[k].display = o->display;
				o = o->next;
			}
			nvis = ff->size;
			if(nvis == 0)
				nvis = min(n, 4);
			ans = newselect(f, nvis, ao, n);
		}
		break;
	case Ffile:
		if(dbglay)
			trace("warning: unimplemented file form field\n");
		break;
	}
	if(ans != nil)
		ans->ff = ff;
	return ans;
}

Control*
newscroll(Frame* f, int isvert, int length, int breadth)
{
	Point	maxpt;
	int	flags;
	Cscrollbar *sb;

	// need room for at least two squares and 2 borders of size 2
	if(length < 12) {
		breadth = 0;
		length = 0;
	}
	else if(breadth*2 + 4 > length)
		breadth = (length - 4)/2;
	;
	flags = CFenabled;
	if(isvert) {
		maxpt = Pt(breadth, length);
		flags |= CFscrvert;
	}
	else
		maxpt = Pt(length, breadth);
	sb = (Cscrollbar*)emallocz(sizeof(Cscrollbar));
	sb->tag = Cscrollbartag;
	sb->f = f;
	sb->r = Rpt(ZP, maxpt);
	sb->flags = flags;
	return (Control*)sb;
}

Control*
newentry(Frame* f, int nh, int nv, int linewrap)
{
	int	w;
	int	h;
	Centry* e;

	w = ctlcharspace*nh + 2*ENTHMARGIN;
	h = ctllinespace*nv + 2*ENTVMARGIN;
	e = (Centry*)emallocz(sizeof(Centry));
	e->tag = Centrytag;
	e->f = f;
	e->r = Rect(0, 0, w, h);
	e->flags = CFenabled;
	e->linewrap = linewrap;
	return (Control*)e;
}

Control*
newbutton(Frame* f, Image* pic, Image* picmask, Rune* lab, Iimage* it, int candisable, int dorelief)
{
	Image*	dpic;
	Image*	dpicmono;
	Image*	dpicmask;
	int	w;
	int	h;
	Rectangle	r;
	Cbutton*	b;

	if(pic != nil) {
		w = Dx(pic->r);
		h = Dy(pic->r);
	}
	else if(it != nil) {
		w = it->imwidth;
		h = it->imheight;
	}
	else {
		w = runestringwidth(fonts[CtlFnt].f, lab);
		h = ctllinespace;
	}
	if(dorelief) {
		// form image buttons are shown without margins in other browsers
		w += 2*BUTMARGIN;
		h += 2*BUTMARGIN;
	}
	r = Rect(0, 0, w, h);
	dpic = nil;
	dpicmask = nil;
	if(candisable && pic != nil) {
		// make "greyed out" image:
		//	- convert pic to monochrome (black where pic is non-white)
		//	- draw pic in White, then DarkGrey shifted (-1,-1) and use
		//	    union of those two areas as mask
		dpicmask = allocimage(display, pic->r, GREY1, 0, DOpaque);
		dpicmono = allocimage(display, pic->r, GREY1, 0, DWhite);
		draw(dpicmono, dpicmono->r, pic, display->opaque, ZP);
		// leave DOpaque in dpicmask only where mono pic is black (==transparent)
		draw(dpicmask, dpicmask->r, display->transparent, dpicmono, ZP);
		dpic = allocimage(display, pic->r, pic->chan, 0, DWhite);
		// draw(dpic, dpic->r, colorimage(White), dpicmask, ZP);
		draw(dpic, rectaddpt(dpic->r, Pt(-1, -1)), colorimage(DarkGrey), dpicmask, ZP);
		draw(dpicmask, rectaddpt(dpicmask->r, Pt(-1, -1)), display->opaque, dpicmask, ZP);
		freeimage(dpicmono);
	}
	b = (Cbutton*)emallocz(sizeof(Cbutton));
	b->tag = Cbuttontag;
	b->f = f;
	b->r = r;
	b->flags = CFenabled;
	b->pic = pic;
	b->picmask = picmask;
	b->dpic = dpic;
	b->dpicmask = dpicmask;
	b->label = lab;
	b->dorelief = dorelief;
	return (Control*)b;
}

Control*
newcheckbox(Frame* f, int isradio)
{
	Ccheckbox* cb;

	cb = (Ccheckbox*)emallocz(sizeof(Ccheckbox));
	cb->tag = Ccheckboxtag;
	cb->f = f;
	cb->r = Rect(0, 0, CBOXWID, CBOXHT);
	cb->flags = CFenabled;
	cb->isradio = isradio;
	return (Control*)cb;
}

Control*
newselect(Frame* f, int nvis, Option* options, int noptions)
{
	Font*	fnt;
	int	w;
	int	i;
	int	h;
	Control*	scr;
	Cselect*	ans;
	Cscrollbar*	pscr;

	fnt = fonts[CtlFnt].f;
	w = 0;
	for(i = 0; i < noptions; i++)
		w = max(w, runestringwidth(fnt, options[i].display));
	w += 2*SELMARGIN;
	h = ctllinespace*nvis + 2*SELMARGIN;
	scr = nil;
	if(nvis < noptions) {
		scr = newscroll(f, 1, h, SCRFBREADTH);
		scr->r = rectaddpt(scr->r, Pt(w, 0));
		w += SCRFBREADTH;
	}
	pscr = (Cscrollbar*)scr;
	ans = (Cselect*)emallocz(sizeof(Cselect));
	ans->tag = Cselecttag;
	ans->f = f;
	ans->r = Rect(0, 0, w, h);
	ans->flags = CFenabled;
	ans->scr = pscr;
	ans->nvis = nvis;
	ans->options = options;
	ans->noptions = noptions;
	if(scr != nil) {
		pscr->ctl = (Control*)ans;
		scrollset(scr, 0, nvis, noptions, 1);
	}
	return (Control*)ans;
}

Control*
newanimimage(Frame* f, CImage* cim, Background bg)
{
	Canimimage *a;

	a = (Canimimage*)emallocz(sizeof(Canimimage));
	a->tag = Canimimagetag;
	a->f = f;
	a->r = Rect(0, 0, cim->width, cim->height);
	a->cim = cim;
	a->bg = bg;
	return (Control*)a;
}

Control*
newprogbox(Frame* f)
{
	Cprogbox *b;

	b = (Cprogbox*)emallocz(sizeof(Cprogbox));
	b->tag = Cprogboxtag;
	b->f = f;
	b->r = Rect(0, 0, PBOXWID, PBOXHT);
	b->flags = CFenabled;
	b->state = Punused;
	b->bsid = -1;
	return (Control*)b;
}

Control*
newlabel(Frame* f, Rune* s)
{
	int	w;
	int	h;
	Clabel* l;

	w = runestringwidth(fonts[CtlFnt].f, s);
	h = ctllinespace + 2*ENTVMARGIN;	// give it same height as an entry box
	l = (Clabel*)emallocz(sizeof(Clabel));
	l->tag = Clabeltag;
	l->f = f;
	l->r = Rect(0, 0, w, h);
	l->s = s;
	return (Control*)l;
}

void
disable(Control* c)
{
	if(c->flags&CFenabled) {
		c->flags &= ~CFenabled;
		if(c->f->cim != nil)
			drawctl(c, 1);
	}
}

void
enable(Control* c)
{
	if(!(c->flags&CFenabled)) {
		c->flags |= CFenabled;
		if(c->f->cim != nil)
			drawctl(c, 1);
	}
}

void
losefocus(Control* c)
{
	if(c->flags&CFhasfocus) {
		c->flags &= ~CFhasfocus;
		if(c->f->cim != nil)
			drawctl(c, 1);
	}
}

void
gainfocus(Control* c)
{
	if(!(c->flags&CFhasfocus)) {
		c->flags |= CFhasfocus;
		if(c->f->cim != nil)
			drawctl(c, 1);
	}
}

void
scrollset(Control* c, int v1, int v2, int vmax, int mindelta)
{
	int	length;
	int	breadth;
	int	l;
	Cscrollbar*	sc;

	assert(c->tag == Cscrollbartag);
	sc = (Cscrollbar*)c;
	sc->mindelta = mindelta;
	if(vmax <= 0) {
		sc->top = 0;
		sc->bot = 0;
	}
	else {
		if(v1 < 0)
			v1 = 0;
		if(v2 > vmax)
			v2 = vmax;
		if(v1 > v2)
			v1 = v2;
		if(sc->flags&CFscrvert) {
			length = sc->r.max.y - sc->r.min.y;
			breadth = sc->r.max.x - sc->r.min.x;
		}
		else {
			length = sc->r.max.x - sc->r.min.x;
			breadth = sc->r.max.y - sc->r.min.y;
		}
		l = length - 2*breadth;
		assert(l >= 0);
		sc->top = l*v1/vmax;
		sc->bot = l*(vmax - v2)/vmax;
	}
	drawctl(c, 1);
}

int
dokey(Control* ctl, int keychar)
{
	int	ans;
	int	k;
	int	newcurs;
	int	slen;
	Centry*	c;

	if(!(ctl->flags&CFenabled))
		return CAnone;
	ans = CAnone;
	switch(ctl->tag) {
	case Centrytag:
		c = (Centry*)ctl;
		newcurs = -1;
		slen = Strlen(c->s);
		switch(keychar) {
		case 'a' & CTRLMASK :
			newcurs = 0;
			break;
		case 'e' & CTRLMASK:
			newcurs = slen;
			break;
		case 'f' & CTRLMASK:
			if(c->curs < slen)
				newcurs = c->curs + 1;
			break;
		case 'b' & CTRLMASK:
			if(c->curs > 0)
				newcurs = c->curs - 1;
			break;
		case 'u' & CTRLMASK:
			entrydelrange(c, 0, slen);
			break;
		case 'v' & CTRLMASK:
			entrysetfromsnarf(c);
			break;
		case 'h' & CTRLMASK:
			if(c->curs > 0)
				entrydelrange(c, c->curs - 1, c->curs);
			break;
		case DEL:
			if(c->curs < slen)
				entrydelrange(c, c->curs, c->curs + 1);
			break;
		case TAB:
			ans = CAtabkey;
			break;
		default:
			if(keychar == CR) {
				if(c->linewrap)
					keychar = '\n';
				else
					ans = CAreturnkey;
			}
			if(keychar > CTRLMASK || (keychar == '\n' && c->linewrap)) {
				c->s = (Rune*)erealloc(c->s, (slen+2)*sizeof(Rune));
				c->s[slen+1] = 0;
				for(k = slen; k > c->curs; k--)
					c->s[k] = c->s[k - 1];
				c->s[c->curs] = keychar;
				newcurs = c->curs + 1;
			}
			break;
		}
		if(newcurs >= 0) {
			c->curs = newcurs;
			entryscroll(c);
		}
		break;
	}
	return ans;
}

int
domouse(Control* ctl, Point p, int mtype)
{
	int	ans;
	int	changed;
	int	cx;
	int	x;
	Font*	fnt;
	Rune*	s;
	Rune	s1[2];
	int	i;
	int	iend;
	Control*	d;
	Formfield*	ff;
	Form*	frm;
	Formfield*	lf;
	int	n;
	int	newfirst;
	int	val;
	int	v;
	int	vmin;
	int	vmax;
	int	b;
	int	vsltop;
	int	vslbot;
	int	actflags;
	int	oldactflags;
	int	down;
	int*	linestarts;
	int	topline;
	int	cursline;
	int	nlines;
	int	lineno;
	Cbutton*	cb;
	Centry*	ce;
	Ccheckbox*	ccb;
	Cselect*	cs;
	Cscrollbar*	csb;

	if(!(ctl->flags&CFenabled))
		return CAnone;
	ans = CAnone;
	changed = 0;
	switch(ctl->tag) {
	case Cbuttontag:
		cb = (Cbutton*)ctl;
		if(mtype == Mlbuttondown) {
			cb->flags |= CFactive;
			changed = 1;
		}
		else if(mtype == Mmove && cb->ff == nil) {
			ans = CAflyover;
		}
		else if(mtype == Mlbuttonup) {
			cb->flags &= ~CFactive;
			changed = 1;
			ans = CAbuttonpush;
		}
		break;
	case Centrytag:
		ce = (Centry*)ctl;
		if(mtype == Mlbuttondown) {
			x = ce->r.min.x + ENTHMARGIN;
			fnt = fonts[CtlFnt].f;
			s = ce->s;
			s1[1] = 0;
			i = ce->left;
			iend = Strlen(s);
			if(ce->linewrap) {
				nlines = entrywrapcalc(ce, &linestarts, &topline, &cursline);
				if(nlines > 1) {
					lineno = topline + (p.y-(ce->r.min.y+ENTVMARGIN))/ctllinespace;
					if(lineno >= nlines)
						lineno = nlines-1;
					i = linestarts[lineno];
					iend = linestarts[lineno+1];
				}
			}
			for(; i < iend; i++) {
				s1[0] = s[i];
				cx = runestringwidth(fnt, s1);
				if(p.x < x + cx)
					break;
				x += cx;
			}
			ce->curs = i;
			changed = 1;
			if(!(ce->flags&CFhasfocus))
				ans = CAkeyfocus;
		}
		break;
	case Ccheckboxtag:
		ccb = (Ccheckbox*)ctl;
		if(mtype == Mlbuttonup) {
			if(ccb->isradio) {
				if(!(ccb->flags&CFactive)) {
					ccb->flags |= CFactive;
					changed = 1;
					frm = ccb->ff->form;
					for(lf = frm->fields; lf != nil; lf = lf->next) {
						ff = lf;
						if(ff == ccb->ff)
							continue;
						if(ff->ftype == Fradio && !Strcmp(ff->name, ccb->ff->name) && ff->ctlid >= 0) {
							d = ccb->f->controls[ff->ctlid];
							if(d->flags&CFactive) {
								d->flags &= ~CFactive;
								drawctl(d, 0);
								break;
							}
						}
					}
				}
			}
			else {
				ccb->flags ^= CFactive;
				changed = 1;
			}
		}
		break;
	case Cselecttag:
		cs = (Cselect*)ctl;
		if(cs->scr != nil && p.x >= cs->r.max.x - SCRFBREADTH) {
			return domouse((Control*)cs->scr, p, mtype);
		}
		else if(mtype == Mlbuttonup) {
			n = (p.y - (cs->r.min.y + SELMARGIN))/ctllinespace + cs->first;
			if(n >= 0 && n < cs->noptions) {
				cs->options[n].selected ^= 1;
				if(cs->ff != nil && !(cs->ff->flags&FFmultiple) && cs->options[n].selected) {
					for(i = 0; i < cs->noptions; i++) {
						if(i != n)
							cs->options[i].selected = 0;
					}
				}
				changed = 1;
			}
		}
		break;
	case Cscrollbartag:
		csb = (Cscrollbar*)ctl;
		val = 0;
		if(csb->flags&CFscrvert) {
			v = p.y;
			vmin = csb->r.min.y;
			vmax = csb->r.max.y;
			b = Dx(csb->r);
		}
		else {
			v = p.x;
			vmin = csb->r.min.x;
			vmax = csb->r.max.x;
			b = Dy(csb->r);
		}
		vsltop = vmin + b + csb->top;
		vslbot = vmax - b - csb->bot;
		actflags = 0;
		oldactflags = csb->flags&CFscrallact;
		down = (mtype == Mlbuttondown);
		if(v >= vsltop && v < vslbot) {
			if(mtype == Mldrag && (csb->flags&CFactive)) {
				actflags = CFactive;
				val = v - csb->dragv;
				if(abs(val) > csb->mindelta) {
					ans = CAscrolldelta;
					csb->dragv = v;
				}
			}
			else if(down || mtype == Mldrag) {
				actflags = CFactive;
				csb->dragv = v;
			}
		}
		else if(down || mtype == Mlbuttonup) {
			if(v < vmin + b) {
				if(down)
					actflags = CFscracta1;
				else {
					ans = CAscrollline;
					val = -1;
				}
			}
			else if(v < vsltop) {
				if(down)
					actflags = CFscracttr1;
				else {
					ans = CAscrollpage;
					val = -1;
				}
			}
			else if(v >= vmax - b) {
				if(down)
					actflags = CFscracta2;
				else {
					ans = CAscrollline;
					val = 1;
				}
			}
			else if(v >= vslbot) {
				if(down)
					actflags = CFscracttr2;
				else {
					ans = CAscrollpage;
					val = 1;
				}
			}
		}
		csb->flags = (csb->flags&~CFscrallact)|actflags;
		if(ans != CAnone) {
			if(csb->ctl != nil) {
				switch(csb->ctl->tag) {
				case Cselecttag:
					cs = (Cselect*)csb->ctl;
					newfirst = cs->first;
					switch(ans) {
					case CAscrollpage:
						newfirst += val*cs->nvis;
						break;
					case CAscrollline:
						newfirst += val;
						break;
					case CAscrolldelta:
						if(val > 0)
							newfirst++;
						else
							newfirst--;
						break;
					}
					newfirst = max(0, min(newfirst, cs->noptions - cs->nvis));
					cs->first = newfirst;
					scrollset((Control*)csb, newfirst, newfirst + cs->nvis, cs->noptions, 1);
					drawctl((Control*)cs, 1);
					return ans;
				}
			}
			else {
				if(csb->flags&CFscrvert)
					yscroll(csb->f, ans, val);
				else
					xscroll(csb->f, ans, val);
			}
			changed = 1;
		}
		else if(actflags != oldactflags) {
			changed = 1;
		}
		break;
	case Cprogboxtag:
		if(mtype == Mlbuttondown)
			ans = CAbuttonpush;
		break;
	}
	if(changed)
		drawctl(ctl, 1);
	return ans;
}

void
resetctl(Control* ctl)
{
	Option*	o;
	int	i;
	Centry*	ce;
	Ccheckbox*	ccb;
	Cselect*	cs;

	switch(ctl->tag) {
	case Cbuttontag:
		ctl->flags &= ~CFactive;
		break;
	case Centrytag:
		ce = (Centry*)ctl;
		ce->s = nil;
		ce->curs = 0;
		ce->left = 0;
		if(ce->ff != nil && ce->ff->value != nil)
			ce->s = ce->ff->value;
		break;
	case Ccheckboxtag:
		ccb = (Ccheckbox*)ctl;
		ccb->flags &= ~CFactive;
		if(ccb->ff != nil && !(ccb->ff->flags&FFchecked))
			ccb->flags |= CFactive;
		break;
	case Cselecttag:
		cs = (Cselect*)ctl;
		if(cs->ff != nil) {
			o = cs->ff->options;
			for(i = 0; i < cs->noptions; i++) {
				cs->options[i].selected = o->selected;
				o = o->next;
			}
		}
		cs->first = 0;
		if(cs->scr != nil) {
			scrollset((Control*)cs->scr, 0, cs->nvis, cs->noptions, 1);
		}
		break;
	case Canimimagetag:
		((Canimimage*)ctl)->cur = 0;
		break;
	}
	drawctl(ctl, 0);
}

void
drawctl(Control* ctl, int flush)
{
	Image*	win;
	Iimage*	imi;
	Image*	m;
	Image*	pic;
	Point	p;
	int	w;
	int	h;
	int	x;
	int	y;
	int	relief;
	Rectangle	cursr;
	Rune*	s;
	Font*	fnt;
	int	a;
	int	a1;
	Point	cen;
	Point	p1;
	Point	p2;
	Point	p3;
	Point	p4;
	Rectangle	ir;
	Rectangle	r;
	Image*	black;
	Image*	white;
	Image*	navy;
	int	i;
	int	xr;
	int	yt1;
	int	ys;
	int	yb;
	int	ya2;
	int	yt2;
	int	xt1;
	int	xs;
	int	xa2;
	int	xt2;
	Rectangle	ra1;
	Rectangle	rt1;
	Rectangle	rs;
	Rectangle	rt2;
	Rectangle	ra2;
	int	b;
	int	l;
	int	a1kind;
	int	a2kind;
	int	a1relief;
	int	a2relief;
	int	rsrelief;
	int	iprev;
	MaskedImage*	mim;
	Image*	bgi;
	int	bcol;
	int	rcol;
	int	xw;
	int	cursx;
	int	cursy;
	Rune**	lines;
	int*	linestarts;
	int	nlines;
	int	istart;
	int	iend;
	int	n;
	Point	q;
	Cbutton*	cb;
	Centry*	ce;
	Ccheckbox*	ccb;
	Cselect*	cs;
	Cscrollbar*	csb;
	Canimimage*	ca;
	Cprogbox*	cp;
	Clabel*	cl;

	if(!rectclip(&ctl->r, ctl->f->r))
		return;
	win = ctl->f->cim;
	pushclipr(ctl->r);
	switch(ctl->tag) {
	case Cbuttontag:
		cb = (Cbutton*)ctl;
		if(cb->ff != nil && cb->ff->image != nil && cb->pic == nil) {
			// check to see if image arrived
			// (dimensions will have been set by checkffsize, if needed;
			// this code is only for when the HTML specified the dimensions)
			imi = (Iimage*)cb->ff->image;
			assert(imi->tag == Iimagetag);
			if(imi->ci->mims != nil) {
				cb->pic = imi->ci->mims[0]->im;
				cb->picmask = imi->ci->mims[0]->mask;
			}
		}
		if(cb->dorelief || cb->pic == nil)
			draw(win, cb->r, colorimage(Grey), nil, ZP);
		if(cb->pic != nil) {
			if(cb->flags&CFenabled) {
				pic = cb->pic;
				m = cb->picmask;
			}
			else {
				pic = cb->dpic;
				m = cb->dpicmask;
			}
			w = Dx(pic->r);
			h = Dy(pic->r);
			x = cb->r.min.x + (Dx(cb->r) - w)/2;
			y = cb->r.min.y + (Dy(cb->r) - h)/2;
			if((cb->flags&CFactive) && cb->dorelief) {
				x++;
				y++;
			}
			draw(win, Rect(x, y, x + w, y + h), pic, m, ZP);
		}
		else if(cb->label != nil) {
			p = addpt(cb->r.min, Pt(BUTMARGIN, BUTMARGIN));
			if(cb->flags&CFactive)
				p = addpt(p, Pt(1, 1));
			runestring(win, p, colorimage(Black), ZP, fonts[CtlFnt].f, cb->label);
		}
		if(cb->dorelief) {
			relief = ReliefRaised;
			if(cb->flags&CFactive)
				relief = ReliefSunk;
			drawrelief(win, insetrect(cb->r, 2), relief);
		}
		break;
	case Centrytag:
		ce = (Centry*)ctl;
		draw(win, ce->r, colorimage(White), nil, ZP);
		drawrelief(win, insetrect(ce->r, 2), ReliefSunk);
		p = addpt(ce->r.min, Pt(ENTHMARGIN, ENTVMARGIN));
		s = ce->s + ce->left;
		fnt = fonts[CtlFnt].f;
		cursx = -1;
		cursy = p.y;
		if(ce->linewrap) {
			nlines = wrapstring(fnt, s, Dx(ce->r)-2*ENTHMARGIN, &lines, &linestarts);
			q = p;
			if(nlines > 0)
				cursx = 0;
			for(n = 0; n < nlines; n++) {
				s = lines[n];
				runestring(win, q, colorimage(Black), ZP, fnt, s);
				istart = linestarts[n];
				iend = linestarts[n+1];
				if(ce->curs >= istart && (ce->curs<iend || (ce->curs==iend && n == nlines-1))) {
					if(ce->curs > istart)
						cursx = runestringnwidth(fnt, s, ce->curs-ce->left);
					else
						cursx = 0;
					cursy = q.y;
				}
				q.y += ctllinespace;
			}
		}
		else {
			runestring(win, p, colorimage(Black), ZP, fnt, s);
			if(ce->curs >= ce->left)
				cursx = runestringnwidth(fnt, s, ce->curs - ce->left);
		}
		if((ce->flags&CFhasfocus) && cursx >= 0) {
			cursr = Rect(p.x + cursx - 2, cursy - 1, p.x + cursx, cursy + ctllinespace + 1);
			draw(win, cursr, colorimage(Black), nil, ZP);
		}
		break;
	case Ccheckboxtag:
		ccb = (Ccheckbox*)ctl;
		draw(win, ccb->r, colorimage(White), nil, ZP);
		if(ccb->isradio) {
			a = CBOXHT/2;
			a1 = a - 1;
			cen = Pt(ccb->r.min.x + a, ccb->r.min.y + a);
			ellipse(win, cen, a1, a1, 1, colorimage(DarkGrey), ZP);
			arc(win, cen, a, a, 0, colorimage(Black), ZP, 45, 180);
			arc(win, cen, a, a, 0, colorimage(Grey), ZP, 225, 180);
			if(ccb->flags&CFactive)
				fillellipse(win, cen, 2, 2, colorimage(Black), ZP);
		}
		else {
			ir = insetrect(ccb->r, 2);
			ir.min.x += CBOXWID - CBOXHT;
			ir.max.x -= CBOXWID - CBOXHT;
			drawrelief(win, ir, ReliefSunk);
			if(ccb->flags&CFactive) {
				p1 = Pt(ir.min.x, ir.min.y);
				p2 = Pt(ir.max.x, ir.max.y);
				p3 = Pt(ir.max.x, ir.min.y);
				p4 = Pt(ir.min.x, ir.max.y);
				line(win, p1, p2, Endsquare, Endsquare, 0, colorimage(Black), ZP);
				line(win, p3, p4, Endsquare, Endsquare, 0, colorimage(Black), ZP);
			}
		}
		break;
	case Cselecttag:
		cs = (Cselect*)ctl;
		black = colorimage(Black);
		white = colorimage(White);
		navy = colorimage(Navy);
		draw(win, cs->r, white, nil, ZP);
		drawrelief(win, insetrect(cs->r, 2), ReliefSunk);
		ir = insetrect(cs->r, SELMARGIN);
		p = ir.min;
		fnt = fonts[CtlFnt].f;
		for(i = cs->first; i < cs->noptions && i < cs->first + cs->nvis; i++) {
			if(cs->options[i].selected) {
				r = Rect(p.x - SELMARGIN, p.y, cs->r.max.x - SCRFBREADTH, p.y + ctllinespace);
				draw(win, r, navy, nil, ZP);
				runestring(win, p, white, ZP, fnt, cs->options[i].display);
			}
			else {
				runestring(win, p, black, ZP, fnt, cs->options[i].display);
			}
			p.y += ctllinespace;
		}
		if(cs->scr != nil) {
			cs->scr->r = rectsubpt(cs->scr->r, cs->scr->r.min);
			cs->scr->r = rectaddpt(cs->scr->r, Pt(cs->r.max.x - SCRFBREADTH, cs->r.min.y));
			drawctl((Control*)cs->scr, 0);
		}
		break;
	case Cscrollbartag:
		csb = (Cscrollbar*)ctl;
		x = csb->r.min.x;
		y = csb->r.min.y;
		if(csb->flags&CFscrvert) {
			l = csb->r.max.y - csb->r.min.y;
			b = csb->r.max.x - csb->r.min.x;
			xr = x + b;
			yt1 = y + b;
			ys = yt1 + csb->top;
			yb = y + l;
			ya2 = yb - b;
			yt2 = ya2 - csb->bot;
			ra1 = Rect(x, y, xr, yt1);
			rt1 = Rect(x, yt1, xr, ys);
			rs = Rect(x, ys, xr, yt2);
			rt2 = Rect(x, yt2, xr, ya2);
			ra2 = Rect(x, ya2, xr, yb);
			a1kind = TRIup;
			a2kind = TRIdown;
		}
		else {
			l = csb->r.max.x - csb->r.min.x;
			b = csb->r.max.y - csb->r.min.y;
			yb = y + b;
			xt1 = x + b;
			xs = xt1 + csb->top;
			xr = x + l;
			xa2 = xr - b;
			xt2 = xa2 - csb->bot;
			ra1 = Rect(x, y, xt1, yb);
			rt1 = Rect(xt1, y, xs, yb);
			rs = Rect(xs, y, xt2, yb);
			rt2 = Rect(xt2, y, xa2, yb);
			ra2 = Rect(xa2, y, xr, yb);
			a1kind = TRIleft;
			a2kind = TRIright;
		}
		a1relief = ReliefRaised;
		if(csb->flags&CFscracta1)
			a1relief = ReliefSunk;
		a2relief = ReliefRaised;
		if(csb->flags&CFscracta2)
			a2relief = ReliefSunk;
		drawtriangle(win, ra1, a1kind, a1relief);
		drawtriangle(win, ra2, a2kind, a2relief);
		drawfill(win, rt1, Grey);
		rs = insetrect(rs, 2);
		drawfill(win, rs, Grey);
		rsrelief = ReliefRaised;
		if(csb->flags&CFactive)
			rsrelief = ReliefSunk;
		drawrelief(win, rs, rsrelief);
		drawfill(win, rt2, Grey);
		break;
	case Canimimagetag:
		ca = (Canimimage*)ctl;
		i = ca->cur;
		if(ca->redraw)
			i = 0;
		else if(i > 0) {
			iprev = i - 1;
			if(ca->cim->mims[iprev]->bgcolor != -1) {
				i = iprev;
				// get i back to before all "reset to previous"
				// images (which will be skipped in following
				// image drawing loop)
				while(i > 0 && ca->cim->mims[i]->bgcolor == -2)
					i--;
			}
		}
		bgi = colorimage(ca->bg.color);
		if(ca->bg.image != nil && ca->bg.image->mimslen > 0)
			bgi = ca->bg.image->mims[0]->im;
		for(; i <= ca->cur; i++) {
			mim = ca->cim->mims[i];
			if(i > 0 && i < ca->cur && mim->bgcolor == -2)
				continue;
			p = addpt(ca->r.min, mim->origin);
			r = mim->im->r;
			r = Rpt(p, addpt(p, Pt(Dx(r), Dy(r))));

			// IE takes "clear-to-background" disposal method to mean
			// clear to background of HTML page, ignoring any background
			// color specified in the GIF.
			// IE clears to background before frame 0
			if(i == 0)
				draw(win, ca->r, bgi, nil, ZP);
			if(i != ca->cur && mim->bgcolor >= 0)
				draw(win, r, bgi, nil, ZP);
			else
				draw(win, r, mim->im, mim->mask, ZP);
		}
		break;
	case Cprogboxtag:
		cp = (Cprogbox*)ctl;
		if(cp->state != Punused) {
			bcol = Black;
			rcol = Grey;
			xw = (cp->pcnt*PBOXWID + 50)/100;
			if(xw > PBOXWID)
				xw = PBOXWID;
			switch(cp->state) {
			case Pstart:
				drawfill(win, cp->r, Grey);
				bcol = White;
				break;
			case Pconnected:
				bcol = Black;
				break;
			case Phavehdr:
				bcol = DarkGrey;
				break;
			case Phavedata:
				bcol = rcol = DarkGrey;
				break;
			case Pdone:
				xw = PBOXWID;
				bcol = rcol = DarkestGrey;
				break;
			case Perr:
				bcol = rcol = Red;
				xw = PBOXWID;
				break;
			case Paborted:
				bcol = rcol = Navy;
				xw = PBOXWID;
				break;
			}
			drawborder(win, insetrect(cp->r, 3), 3, bcol);
			if(rcol != Grey) {
				r = cp->r;
				r.max.x -= (PBOXWID - xw);
				drawfill(win, r, rcol);
			}
		}
		break;
	case Clabeltag:
		cl = (Clabel*)ctl;
		p = addpt(cl->r.min, Pt(0, ENTVMARGIN));
		runestring(win, p, colorimage(Black), ZP, fonts[CtlFnt].f, cl->s);
		break;
	}
	popclipr();
	if(flush)
		flushimage(display, 1);
}

// Break s up into substrings that fit in width availw
// when printing with font fnt.
// The returned linestarts array will contain the indexes into the original
// string where the corresponding line starts (which might not be simply
// the sum of the preceding lines because of cr/lf's in the original string
// which are omitted from the lines array.
// Return value is length of plines array.
// Empty lines (ending in cr) get put into the array as empty strings.
// The start indices array has an entry for the phantom next line, to avoid
// the need for special cases in the rest of the code.
static int
wrapstring(Font* fnt, Rune* s, int availw, Rune*** plines, int** plinestarts)
{
	Strlist* sl;
	List* sstartl;
	int sw;
	int n;
	int k;
	int slen;
	int done;
	int needbs;
	int origlen;
	int kincr;
	Rune* s1;
	Rune* s2;
	int w1;
	int w2;
	int s1len;
	int s2len;
	Rune** lines;
	int* linestarts;

	sl = nil;
	sstartl = nil;
	sw = runestringwidth(fnt, s);
	n = 0;
	k = 0;
	slen = Strlen(s);
	origlen = slen;
	done = 0;
	while(!done) {
		kincr = 0;
		needbs = 0;
		if(slen == 0) {
			s1 = nil;
			done = 1;
		}
		else {
			// if any newlines in s1, it's a forced break
			// (and newlines aren't to appear in result)
			splitl(s, slen, L"\n", &s1, &s1len, &s2, &s2len);
			if(s2len == 0) {
				if(sw <= availw) {
					s1 = s;
					done = 1;
				}
				else
					needbs = 1;
			}
			else {
				if(runestringnwidth(fnt,s1,s1len) <= availw) {
					s1 = Strndup(s1, s1len);
					s = Strndup(s2+1, s2len-1);
					slen = s2len-1;
					sw = runestringwidth(fnt, s);
					kincr = s1len+1;
				}
				else
					needbs = 1;
			}
			if(needbs) {
				if(breakstring(s, sw, fnt, availw, 0, &s1, &w1, &s2, &w2) < 0) {
					// no break possible
					s1 = s;
					done = 1;
				}
				else {
					s = s2;
					slen = Strlen(s);
					sw = w2;
					kincr = Strlen(s1);
				}
			}
		}
		sl = newstrlist(s1, sl);
		sstartl = newlist(k, sstartl);
		k += kincr;
		n++;
	}
	// Use arrays for return values,
	// reverse back to original order
	lines = (Rune**)emalloc(n * sizeof(Rune*));
	linestarts = (int*)emalloc((n+1) * sizeof(int));
	linestarts[n] = origlen;
	k = n;
	while(sl != nil) {
		lines[--k] = sl->val;
		linestarts[k] = sstartl->val;
		sl = sl->next;
		sstartl = sstartl->next;
	}
	*plines = lines;
	*plinestarts = linestarts;
	return n;
}

void
entryset(Control* c, Rune* s)
{
	Centry*	e;

	assert(c->tag == Centrytag);
	e = (Centry*)c;
	e->s = s;
	e->curs = 0;
	e->left = 0;
	drawctl((Control*)e, 1);
}

// delete given range of characters, and redraw
static void
entrydelrange(Centry* e, int istart, int iend)
{
	int	n;
	int	ns;
	int	ne;
	Rune*	news;
	Rune*	p;

	n = iend - istart;
	if(n > 0) {
		// replace e->s with e->s[0:istart] + e->s[iend:]
		ns = Strlen(e->s);
		if(istart == 0 && iend == ns)
			e->s = nil;
		else {
			ne = ns- iend;
			news = newstr(istart+ne);
			p = Stradd(news, e->s, istart);
			p = Stradd(p, e->s+iend, ne);
			*p = 0;
			e->s = news;
		}
		if(e->curs > istart) {
			if(e->curs < iend)
				e->curs = istart;
			else
				e->curs = istart + e->curs - iend;
		}
		if(e->left > istart)
			e->left = max(istart - 1, 0);
		entryscroll(e);
	}
}

// Set entry from snarf buffer.
static void
entrysetfromsnarf(Centry* e)
{
	int f;
	int n;
	Rune* s;
	uchar buf[BIGBUFSIZE];

	f = open("/dev/snarf", OREAD);
	if(f >= 0) {
		n = read(f, buf, BIGBUFSIZE);
		if(n > 0) {
			// trim a trailing newline, as a service...
			if(buf[n-1] == '\n')
				n--;
			s = toStr(buf, n, UTF_8);
			entryset((Control*)e, s);
		}
		close(f);
	}
}

// Given e, a Centry with line wrapping,
// return number of wrapped lines, and fill in
//   *pstartlines=line start indices,
//   *ptopline=line# of top displayed line,
//   *pcursline# containing cursor
static int
entrywrapcalc(Centry* e, int** plinestarts, int* ptopline, int* pcursline)
{
	int i, nlines, topline, cursline, i1, i2;
	Rune** lines;
	int* linestarts;

	nlines = wrapstring(fonts[CtlFnt].f, e->s, Dx(e->r)-2*ENTHMARGIN, &lines, &linestarts);
	topline = 0;
	cursline = 0;
	for(i = 0; i < nlines; i++) {
		i1 = linestarts[i];
		i2 = linestarts[i+1];
		if(e->left >= i1 && e->left < i2)
			topline = i;
		if(e->curs >= i1 && e->curs < i2)
			cursline = i;
	}
	if(e->curs == linestarts[nlines])
		cursline = nlines - 1;
	*plinestarts = linestarts;
	*ptopline = topline;
	*pcursline = cursline;
	return nlines;
}

// make sure can see cursor and following char or two, and redraw
static void
entryscroll(Centry* e)
{
	Rune* s;
	int w;
	Font* fnt;
	int wantw;
	int* linestarts;
	int cursline, topline, vislines;

	s = e->s;
	if(e->linewrap) {
		// For multiple line entries, c->left is the char
		// at the beginning of the topmost visible line,
		// and we just want to scroll to make sure that
		// the line with the cursor is visible
		entrywrapcalc(e, &linestarts, &topline, &cursline);
		if(cursline < topline)
			topline = cursline;
		else {
			vislines = (Dy(e->r)-2*ENTVMARGIN)/ctllinespace;
			if(cursline >= topline+vislines)
				topline = cursline-vislines+1;
		}
		e->left = linestarts[topline];
	}
	else {
		if(e->curs < e->left)
			e->left = e->curs;
		else if(e->curs > e->left) {
			fnt = fonts[CtlFnt].f;
			wantw = Dx(e->r) - 2*ENTHMARGIN - 2*ctlspspace;
			while(e->left < e->curs - 1) {
				w = runestringnwidth(fnt, s+e->left, e->curs - e->left);
				if(w < wantw)
					break;
				e->left++;
			}
		}
	}
	drawctl((Control*)e, 1);
}

Lay*
newlay(int targwidth, int just, int margin, Background bg)
{
	Lay*	ans;
	Item*	it;

	ans = (Lay*)emallocz(sizeof(Lay));
	ans->start = newline();
	ans->end = newline();
	ans->start->pos = Pt(margin, margin);
	ans->start->next = ans->end;
	ans->end->prev = ans->start;
	ans->targetwidth = targwidth - 2*margin;
	if(ans->targetwidth < 0)
		ans->targetwidth = 0;
	ans->margin = margin;
	ans->background = bg;
	ans->just = just;
	it = newispacer(ISPnull);
	it->state = IFbrk|IFcleft|IFcright;
	ans->end->items = it;
	return ans;
}

Line*
newline(void)
{
	return (Line*)emallocz(sizeof(Line));
}

Loc*
newloc(void)
{
	Loc* ans;

	ans = (Loc*)emallocz(sizeof(Loc));
	ans->lelen = 10;
	ans->le = (Locelem*)emallocz(ans->lelen * sizeof(Locelem));
	return ans;
}

void
addloc(Loc* loc, int kind, Point pos)
{
	if(loc->n == loc->lelen) {
		loc->lelen += 10;
		loc->le = (Locelem*)erealloc(loc->le, loc->lelen * sizeof(Locelem));
	}
	loc->le[loc->n].kind = kind;
	loc->le[loc->n].pos = pos;
	loc->n++;
}

// return last frame in loc's path
Frame*
lastframe(Loc* loc)
{
	int	i;

	for(i = loc->n - 1; i >= 0; i--)
		if(loc->le[i].kind == LEframe)
			return loc->le[i].frame;
	return nil;
}

void
printloc(Loc* loc, char* msg)
{
	int	i;

	trace("%s: Loc with %d components, pos=(%d,%d)\n", msg, loc->n, loc->pos.x, loc->pos.y);
	for(i = 0; i < loc->n; i++) {
		switch(loc->le[i].kind) {
		case LEframe:
			trace("frame %p\n", (void*)loc->le[i].frame);
			break;
		case LEline:
			trace("line %p\n", (void*)loc->le[i].line);
			break;
		case LEitem:
			trace("item: %p\n%I", (void*)loc->le[i].item, loc->le[i].item);
			break;
		case LEtablecell:
			trace("tablecell: %p, cellid=%d\n", (void*)loc->le[i].tcell, loc->le[i].tcell->cellid);
			break;
		case LEcontrol:
			trace("control %p\n", (void*)loc->le[i].control);
			break;
		}
	}
}

static Sources*
newsources(void)
{
	Sources* ans;

	ans = emallocz(sizeof(Sources));
	return ans;
}

static Source*
newshtml(ByteSource* bs, ItemSource* is)
{
	Shtml* s;

	s = (Shtml*)emallocz(sizeof(Shtml));
	s->tag = Shtmltag;
	s->bs = bs;
	s->itsrc = is;
	return (Source*)s;
}

static Source*
newsimage(ByteSource* bs, CImage* ci, ImageSource* is)
{
	Simage* s;

	s = (Simage*)emallocz(sizeof(Simage));
	s->tag = Simagetag;
	s->bs = bs;
	s->ci = ci;
	s->imsrc = is;
	return (Source*)s;
}

static void
addsource(Sources* srcs, Source* s)
{
	s->next = srcs->srcs;
	srcs->srcs = s;
	srcs->tot++;
}

static Source*
findbs(Sources* srcs, ByteSource* bs)
{
	Source*	s;

	for(s = srcs->srcs; s != nil; s = s->next) {
		if(s->bs == bs)
			return s;
	}
	return nil;
}

// spawned to animate images in frame f = args[0]
static void
animproc(void* arg)
{
USED(arg);
/* TODO
	Frame*	f;
	Itemlist*	aits;
	int	del;
	int	d;
	vlong	tot;
	Loc*	loc;
	Point	p;
	Control*	ctl;
	MaskedImage**	ms;
	Item*	i;
	Item*	it;
	MaskedImage*	m;
	Control*	c;
	int	newdel;
	Itemlist*	al;

	f = (Frame*)args[0];
	f->animpid = pctl(0, nil);
	aits = nil;
	del = 10000000;
	for(i = f->doc->images; i != nil; i = i->nextimage) {
		ms = i->ci->mims;
		if(i->ci->mimslen > 1) {
				loc = findloc(f, ZP, it);
				if(loc == nil) {
					if(dbglay)
						trace("couldn't find item for animated image\n");
					continue;
				}
				p = loc->le[loc->n - 1].pos;
				p.x += i->hspace + i->border;
				ctl = newanimimage(f, i->ci, f->layout->background, 0);
				ctl->r = rectaddpt(ctl->r, p);
				i->ctlid = addcontrol(f, ctl);
				d = ms[0]->delay;
				if(dbglay)
					trace("added anim ctl %d for image %S, initial delay %d\n", i->ctlid, i->ci->src->url->url, d);
				aits = newItempl(it, aits);
				if(d < del)
					del = d;
			}
			break;
		}
	}
	if(aits == nil)
		return ;
	tot = (vlong)0;
	while(1) {
		sleep(del);
		tot = tot + (vlong)del;
		newdel = 10000000;
		for(al = aits; al != nil; al = al->tl) {
			it = al->hd;
			// TODO: declare Item* tmp_32
			tmp_32 = al->hd;
			switch(tmp_32->tag) {
			case Iimagetag:
				i = (Iimage*)tmp_32;
				ms = i->ci->mims;
				// TODO: declare Control* tmp_33
				tmp_33 = f->controls[i->ctlid];
				switch(tmp_33->tag) {
				case Canimimagetag:
					c = (Canimimage*)tmp_33;
					m = ms[c->cur];
					d = m->delay;
					if(d > 0) {
						d = (int)(tot%((vlong)d));
						if(d > 0)
							d = m->delay - d;
					}
					if(d == 0) {
						c->cur++;
						if(c->cur == arraylen(ms))
							c->cur = 0;
						d = ms[c->cur]->delay;
						draw(c, 1);
					}
					if(d < newdel)
						newdel = d;
					break;
				}
				break;
			}
		}
		del = newdel;
	}
*/
}


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.