shithub: riscv

ref: 8be7f27b758ef9ef818f68db0d8bcb7b038eae0e
dir: /sys/src/cmd/abaco/util.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <memdraw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <plumb.h>
#include <html.h>
#include <regexp.h>
#include "dat.h"
#include "fns.h"

static	Point		prevmouse;
static	Window	*mousew;

int
min(int a, int b)
{
	if(a < b)
		return a;
	return b;
}

int
max(int a, int b)
{
	if(a > b)
		return a;
	return b;
}

void
cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
{
	uchar *q;
	Rune *s;
	int j, w;

	/*
	 * Always guaranteed that n bytes may be interpreted
	 * without worrying about partial runes.  This may mean
	 * reading up to UTFmax-1 more bytes than n; the caller
	 * knows this.  If n is a firm limit, the caller should
	 * set p[n] = 0.
	 */
	q = (uchar*)p;
	s = r;
	for(j=0; j<n; j+=w){
		if(*q < Runeself){
			w = 1;
			*s = *q++;
		}else{
			w = chartorune(s, (char*)q);
			q += w;
		}
		if(*s)
			s++;
		else if(nulls)
			*nulls = TRUE;
	}
	*nb = (char*)q-p;
	*nr = s-r;
}

void
bytetorunestr(char *s, Runestr *rs)
{
	Rune *r;
	int nb, nr;

	nb = strlen(s);
	r = runemalloc(nb+1);
	cvttorunes(s, nb, r, &nb, &nr, nil);
	r[nr] = '\0';
	rs->nr = nr;
	rs->r = r;
}

void
error(char *s)
{
	fprint(2, "abaco: %s: %r\n", s);
//	abort();
	threadexitsall(s);
}

void*
emalloc(ulong n)
{
	void *p;

	p = malloc(n);
	if(p == nil)
		error("malloc failed");
	setmalloctag(p, getcallerpc(&n));
	memset(p, 0, n);
	return p;
}

void*
erealloc(void *p, ulong n)
{
	p = realloc(p, n);
	if(p == nil)
		error("realloc failed");
	setmalloctag(p, getcallerpc(&n));
	return p;
}

Rune*
erunestrdup(Rune *r)
{
	void *p;

	if(r == nil)
		return nil;
	p = runestrdup(r);
	if(p == nil)
		error("runestrdup failed");
	setmalloctag(p, getcallerpc(&r));
	return p;
}

char*
estrdup(char *s)
{
	char *t;

	t = strdup(s);
	if(t == nil)
		error("strdup failed");
	setmalloctag(t, getcallerpc(&s));
	return t;
}

int
runestreq(Runestr a, Runestr b)
{
	return runeeq(a.r, a.nr, b.r, b.nr);
}

int
runeeq(Rune *s1, uint n1, Rune *s2, uint n2)
{
	if(n1 != n2)
		return FALSE;
	return memcmp(s1, s2, n1*sizeof(Rune)) == 0;
}

void
closerunestr(Runestr *rs)
{

	rs->nr = 0;
	if(rs->r)
		free(rs->r);
	rs->r = nil;
}

void
copyrunestr(Runestr *a, Runestr *b)
{
	closerunestr(a);
	a->r = runemalloc(b->nr+1);
	runemove(a->r, b->r, b->nr);
	a->r[b->nr] = 0;
	a->nr = b->nr;
}

int
isalnum(Rune c)
{
	/*
	 * Hard to get absolutely right.  Use what we know about ASCII
	 * and assume anything above the Latin control characters is
	 * potentially an alphanumeric.
	 */
	if(c <= ' ')
		return FALSE;
	if(0x7F<=c && c<=0xA0)
		return FALSE;
	if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
		return FALSE;
	return TRUE;
}

Rune*
skipbl(Rune *r, int n, int *np)
{
	while(n>0 && (*r==' ' || *r=='\t' || *r=='\n')){
		--n;
		r++;
	}
	*np = n;
	return r;
}

Rune*
findbl(Rune *r, int n, int *np)
{
	while(n>0 && *r!=' ' && *r!='\t' && *r!='\n'){
		--n;
		r++;
	}
	*np = n;
	return r;
}

int
istextfield(Item *i)
{
	Formfield *ff;

	ff = ((Iformfield *)i)->formfield;
	if(ff->ftype==Ftext || ff->ftype==Ftextarea || ff->ftype==Fpassword)
		return TRUE;

	return FALSE;
}

