shithub: purgatorio

ref: c116550e6a41572796e4db65e4f6acbcb3d9d6f8
dir: /libprefab/textelement.c/

View raw version
#include <lib9.h>
#include <draw.h>
#include <interp.h>
#include <isa.h>
#include "../libinterp/runt.h"
#include <drawif.h>
#include <prefab.h>
#include <kernel.h>

typedef struct State State;

struct State
{
	Prefab_Environ	*env;
	List			*list;
	char			word[Maxchars+UTFmax];
	char			*s;
	char			*pending;
	Draw_Font	*font;
	Draw_Image	*color;
	Draw_Image	*icon;
	Draw_Image	*mask;
	String		*tag;
	Point			p;
	int			mainkind;
	int			kind;
	int			wid;
	int			newelem;
	int			ascent;
	int			descent;
};

static
char*
advword(char *s, char *word)
{
	char *e;
	int w;
	Rune r;

	e = s+Maxchars-1;
	switch(*word++ = *s){
	case '\t':		/* BUG: what to do about tabs? */
		strcpy(word-1, "    ");
		return s+1;
	case '\n':
	case ' ':
		*word = 0;
		return s+1;
	case '\0':
		return s;
	}
	s++;
	while(s<e && *s && *s!=' ' && *s!='\t' && *s!='\n'){
		if(*(uchar*)s < Runeself)
			*word++ = *s++;
		else{
			w = chartorune(&r, s);
			memmove(word, s, w);
			word += w;
			s += w;
		}
	}
	*word = 0;
	return s;
}

static
int
ismore(State *state)
{
	Prefab_Style *style;
	Prefab_Layout *lay;
	int text, icon;

	state->newelem = 0;
	if(state->kind==EIcon || (state->s && state->s[0]) || state->pending)
		return 1;
	if(state->list == H)
		return 0;
	lay = (Prefab_Layout*)state->list->data;
	text = (lay->text!=H && lay->text->len != 0);
	icon = (lay->icon!=H && lay->mask!=H);
	if(!text && !icon)
		return 0;
	state->newelem = 1;
	state->s = string2c(lay->text);
	state->font = lay->font;
	state->color = lay->color;
	state->icon = lay->icon;
	state->mask = lay->mask;
	state->tag = lay->tag;
	style = state->env->style;
	if(icon)	/* has precedence; if lay->icon is set, we ignore the text */
		state->kind = EIcon;
	else{
		if(state->mainkind == ETitle){
			if(state->font == H)
				state->font = style->titlefont;
			if(state->color == H)
				state->color = style->titlecolor;
		}else{
			if(state->font == H)
				state->font = style->textfont;
			if(state->color == H)
				state->color = style->textcolor;
		}
		state->kind = state->mainkind;
	}
	state->list = state->list->tail;
	return 1;
}

PElement*
growtext(PElement *pline, State *state, char *w, int minx, int maxx)
{
	String *s;
	PElement *pe, *plist;
	Prefab_Element *e;
	List *atom;
	Point size;
	Image *image;

	if(state->newelem || pline==H) {
		pe = mkelement(state->env, state->kind);
		e = &pe->e;
		e->r.min.x = minx;
		if(state->kind == EIcon){
			e->image = state->icon;
			D2H(e->image)->ref++;
			e->mask = state->mask;
			D2H(e->mask)->ref++;
		}else{
			e->image = state->color;
			D2H(e->image)->ref++;
			e->font = state->font;
			D2H(e->font)->ref++;
		}
		e->tag = state->tag;
		if(e->tag != H)
			D2H(e->tag)->ref++;
		if(pline == H)
			pline = pe;
		else{
			if(pline->pkind != EHorizontal){
				/* promote pline to list encapsulating current contents */
				atom = prefabwrap(pline);
				plist = mkelement(state->env, EHorizontal);
				destroy(pline);
				/* rest of plist->e.r will be set later */
				plist->e.r.min.x = state->p.x;
				plist->drawpt = state->p;
				plist->e.kids = atom;
				plist->first = atom;
				plist->last = atom;
				plist->vfirst = atom;
				plist->vlast = atom;
				pline = plist;
			}
			/* add e to line */
			atom = prefabwrap(e);
			destroy(e);	/* relevant data now in wrapper */
			e = *(Prefab_Element**)atom->data;
			pline->last->tail = atom;
			pline->last = atom;
			pline->vlast = atom;
			pline->nkids++;
		}
		state->newelem = 0;
	}else{
		pe = pline;
		if(pe->pkind == EHorizontal)
			pe = *(PElement**)pe->last->data;
		e = &pe->e;
	}

	if(state->kind == EIcon){
		/* guaranteed OK by buildine */	
		image = lookupimage(state->icon);
		size = iconsize(image);
		/* put one pixel on each side */
		e->r.max.x = e->r.min.x+1+size.x+1;
		pline->e.r.max.x = e->r.max.x;
		if(state->ascent < size.y)
			state->ascent = size.y;
		state->kind = -1;	/* consume EIcon from state */
		return pline;
	}

	e->r.max.x = maxx;
	pline->e.r.max.x = maxx;
	if(*w == '\n') {
		pline->newline = 1;
		return pline;
	}

	s = addstring(e->str, c2string(w, strlen(w)), 0);
	destroy(e->str);
	e->str = s;

	if(state->ascent < e->font->ascent)
		state->ascent = e->font->ascent;
	if(state->descent < e->font->height-e->font->ascent)
		state->descent = e->font->height-e->font->ascent;
	return pline;
}

