diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /libprefab/textelement.c | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'libprefab/textelement.c')
| -rw-r--r-- | libprefab/textelement.c | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/libprefab/textelement.c b/libprefab/textelement.c new file mode 100644 index 00000000..598b688c --- /dev/null +++ b/libprefab/textelement.c @@ -0,0 +1,488 @@ +#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; +} |
