Charles.Forsyth20060303a828770d19 years agoHistory
488 lines · 10.2 KB · C
RawDownload
#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;}; staticchar*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;} staticintismore(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;}