xref: /inferno-os/libprefab/textelement.c (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
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