PElement*
buildline(State *state, int *ok)
{
	int wordwid, linewid, nb, rwid, x;
	char tmp[UTFmax+1], *w, *t;
	PElement *pl, *pe;
	Rune r;
	Font *f;
	List *l;
	Image *icon;
	Point size;

	*ok = 1;
	linewid = 0;
	pl = H;
	state->ascent = 0;
	state->descent = 0;
	x = state->p.x;
	while(ismore(state)){
		f = nil;
		if(state->kind == EIcon){
			icon = lookupimage(state->icon);
			if(icon == nil){
    Error:
				destroy(pl);
				*ok = 0;
				return H;
			}
			size = iconsize(icon);
			wordwid = 1+size.x+1;
		}else{
			if(state->pending == 0){
				state->s = advword(state->s, state->word);
				state->pending = state->word;
			}
			if(*(state->pending) == '\n'){
				pl = growtext(pl, state, state->pending, x, x);
				if(pl == H){
					*ok = 0;
					return H;
				}
				state->pending = 0;
				break;
			}
			f = lookupfont(state->font);
			if(f == nil)
				goto Error;
			wordwid = stringwidth(f, state->pending);
		}
		if(linewid+wordwid<=state->wid){
    Easy:
			pl = growtext(pl, state, state->pending, x, x+wordwid);
			if(pl == H){
				*ok = 0;
				return H;
			}
			linewid += wordwid;
			state->pending = 0;
			x += wordwid;
			continue;
		}
		/* this word doesn't fit on this line */
		/* if it's white space or an icon, just generate a line break */
		if(state->word[0]==' ' || state->kind==EIcon){
			if(linewid == 0)	/* it's just too wide; emit it and it'll get clipped */
				goto Easy;
			state->pending = 0;
			break;
		}
		/* if word would fit were we to break the line now, do so */
		if(wordwid <= state->wid)
			break;
		/* worst case: bite off the biggest piece that fits */
		w = state->pending;
		while(*w){
			nb = chartorune(&r, w);
			memmove(tmp, w, nb);
			tmp[nb] = 0;
			rwid = stringwidth(f, tmp);
			if(linewid+rwid > state->wid)
				break;
			linewid += rwid;
			w += nb;
		}
		if(w == state->pending){
			/* first char too wide for remaining space */
			if(linewid > 0)
				break;
			/* remaining space is all we'll get */
			kwerrstr("can't handle wide word in textelement\n");
			goto Error;
		}
		nb = w-state->pending;
		t = malloc(nb+1);
		if(t == nil)
			goto Error;
		memmove(t, state->pending, nb);
		t[nb] = 0;
		pl = growtext(pl, state, t, x, state->p.x+linewid);
		free(t);
		if(pl == H){
			*ok = 0;
			return H;
		}
		state->pending = w;
		break;
	}
	pl->e.r.min.y = state->p.y;
	pl->e.r.max.y = state->p.y+state->ascent+state->descent;
	P2P(pl->drawpt, pl->e.r.min);
	if(pl->pkind==EHorizontal){
		for(l=pl->first; l!=H; l=l->tail){
			pe = *(PElement**)l->data;
			pe->e.r.min.y = state->p.y;
			pe->e.r.max.y = state->p.y+state->ascent+state->descent;
			pe->drawpt.x = pe->e.r.min.x;
			if(pe->e.kind == EIcon){
				/* add a pixel on the left; room was left in growtext */
				pe->drawpt.x += 1;
				pe->drawpt.y = pe->e.r.min.y+(state->ascent-Dy(pe->e.image->r));
			}else
				pe->drawpt.y = pe->e.r.min.y+(state->ascent-pe->e.font->ascent);
		}
	}
	return pl;
}

