1 #include <lib9.h>
2 #include <draw.h>
3 #include <interp.h>
4 #include <isa.h>
5 #include "../libinterp/runt.h"
6 #include <drawif.h>
7 #include <prefab.h>
8 #include <kernel.h>
9
10 typedef struct State State;
11
12 struct State
13 {
14 Prefab_Environ *env;
15 List *list;
16 char word[Maxchars+UTFmax];
17 char *s;
18 char *pending;
19 Draw_Font *font;
20 Draw_Image *color;
21 Draw_Image *icon;
22 Draw_Image *mask;
23 String *tag;
24 Point p;
25 int mainkind;
26 int kind;
27 int wid;
28 int newelem;
29 int ascent;
30 int descent;
31 };
32
33 static
34 char*
advword(char * s,char * word)35 advword(char *s, char *word)
36 {
37 char *e;
38 int w;
39 Rune r;
40
41 e = s+Maxchars-1;
42 switch(*word++ = *s){
43 case '\t': /* BUG: what to do about tabs? */
44 strcpy(word-1, " ");
45 return s+1;
46 case '\n':
47 case ' ':
48 *word = 0;
49 return s+1;
50 case '\0':
51 return s;
52 }
53 s++;
54 while(s<e && *s && *s!=' ' && *s!='\t' && *s!='\n'){
55 if(*(uchar*)s < Runeself)
56 *word++ = *s++;
57 else{
58 w = chartorune(&r, s);
59 memmove(word, s, w);
60 word += w;
61 s += w;
62 }
63 }
64 *word = 0;
65 return s;
66 }
67
68 static
69 int
ismore(State * state)70 ismore(State *state)
71 {
72 Prefab_Style *style;
73 Prefab_Layout *lay;
74 int text, icon;
75
76 state->newelem = 0;
77 if(state->kind==EIcon || (state->s && state->s[0]) || state->pending)
78 return 1;
79 if(state->list == H)
80 return 0;
81 lay = (Prefab_Layout*)state->list->data;
82 text = (lay->text!=H && lay->text->len != 0);
83 icon = (lay->icon!=H && lay->mask!=H);
84 if(!text && !icon)
85 return 0;
86 state->newelem = 1;
87 state->s = string2c(lay->text);
88 state->font = lay->font;
89 state->color = lay->color;
90 state->icon = lay->icon;
91 state->mask = lay->mask;
92 state->tag = lay->tag;
93 style = state->env->style;
94 if(icon) /* has precedence; if lay->icon is set, we ignore the text */
95 state->kind = EIcon;
96 else{
97 if(state->mainkind == ETitle){
98 if(state->font == H)
99 state->font = style->titlefont;
100 if(state->color == H)
101 state->color = style->titlecolor;
102 }else{
103 if(state->font == H)
104 state->font = style->textfont;
105 if(state->color == H)
106 state->color = style->textcolor;
107 }
108 state->kind = state->mainkind;
109 }
110 state->list = state->list->tail;
111 return 1;
112 }
113
114 PElement*
growtext(PElement * pline,State * state,char * w,int minx,int maxx)115 growtext(PElement *pline, State *state, char *w, int minx, int maxx)
116 {
117 String *s;
118 PElement *pe, *plist;
119 Prefab_Element *e;
120 List *atom;
121 Point size;
122 Image *image;
123
124 if(state->newelem || pline==H) {
125 pe = mkelement(state->env, state->kind);
126 e = &pe->e;
127 e->r.min.x = minx;
128 if(state->kind == EIcon){
129 e->image = state->icon;
130 D2H(e->image)->ref++;
131 e->mask = state->mask;
132 D2H(e->mask)->ref++;
133 }else{
134 e->image = state->color;
135 D2H(e->image)->ref++;
136 e->font = state->font;
137 D2H(e->font)->ref++;
138 }
139 e->tag = state->tag;
140 if(e->tag != H)
141 D2H(e->tag)->ref++;
142 if(pline == H)
143 pline = pe;
144 else{
145 if(pline->pkind != EHorizontal){
146 /* promote pline to list encapsulating current contents */
147 atom = prefabwrap(pline);
148 plist = mkelement(state->env, EHorizontal);
149 destroy(pline);
150 /* rest of plist->e.r will be set later */
151 plist->e.r.min.x = state->p.x;
152 plist->drawpt = state->p;
153 plist->e.kids = atom;
154 plist->first = atom;
155 plist->last = atom;
156 plist->vfirst = atom;
157 plist->vlast = atom;
158 pline = plist;
159 }
160 /* add e to line */
161 atom = prefabwrap(e);
162 destroy(e); /* relevant data now in wrapper */
163 e = *(Prefab_Element**)atom->data;
164 pline->last->tail = atom;
165 pline->last = atom;
166 pline->vlast = atom;
167 pline->nkids++;
168 }
169 state->newelem = 0;
170 }else{
171 pe = pline;
172 if(pe->pkind == EHorizontal)
173 pe = *(PElement**)pe->last->data;
174 e = &pe->e;
175 }
176
177 if(state->kind == EIcon){
178 /* guaranteed OK by buildine */
179 image = lookupimage(state->icon);
180 size = iconsize(image);
181 /* put one pixel on each side */
182 e->r.max.x = e->r.min.x+1+size.x+1;
183 pline->e.r.max.x = e->r.max.x;
184 if(state->ascent < size.y)
185 state->ascent = size.y;
186 state->kind = -1; /* consume EIcon from state */
187 return pline;
188 }
189
190 e->r.max.x = maxx;
191 pline->e.r.max.x = maxx;
192 if(*w == '\n') {
193 pline->newline = 1;
194 return pline;
195 }
196
197 s = addstring(e->str, c2string(w, strlen(w)), 0);
198 destroy(e->str);
199 e->str = s;
200
201 if(state->ascent < e->font->ascent)
202 state->ascent = e->font->ascent;
203 if(state->descent < e->font->height-e->font->ascent)
204 state->descent = e->font->height-e->font->ascent;
205 return pline;
206 }
207
208 PElement*
buildline(State * state,int * ok)209 buildline(State *state, int *ok)
210 {
211 int wordwid, linewid, nb, rwid, x;
212 char tmp[UTFmax+1], *w, *t;
213 PElement *pl, *pe;
214 Rune r;
215 Font *f;
216 List *l;
217 Image *icon;
218 Point size;
219
220 *ok = 1;
221 linewid = 0;
222 pl = H;
223 state->ascent = 0;
224 state->descent = 0;
225 x = state->p.x;
226 while(ismore(state)){
227 f = nil;
228 if(state->kind == EIcon){
229 icon = lookupimage(state->icon);
230 if(icon == nil){
231 Error:
232 destroy(pl);
233 *ok = 0;
234 return H;
235 }
236 size = iconsize(icon);
237 wordwid = 1+size.x+1;
238 }else{
239 if(state->pending == 0){
240 state->s = advword(state->s, state->word);
241 state->pending = state->word;
242 }
243 if(*(state->pending) == '\n'){
244 pl = growtext(pl, state, state->pending, x, x);
245 if(pl == H){
246 *ok = 0;
247 return H;
248 }
249 state->pending = 0;
250 break;
251 }
252 f = lookupfont(state->font);
253 if(f == nil)
254 goto Error;
255 wordwid = stringwidth(f, state->pending);
256 }
257 if(linewid+wordwid<=state->wid){
258 Easy:
259 pl = growtext(pl, state, state->pending, x, x+wordwid);
260 if(pl == H){
261 *ok = 0;
262 return H;
263 }
264 linewid += wordwid;
265 state->pending = 0;
266 x += wordwid;
267 continue;
268 }
269 /* this word doesn't fit on this line */
270 /* if it's white space or an icon, just generate a line break */
271 if(state->word[0]==' ' || state->kind==EIcon){
272 if(linewid == 0) /* it's just too wide; emit it and it'll get clipped */
273 goto Easy;
274 state->pending = 0;
275 break;
276 }
277 /* if word would fit were we to break the line now, do so */
278 if(wordwid <= state->wid)
279 break;
280 /* worst case: bite off the biggest piece that fits */
281 w = state->pending;
282 while(*w){
283 nb = chartorune(&r, w);
284 memmove(tmp, w, nb);
285 tmp[nb] = 0;
286 rwid = stringwidth(f, tmp);
287 if(linewid+rwid > state->wid)
288 break;
289 linewid += rwid;
290 w += nb;
291 }
292 if(w == state->pending){
293 /* first char too wide for remaining space */
294 if(linewid > 0)
295 break;
296 /* remaining space is all we'll get */
297 kwerrstr("can't handle wide word in textelement\n");
298 goto Error;
299 }
300 nb = w-state->pending;
301 t = malloc(nb+1);
302 if(t == nil)
303 goto Error;
304 memmove(t, state->pending, nb);
305 t[nb] = 0;
306 pl = growtext(pl, state, t, x, state->p.x+linewid);
307 free(t);
308 if(pl == H){
309 *ok = 0;
310 return H;
311 }
312 state->pending = w;
313 break;
314 }
315 pl->e.r.min.y = state->p.y;
316 pl->e.r.max.y = state->p.y+state->ascent+state->descent;
317 P2P(pl->drawpt, pl->e.r.min);
318 if(pl->pkind==EHorizontal){
319 for(l=pl->first; l!=H; l=l->tail){
320 pe = *(PElement**)l->data;
321 pe->e.r.min.y = state->p.y;
322 pe->e.r.max.y = state->p.y+state->ascent+state->descent;
323 pe->drawpt.x = pe->e.r.min.x;
324 if(pe->e.kind == EIcon){
325 /* add a pixel on the left; room was left in growtext */
326 pe->drawpt.x += 1;
327 pe->drawpt.y = pe->e.r.min.y+(state->ascent-Dy(pe->e.image->r));
328 }else
329 pe->drawpt.y = pe->e.r.min.y+(state->ascent-pe->e.font->ascent);
330 }
331 }
332 return pl;
333 }
334
335 PElement*
layoutelement(Prefab_Environ * env,List * laylist,Draw_Rect rr,enum Elementtype kind)336 layoutelement(Prefab_Environ *env, List *laylist, Draw_Rect rr, enum Elementtype kind)
337 {
338 PElement *pline, *plist, *firstpline;
339 List *lines, *atom, *tail;
340 State state;
341 int nlines, linewid, maxwid, wid, trim, maxy, ok;
342 Point p;
343 Rectangle r;
344 Screen *screen;
345
346 nlines = 0;
347 trim = 0;
348 wid = Dx(rr);
349 if(wid < 25){
350 if(wid <= 0)
351 trim = 1;
352 screen = lookupscreen(env->screen);
353 if(screen == nil)
354 return H;
355 wid = Dx(screen->display->image->r)-32;
356 if(wid < 100)
357 wid = 100;
358 }
359 wid -= 3+3; /* three pixels left and right */
360
361 gchalt++;
362 state.env = env;
363 state.list = laylist;
364 state.s = 0;
365 state.pending = 0;
366 state.font = H;
367 state.color = H;
368 state.tag = H;
369 p = IPOINT(rr.min);
370 p.x += 3;
371 state.p = p;
372 state.kind = EText; /* anything but EIcon */
373 state.mainkind = kind;
374 state.wid = wid;
375 lines = H;
376 tail = H;
377 firstpline = H;
378 maxwid = 0;
379 maxy = 0;
380 while(ismore(&state)){
381 pline = buildline(&state, &ok);
382 if(ok == 0){
383 plist = H;
384 goto Return;
385 }
386 if(pline == H)
387 break;
388 linewid = Dx(pline->e.r);
389 if(linewid > maxwid)
390 maxwid = linewid;
391 if(firstpline == H)
392 firstpline = pline;
393 else{
394 atom = prefabwrap(pline);
395 destroy(pline); /* relevant data now in wrapper */
396 pline = *(PElement**)atom->data;
397 if(lines == H){
398 lines = prefabwrap(firstpline);
399 destroy(firstpline);
400 firstpline = 0; /* never used again; this proves it! */
401 tail = lines;
402 }
403 tail->tail = atom;
404 tail = atom;
405 }
406 nlines++;
407 state.p.y = pline->e.r.max.y;
408 if(maxy==0 || state.p.y<=rr.max.y)
409 maxy = state.p.y;
410 }
411 if(trim == 0)
412 maxwid = wid;
413 if(nlines == 0){
414 plist = H;
415 goto Return;
416 }
417 if(nlines == 1){
418 if(trim == 0){ /* restore clipping around element */
419 firstpline->e.r.min.x = rr.min.x;
420 firstpline->e.r.max.x = rr.min.x+3+maxwid+3;
421 }
422 plist = firstpline;
423 goto Return;
424 }
425 plist = mkelement(env, EVertical);
426 plist->e.r.min.x = rr.min.x;
427 plist->e.r.min.y = p.y;
428 plist->e.r.max.x = rr.min.x+3+maxwid+3;
429 plist->e.r.max.y = (*(Prefab_Element**)tail->data)->r.max.y;
430 plist->drawpt = p;
431 plist->e.kids = lines;
432 plist->first = lines;
433 plist->last = tail;
434 plist->vfirst = lines;
435 plist->vlast = tail;
436 plist->nkids = nlines;
437 /* if asked for a fixed size and list is too long, clip */
438 if(Dy(rr)>0 && rr.max.y<plist->e.r.max.y){
439 R2R(r, plist->e.r);
440 r.max.y = maxy;
441 clipelement(&plist->e, r);
442 }
443
444 Return:
445 gchalt--;
446 return plist;
447 }
448
449 /*
450 * Create List with one Layout in it, using malloc instead of heap to
451 * keep it out of the eyes of the garbage collector
452 */
453 List*
listoflayout(Prefab_Style * style,String * text,int kind)454 listoflayout(Prefab_Style *style, String *text, int kind)
455 {
456 List *listp;
457 Prefab_Layout *layp;
458
459 listp = malloc(sizeof(List) + TLayout->size);
460 if(listp == nil)
461 return H;
462 listp->tail = H;
463 layp = (Prefab_Layout*)listp->data;
464 if(kind == EText){
465 layp->font = style->textfont;
466 layp->color = style->textcolor;
467 }else{
468 layp->font = style->titlefont;
469 layp->color = style->titlecolor;
470 }
471 layp->text = text;
472 layp->icon = H;
473 layp->mask = H;
474 layp->tag = H;
475 return listp;
476 }
477
478 PElement*
textelement(Prefab_Environ * env,String * str,Draw_Rect rr,enum Elementtype kind)479 textelement(Prefab_Environ *env, String *str, Draw_Rect rr, enum Elementtype kind)
480 {
481 PElement *pe;
482 List *l;
483
484 l = listoflayout(env->style, str, kind);
485 pe = layoutelement(env, l, rr, kind);
486 free(l);
487 return pe;
488 }
489