int
forceitem(Item *i)
{
	if(i->state&IFwrap && i->tag!=Iruletag && i->tag!=Itabletag)
		return FALSE;

	return TRUE;
}

int
dimwidth(Dimen d, int w)
{
	int s, k;

	k = dimenkind(d);
	if(k == Dnone)
		return w;
	s = dimenspec(d);
	if(k == Dpixels)
		 w = s;
	else if(k==Dpercent && s<100)
		w = s*w/100;

	return w;
}

void
frdims(Dimen *d, int n, int t, int **ret)
{
	int totpix, totpcnt, totrel;
	double spix, spcnt, relu, vd;
	int tt, trest, totpixrel, minrelu, i;
	int *x, *spec, *kind;

	if(n == 1){
		*ret = x = emalloc(sizeof(int));
		x[0] = t;
		return;
	}
	totpix = totpcnt = totrel = 0;
	spec = emalloc(n*sizeof(int));
	kind = emalloc(n*sizeof(int));
	for(i=0; i<n; i++){
		spec[i] = dimenspec(d[i]);
		if(spec[i] < 0)
			spec[i] = 0;
		kind[i] = dimenkind(d[i]);
		switch(kind[i]){
		case Dpixels:
			totpix += spec[i];
			break;
		case Dpercent:
			totpcnt += spec[i];
			break;
		case Drelative:
			totrel += spec[i];
			break;
		case Dnone:
			totrel++;
			break;
		}
	}
	spix = spcnt = 1.0;
	minrelu = 0;
	if(totrel > 0)
		minrelu = Scrollsize+Scrollgap;
	relu = (double)minrelu;
	tt = totpix + t*totpcnt/100 + totrel*minrelu;
	if(tt < t){
		if(totrel == 0){
			if(totpcnt != 0)
				spcnt = (double)((t-totpix)*100)/(double)(t*totpcnt);
			else
				spix = (double)t/(double)totpix;
		}else
			relu += (double)(t-tt)/(double)totrel;
	}else{
		totpixrel = totpix + totrel*minrelu;
		if(totpixrel < t)
			spcnt = (double)((t-totpixrel)*100)/(double)(t*totpcnt);
		else{
			trest = t - totrel*minrelu;
			if(trest > 0)
				spcnt = (double)trest/(double)(totpix + (t*totpcnt/100));
			else{
				spcnt = (double)t/(double)tt;
				relu = 0.0;
			}
			spix = spcnt;
		}
	}
	x = emalloc(n * sizeof(int));
	tt = 0;
	for(i=0; i<n-1; i++){
		vd = (double)spec[i];
		switch(kind[i]){
		case Dpixels:
			vd = vd*spix;
			break;
		case Dpercent:
			vd = vd*(double)t*spcnt/100.0;
			break;
		case Drelative:
			vd = vd*relu;
			break;
		case Dnone:
			vd = relu;
			break;
		}
		x[i] = (int)(vd+.5);
		tt += x[i];
	}
	x[n - 1] = t - tt;
	*ret = x;
	free(spec);
	free(kind);
}

Image *
getbg(Page *p)
{
	Docinfo *d;
	Cimage *ci;
	Image *bg;

	d = p->doc;
	if(d->backgrounditem){
		if(d->backgrounditem->aux){
			ci = d->backgrounditem->aux;
			if(ci->mi)
				getimage(ci, d->backgrounditem->altrep);
			bg = ci->i;
		}else
			bg = display->white;
	}else
		bg = getcolor(d->background.color);

	return bg;
}

Rune *
getbase(Page *p)
{
	if(p->doc)
		return p->doc->base;
	if(p->url->act.r)
		return p->url->act.r;
	return p->url->src.r;
}

Image *
eallocimage(Display *d, Rectangle r, ulong chan, int repl, int col)
{
	Image *i;

	i = allocimage(d, r, chan, repl, col);
	if(i == nil)
		error("allocimage failed");
	return i;
}

