ref: b39057dffedac61fd6c028908a9aaadf4426b446
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; }