ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /libprefab/textelement.c/
#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;
}