void
rect3d(Image *im, Rectangle r, int i, Image **c, Point sp)
{
	Point p[6];

	if(i < 0){
		r = insetrect(r, i);
		sp = addpt(sp, Pt(i,i));
		i = -i;
	}
	draw(im, Rect(r.min.x+i, r.min.y+i, r.max.x-i, r.max.y-i), c[2], nil, sp);
	p[0] = r.min;
	p[1] = Pt(r.min.x, r.max.y);
	p[2] = Pt(r.min.x+i, r.max.y-i);
	p[3] = Pt(r.min.x+i, r.min.y+i);
	p[4] = Pt(r.max.x-i, r.min.y+i);
	p[5] = Pt(r.max.x, r.min.y);
	fillpoly(im, p, 6, 0, c[0], sp);
	p[0] = r.max;
	p[1] = Pt(r.min.x, r.max.y);
	p[2] = Pt(r.min.x+i, r.max.y-i);
	p[3] = Pt(r.max.x-i, r.max.y-i);
	p[4] = Pt(r.max.x-i, r.min.y+i);
	p[5] = Pt(r.max.x, r.min.y);
	fillpoly(im, p, 6, 0, c[1], sp);
}

void
ellipse3d(Image *im, Point p, int rad, int i, Image **c, Point sp)
{
	fillarc(im, p, rad, rad, c[0], sp, 45, 180);
	fillarc(im, p, rad, rad, c[1], sp,  45, -180);
	fillellipse(im, p, rad-i, rad-i, c[2], sp);
}

void
colarray(Image **c, Image *c0, Image *c1, Image *c2, int checked)
{
	if(checked){
		c[0] = c0;
		c[1] = c1;
	}else{
		c[0] = c1;
		c[1] = c0;
	}
	c[2] = c2;
}

static char *deffontpaths[] = {
#include "fonts.h"
};

static char *fontpaths[NumFnt];
static Font *fonts[NumFnt];

void
initfontpaths(void)
{
	Biobufhdr *bp;
	char buf[128];
	char *s;
	int i;

	/* we don't care if getenv(2) fails */
	s = getenv("home");
	snprint(buf, sizeof(buf)-1, "%s/lib/abaco.fonts", s);
	free(s);
	if((bp=Bopen(buf, OREAD)) == nil)
		goto Default;

	for(i=0; i<NumFnt; i++)
		if((fontpaths[i]=Brdstr(bp, '\n', 1)) == nil)
			goto Error;

	Bterm(bp);
	return;
Error:
	fprint(2, "abaco: not enough fontpaths in '%s'\n", buf);
	Bterm(bp);
	for(i--; i>=0; i--)
		free(fontpaths[i]);
Default:
	for(i=0; i<NumFnt; i++)
		fontpaths[i] = deffontpaths[i];
}

Font *
getfont(int i)
{
	if(fonts[i] == nil){
		fonts[i] = openfont(display, fontpaths[i]);
		if(fonts[i] == nil)
			error("can't open font file");
	}
	return fonts[i];
}

typedef struct Color Color;

struct Color {
	int	rgb;
	Image	*i;
	Color	*next;
};

enum {
	NHASH = 19,
};

static Color *colortab[NHASH];

Image *
getcolor(int rgb)
{
	Color *c;
	int h;

	if(rgb == 0xFFFFFF)
		return display->white;
	else if(rgb == 0x000000)
		return display->black;

	h = rgb%NHASH;
	for(c=colortab[h]; c!=nil; c=c->next)
		if(c->rgb == rgb){
			flushimage(display, 0);	/* BUG? */
			return c->i;
		}
	c = emalloc(sizeof(Color));
	c->i = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, (rgb<<8)|0xFF);
	c->rgb = rgb;
	c->next = colortab[h];
	colortab[h] = c;

	return c->i;
}

int
plumbrunestr(Runestr *rs, char *attr)
{
	Plumbmsg *m;
	int i;

	i = -1;
	if(plumbsendfd >= 0){
		m = emalloc(sizeof(Plumbmsg));
		m->src = estrdup("abaco");
		m->dst = nil;
		m->wdir = estrdup("/tmp");
		m->type = estrdup("text");
		if(attr)
			m->attr = plumbunpackattr(attr);
		else
			m->attr = nil;
		m->data = smprint("%.*S", rs->nr, rs->r);
		m->ndata = -1;
		i = plumbsend(plumbsendfd, m);
		plumbfree(m);
	}
	return i;
}

int
hexdigit(int v)
{
	if(0<=v && v<=9)
		return '0' + v;
	else
		return 'A' + v - 10;
}