PElement*
layoutelement(Prefab_Environ *env, List *laylist, Draw_Rect rr, enum Elementtype kind)
{
	PElement *pline, *plist, *firstpline;
	List *lines, *atom, *tail;
	State state;
	int nlines, linewid, maxwid, wid, trim, maxy, ok;
	Point p;
	Rectangle r;
	Screen *screen;

	nlines = 0;
	trim = 0;
	wid = Dx(rr);
	if(wid < 25){
		if(wid <= 0)
			trim = 1;
		screen = lookupscreen(env->screen);
		if(screen == nil)
			return H;
		wid = Dx(screen->display->image->r)-32;
		if(wid < 100)
			wid = 100;
	}
	wid -= 3+3;	/* three pixels left and right */

	gchalt++;
	state.env = env;
	state.list = laylist;
	state.s = 0;
	state.pending = 0;
	state.font = H;
	state.color = H;
	state.tag = H;
	p = IPOINT(rr.min);
	p.x += 3;
	state.p = p;
	state.kind = EText;	/* anything but EIcon */
	state.mainkind = kind;
	state.wid = wid;
	lines = H;
	tail = H;
	firstpline = H;
	maxwid = 0;
	maxy = 0;
	while(ismore(&state)){
		pline = buildline(&state, &ok);
		if(ok == 0){
			plist = H;
			goto Return;
		}
		if(pline == H)
			break;
		linewid = Dx(pline->e.r);
		if(linewid > maxwid)
			maxwid = linewid;
		if(firstpline == H)
			firstpline = pline;
		else{
			atom = prefabwrap(pline);
			destroy(pline);	/* relevant data now in wrapper */
			pline = *(PElement**)atom->data;
			if(lines == H){
				lines = prefabwrap(firstpline);
				destroy(firstpline);
				firstpline = 0;	/* never used again; this proves it! */
				tail = lines;
			}
			tail->tail = atom;
			tail = atom;
		}
		nlines++;
		state.p.y = pline->e.r.max.y;
		if(maxy==0 || state.p.y<=rr.max.y)
			maxy = state.p.y;
	}
	if(trim == 0)
		maxwid = wid;
	if(nlines == 0){
		plist = H;
		goto Return;
	}
	if(nlines == 1){
		if(trim == 0){	/* restore clipping around element */
			firstpline->e.r.min.x = rr.min.x;
			firstpline->e.r.max.x = rr.min.x+3+maxwid+3;
		}
		plist = firstpline;
		goto Return;
	}
	plist = mkelement(env, EVertical);
	plist->e.r.min.x = rr.min.x;
	plist->e.r.min.y = p.y;
	plist->e.r.max.x = rr.min.x+3+maxwid+3;
	plist->e.r.max.y = (*(Prefab_Element**)tail->data)->r.max.y;
	plist->drawpt = p;
	plist->e.kids = lines;
	plist->first = lines;
	plist->last = tail;
	plist->vfirst = lines;
	plist->vlast = tail;
	plist->nkids = nlines;
	/* if asked for a fixed size and list is too long, clip */
	if(Dy(rr)>0 && rr.max.y<plist->e.r.max.y){
		R2R(r, plist->e.r);
		r.max.y = maxy;
		clipelement(&plist->e, r);
	}

Return:
	gchalt--;
	return plist;
}

/*
 * Create List with one Layout in it, using malloc instead of heap to
 * keep it out of the eyes of the garbage collector
 */
List*
listoflayout(Prefab_Style *style, String *text, int kind)
{
	List *listp;
	Prefab_Layout *layp;

	listp = malloc(sizeof(List) + TLayout->size);
	if(listp == nil)
		return H;
	listp->tail = H;
	layp = (Prefab_Layout*)listp->data;
	if(kind == EText){
		layp->font = style->textfont;
		layp->color = style->textcolor;
	}else{
		layp->font = style->titlefont;
		layp->color = style->titlecolor;
	}
	layp->text = text;
	layp->icon = H;
	layp->mask = H;
	layp->tag = H;
	return listp;
}

PElement*
textelement(Prefab_Environ *env, String *str, Draw_Rect rr, enum Elementtype kind)
{
	PElement *pe;
	List *l;

	l = listoflayout(env->style, str, kind);
	pe = layoutelement(env, l, rr, kind);
	free(l);
	return pe;
}