textelement.c 10 KB

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