static int
inclass(char c, Rune* cl)
{
	int n, ans, negate, i;

	n = runestrlen(cl);
	if(n == 0)
		return 0;
	ans = 0;
	negate = 0;
	if(cl[0] == '^'){
		negate = 1;
		cl++;
		n--;
	}
	for(i=0; i<n; i++){
		if(cl[i]=='-' && i>0 && i<n-1){
			if(c>=cl[i - 1] && c<=cl[i+1]){
				ans = 1;
				break;
			}
			i++;
		}
		else if(c == cl[i]){
			ans = 1;
			break;
		}
	}
	if(negate)
		ans = !ans;
	return ans;
}

Rune*
ucvt(Rune* s)
{
	Rune* u;
	char *t;
	int i, c, n, j, len;

	t = smprint("%S", s);
	n = strlen(t);
	len = 0;
	for(i=0; i<n; i++){
		c = t[i];
		if(inclass(c, L"- /$_@.!*'(),a-zA-Z0-9"))
			len++;
		else
			len += 3;
	}
	u = runemalloc(len+1);
	j = 0;

	for(i=0; i<n; i++){
		c = t[i];
		if(inclass(c, L"-/$_@.!*'(),a-zA-Z0-9"))
			u[j++] = c;
		else if(c == ' ')
			u[j++] = '+';
		else {
			u[j++] = '%';
			u[j++] = hexdigit((c >> 4)&15);
			u[j++] = hexdigit(c&15);
		}
	}
	u[j] = 0;
	free(t);
	return u;
}

void
reverseimages(Iimage **head)
{
	Iimage *r, *c, *n;

	r = nil;
	for(c=*head; c!=nil; c=n){
		n = c->nextimage;
		c->nextimage = r;
		r = c;
	}
	*head = r;
}

char urlexpr[] = "^(https?|ftp|file|gopher|mailto|news|nntp|telnet|wais|"
	"prospero)://[^/]+";
Reprog	*urlprog;

int
validurl(Rune *r)
{
	Resub rs[10];

	if(urlprog == nil){
		urlprog = regcomp(urlexpr);
		if(urlprog == nil)
			error("regcomp");
	}
	memset(rs, 0, sizeof(rs));
	if(rregexec(urlprog, r, rs, nelem(rs)) == 0)
		return FALSE;
	return TRUE;
}

static void
execproc(void *v)
{
	Exec *e;

	threadsetname("execproc");
	e = v;
	rfork(RFFDG);
	dup(e->p[0], 0);
	close(e->p[0]);
	close(e->p[1]);
	if(e->q[0]){
		dup(e->q[1], 1);
		close(e->q[0]);
		close(e->q[1]);
	}
	if(!procstderr)
		close(2);
	procexecl(e->sync, "/bin/rc", "rc", "-c", e->cmd, nil);
	error("can't exec");
}

int
pipeline(int fd, char *cmd, ...)
{
	Exec *e;
	va_list a;

	e = emalloc(sizeof(Exec));
	if(pipe(e->p)<0 || pipe(e->q)<0)
		error("can't create pipe");
	close(e->p[0]);
	e->p[0] = fd;
	va_start(a, cmd);
	e->cmd = vsmprint(cmd, a);
	va_end(a);
	e->sync = chancreate(sizeof(ulong), 0);
	if(e->sync == nil)
		error("can't create channel");
	proccreate(execproc, e, STACK);
	recvul(e->sync);
	chanfree(e->sync);
	free(e->cmd);
	close(e->p[0]);
	close(e->p[1]);
	close(e->q[1]);
	fd = e->q[0];
	free(e);
	return fd;
}

static
int
isspace(char c)
{
	return c==' ' || c== '\t' || c=='\r' || c=='\n';
}

int
findctype(char *b, int l, char *keyword, char *s)
{
	char *p, *e, c;
	int i;

	p = cistrstr(s, keyword);
	if(!p)
		return -1;
	p += strlen(keyword);
	while(*p && isspace(*p))
		p++;
	if(*p != '=')
		return -1;
	p++;
	while(*p && isspace(*p))
		p++;
	if(!*p)
		return -1;
	switch (c = *p){
	case '"':
	case '\'':
		p++;
		e = strchr(p, c);
		if(!e)
			return -1;
		break;
	default:
		for(e = p; *e < 127 && *e > ' ' ; e++)
			;
	}
	i = utfnlen(p, e - p);
	if(i < 1)
		return -1;
	snprint(b, l, "%.*s", i, p);
	return 0;
}

int
xtofchar(Rune *s, Font *f, long p)
{
	Rune *r;
	int q;

	if(p == 0)
		return 0;

	q = 0;
	for(r=s; *r!=L'\0'; r++){
		p -= runestringnwidth(f, r, 1);
		if(p < 0)
			break;
		q++;
	}
	return q;
}

int
istextsel(Page *p, Rectangle r, int *q0, int *q1, Rune *s, Font *f)
{
	int topinr, botinr;

	*q0 = *q1 = 0;
	topinr= ptinrect(p->top, r);
	if(topinr || (r.min.y>p->top.y && r.max.y<p->bot.y))
		p->selecting = TRUE;
	botinr = ptinrect(p->bot, r);
	if(botinr || r.min.y>p->bot.y)
		p->selecting = FALSE;

	if(topinr || botinr){
		if(topinr)
			*q0 = xtofchar(s, f, p->top.x-r.min.x);
		if(botinr)
			*q1 = xtofchar(s, f, p->bot.x-r.min.x);
		if(*q0!=0 || *q1!=0)
			return TRUE;
	}
	return p->selecting;
}

Point
getpt(Page *p, Point xy)
{
	xy.x = xy.x-p->r.min.x+p->pos.x;
	xy.y = xy.y-p->r.min.y+p->pos.y;

	return xy;
}

void
getimage(Cimage *ci, Rune *altr)
{
	Rectangle r;
	Memimage *mi;
	Image *i, *i2;
	char buf[128];
	uchar *bits;
	int nbits;

	mi = ci->mi;
	if(mi == nil){
		snprint(buf, sizeof(buf), "[%S]", altr ? altr : L"IMG");
		r.min = Pt(0, 0);
		r.max.x = 2*Space + stringwidth(font, buf);
		r.max.y = 2*Space + font->height;
		ci->i = eallocimage(display, r, GREY1, 1, DBlack);
		r.min.x += Space;
		r.min.y += Space;
		string(ci->i, r.min, display->white, ZP, font, buf);
		return;
	}
	nbits = bytesperline(mi->r, mi->depth)*Dy(mi->r);
	bits = emalloc(nbits);
	unloadmemimage(mi, mi->r, bits, nbits);
/*
	/* get rid of alpha channel from transparent gif * /

	if(mi->depth == 16){
		for(y=1; y<nbits; y+=2)
			bits[y>>1] = bits[y];
	}
*/
	i = eallocimage(display, mi->r, mi->chan, 0, DNofill);
	loadimage(i, i->r, bits, nbits);
	i2 = eallocimage(display, i->r, RGB24, 1, DNofill);
	draw(i2, i2->r, display->black, nil, ZP);
	draw(i2, i2->r, i, nil, i->r.min);
	free(bits);
	freememimage(mi);
	freeimage(i);
	ci->i = i2;
	ci->mi = nil;
}

static
void
fixtext1(Item **list)
{
	Itext *text, *ntext;
	Item *it, *prev;
	Rune *s, *s1, *s2;
	int n;

	if(*list == nil)
		return;

	prev = nil;
	for(it=*list; it!=nil; it=prev->next){
		if(it->tag!=Itexttag || forceitem(it))
			goto Continue;

		text = (Itext *)it;
		s = text->s;
		while(*s && isspacerune(*s))
			s++;
		if(!*s){
			if(prev == nil)
				prev = *list = it->next;
			else
				prev->next = it->next;

			it->next = nil;
			freeitems(it);
			if(prev == nil)
				return;
			continue;
		}
		n = 0;
		while(s[n] && !isspacerune(s[n]))
			n++;

		if(!s[n])
			goto Continue;

		s1 = runemalloc(n+1);
		s1 = runemove(s1, s, n);
		s1[n] = L'\0';
		s += n;

		while(*s && isspacerune(*s))
			s++;

		if(*s){
			n = runestrlen(s);
			s2 = runemalloc(n+1);
			runemove(s2, s, n);
			s2[n] = L'\0';
			ntext = emalloc(sizeof(Itext));
			ntext->s = s2;
			ntext->ascent = text->ascent;
			ntext->anchorid = text->anchorid;
			ntext->state = text->state&~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
			ntext->tag = text->tag;
			ntext->fnt = text->fnt;
			ntext->fg = text->fg;
			ntext->ul = text->ul;
			ntext->next = (Item *)text->next;
			text->next = (Item *)ntext;
		}
		free(text->s);
		text->s = s1;
    Continue:
		prev = it;
	}
}

void
fixtext(Page *p)
{
	Tablecell *c;
	Table *t;

	fixtext1(&p->items);
	for(t=p->doc->tables; t!=nil; t=t->next)
		for(c=t->cells; c!=nil; c=c->next)
			fixtext1(&c->content);
}

typedef struct Refresh Refresh;

struct Refresh
{
	Page *p;
	Refresh *next;
};

static Refresh *refreshs = nil;
static QLock refreshlock;

void
addrefresh(Page *p, char *fmt, ...)
{
	Refresh *r;
	Rune *s;
	va_list arg;

	if(p->aborting)
		return;

	va_start(arg, fmt);
	s = runevsmprint(fmt, arg);
	va_end(arg);
	if(s == nil)
		error("runevsmprint failed");

	qlock(&refreshlock);
	if(p->status){
		free(p->status);
		p->status = nil;
	}
	p->status = s;
	for(r=refreshs; r!=nil; r=r->next)
		if(r->p == p)
			goto Return;

	incref(p->w);				/* flushrefresh will decref */
	r = emalloc(sizeof(Refresh));
	r->p = p;
	r->next = refreshs;
	refreshs = r;

    Return:
	nbsendp(crefresh, nil);
	qunlock(&refreshlock);
}

/* called while row is locked */
void
flushrefresh(void)
{
	Refresh *r, *next;
	Page *p;

	qlock(&refreshlock);
	for(r=refreshs; r!=nil; r=next){
		p = r->p;
		if(p->changed==TRUE && p->aborting==FALSE){
			p->changed = FALSE;
			if(p->parent==nil || p->loading==FALSE)
				pagerender(p);
			if(!p->refresh.t)
				pagesetrefresh(p);
		}
		if(p->status){
			winsetstatus(p->w, p->status);
			free(p->status);
			p->status = nil;
		}
		winseturl(p->w);
		winsettag(p->w);
		decref(p->w);
		next = r->next;
		free(r);
	}
	refreshs = nil;
	qunlock(&refreshlock);
}

void
savemouse(Window *w)
{
	prevmouse = mouse->xy;
	mousew = w;
}

void
restoremouse(Window *w)
{
	if(mousew!=nil && mousew==w)
		moveto(mousectl, prevmouse);
	mousew = nil;
}

void
clearmouse()
{
	mousew = nil;
}

/*
 * Heuristic city.
 */
Window*
makenewwindow(Page *p)
{
	Column *c;
	Window *w, *bigw, *emptyw;
	Page *emptyp;
	int i, y, el;

	if(activecol)
		c = activecol;
	else if(selpage && selpage->col)
		c = selpage->col;
	else if(p && p->col)
		c = p->col;
	else{
		if(row.ncol==0 && rowadd(&row, nil, -1)==nil)
			error("can't make column");
		c = row.col[row.ncol-1];
	}
	activecol = c;
	if(p==nil || p->w==nil || c->nw==0)
		return coladd(c, nil, nil, -1);

	/* find biggest window and biggest blank spot */
	emptyw = c->w[0];
	bigw = emptyw;
	for(i=1; i<c->nw; i++){
		w = c->w[i];
		/* use >= to choose one near bottom of screen */
		if(Dy(w->page.all) >= Dy(bigw->page.all))
			bigw = w;
		if(w->page.lay==nil && Dy(w->page.all) >= Dy(emptyw->page.all))
			emptyw = w;
	}
	emptyp = &emptyw->page;
	el = Dy(emptyp->all);
	/* if empty space is big, use it */
	if(el>15 || (el>3 && el>(Dy(bigw->page.all)-1)/2))
		y = emptyp->all.max.y;
	else{
		/* if this window is in column and isn't much smaller, split it */
		if(p->col==c && Dy(p->w->r)>2*Dy(bigw->r)/3)
			bigw = p->w;
		y = (bigw->r.min.y + bigw->r.max.y)/2;
	}
	w = coladd(c, nil, nil, y);
	colgrow(w->col, w, 1);
	return w;
}