1 /*
2 * Copyright 2008-2020, Björn Ståhl
3 * License: 3-Clause BSD, see COPYING file in arcan source repository.
4 * Reference: http://arcan-fe.com
5 */
6
7 #include <stdint.h>
8 #include <stdbool.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <string.h>
13 #include <fcntl.h>
14 #include <sys/types.h>
15 #include <stddef.h>
16 #include <math.h>
17 #include <unistd.h>
18 #include <assert.h>
19 #include <errno.h>
20
21 #ifndef ARCAN_FONT_CACHE_LIMIT
22 #define ARCAN_FONT_CACHE_LIMIT 8
23 #endif
24
25 #define ARCAN_TTF
26
27 #include "arcan_math.h"
28 #include "arcan_general.h"
29 #include "arcan_video.h"
30 #include "arcan_videoint.h"
31 #include "arcan_ttf.h"
32 #include "arcan_shmif.h"
33
34 #define shmif_pixel av_pixel
35 #define TTF_Font TTF_Font
36 #define NO_ARCAN_SHMIF
37 #include "../shmif/tui/raster/pixelfont.h"
38 #include "../shmif/tui/raster/raster.h"
39 #undef TTF_Font
40
41 #include "arcan_renderfun.h"
42 #include "arcan_img.h"
43
44 #define STB_IMAGE_RESIZE_IMPLEMENTATION
45 #define STBIR_MALLOC(sz,ctx) arcan_alloc_mem(sz, ARCAN_MEM_VBUFFER, \
46 ARCAN_MEM_NONFATAL, ARCAN_MEMALIGN_PAGE)
47 #define STBIR_FREE(ptr,ctx) arcan_mem_free(ptr)
48 #include "external/stb_image_resize.h"
49
50 struct font_entry_chain {
51 TTF_Font* data[4];
52 file_handle fd[4];
53 size_t count;
54 };
55
56 struct font_entry {
57 struct font_entry_chain chain;
58 char* identifier;
59 size_t size;
60 float vdpi, hdpi;
61 uint8_t usecount;
62 };
63
64 struct text_format {
65 /* font specification */
66 struct font_entry* font;
67 uint8_t col[4];
68 int style;
69 uint8_t alpha;
70
71 /* used in the fallback case when !font */
72 size_t pt_size;
73 size_t px_skip;
74
75 /* for forced loading of images */
76 struct {
77 size_t w, h;
78 av_pixel* buf;
79 } surf;
80 img_cons imgcons;
81
82 /* temporary pointer-alias into line of text where the format was extracted */
83 char* endofs;
84
85 /* metrics */
86 int lineheight;
87 int height;
88 int skip;
89 int ascent;
90 int descent;
91
92 /* metric- overrides */
93 size_t halign;
94
95 /* whitespace management */
96 bool cr;
97 uint8_t tab;
98 uint8_t newline;
99 };
100
101 static int default_hint = TTF_HINTING_NORMAL;
102 static float default_vdpi = 72.0;
103 static float default_hdpi = 72.0;
104
105 /* for embedded blit */
106 static int64_t vid_ofs;
107
108 #define PT_TO_HPX(PT)((float)(PT) * (1.0f / 72.0f) * default_hdpi)
109 #define PT_TO_VPX(PT)((float)(PT) * (1.0f / 72.0f) * default_vdpi)
110
111 static struct text_format last_style = {
112 .col = {0xff, 0xff, 0xff, 0xff},
113 };
114
115 static unsigned int font_cache_size = ARCAN_FONT_CACHE_LIMIT;
116 static struct tui_font builtin_bitmap;
117 static struct font_entry font_cache[ARCAN_FONT_CACHE_LIMIT] = {
118 };
119
nexthigher(uint16_t k)120 static uint16_t nexthigher(uint16_t k)
121 {
122 k--;
123 for (int i=1; i < sizeof(uint16_t) * 8; i = i * 2)
124 k = k | k >> i;
125 return k+1;
126 }
127
128 /*
129 * This one is a mess,
130 * (a) begin by splitting the input string into a linked list of data elements.
131 * each element can EITHER modfiy the current cursor position OR represent
132 * a rendered surface.
133 * (b) take the linked list, sweep through it and figure out which dimensions
134 * it requires, allocate a corresponding storage object.
135 * (c) sweep the list yet again, render to the storage object.
136 */
137
138 /* TTF_FontHeight sets line-spacing. */
139
140 struct rcell {
141 unsigned int width;
142 unsigned int height;
143 int skipv;
144 int ascent;
145 int descent;
146
147 union {
148 struct {
149 size_t w, h;
150 av_pixel* buf;
151 } surf;
152
153 struct {
154 uint8_t newline;
155 uint8_t tab;
156 bool cr;
157 } format;
158 } data;
159
160 struct rcell* next;
161 };
162
arcan_video_fontdefaults(file_handle * fd,int * pt_sz,int * hint)163 void arcan_video_fontdefaults(file_handle* fd, int* pt_sz, int* hint)
164 {
165 if (fd)
166 *fd = font_cache[0].chain.fd[0];
167
168 if (pt_sz)
169 *pt_sz = font_cache[0].size;
170
171 if (hint)
172 *hint = default_hint;
173 }
174
arcan_renderfun_outputdensity(float hppcm,float vppcm)175 void arcan_renderfun_outputdensity(float hppcm, float vppcm)
176 {
177 default_hdpi = vppcm > EPSILON ? 2.54 * vppcm : 72.0;
178 default_vdpi = hppcm > EPSILON ? 2.54 * hppcm : 72.0;
179 }
180
arcan_renderfun_vidoffset(int64_t ofs)181 void arcan_renderfun_vidoffset(int64_t ofs)
182 {
183 vid_ofs = ofs;
184 }
185
186 /* chain functions work like normal, except that they take multiple
187 * fonts and select / scale a fallback if a glyph is not found. */
size_font_chain(struct text_format * cstyle,const char * base,int * w,int * h)188 int size_font_chain(
189 struct text_format* cstyle, const char* base, int *w, int *h)
190 {
191 if (cstyle->font && cstyle->font->chain.data[0]){
192 return TTF_SizeUTF8chain(cstyle->font->chain.data,
193 cstyle->font->chain.count, base, w, h, cstyle->style);
194 }
195 else {
196 size_t len = strlen(base);
197 *h = cstyle->height;
198 *w = cstyle->px_skip * len;
199 return 0;
200 }
201 }
202
update_style(struct text_format * dst,struct font_entry * font)203 static void update_style(struct text_format* dst, struct font_entry* font)
204 {
205 /* if the font has not been set, use the built-in bitmap one - go from pt-size
206 * over dpi to pixel size to whatever the closest built-in match is */
207 if (!font || !font->chain.data[0]){
208 size_t h = 0;
209 size_t px_sz = PT_TO_HPX(dst->pt_size);
210 tui_pixelfont_setsz(builtin_bitmap.bitmap, px_sz, &dst->px_skip, &h);
211 dst->descent = dst->ascent = h >> 1;
212 dst->height = h;
213 dst->skip = (float)px_sz * 1.5 - px_sz;
214 if (!dst->skip)
215 dst->skip = 1;
216 dst->font = NULL;
217 return;
218 }
219
220 /* otherwise query the font for the specific metrics */
221 dst->ascent = TTF_FontAscent(font->chain.data[0]);
222 dst->descent = -1 * TTF_FontDescent(font->chain.data[0]);
223 dst->height = TTF_FontHeight(font->chain.data[0]);
224 dst->skip = dst->height - TTF_FontLineSkip(font->chain.data[0]);
225 dst->font = font;
226 }
227
zap_slot(int i)228 static void zap_slot(int i)
229 {
230 for (size_t j = 0; j < font_cache[i].chain.count; j++){
231 if (font_cache[i].chain.fd[j] != BADFD){
232 close(font_cache[i].chain.fd[j]);
233 font_cache[i].chain.fd[j] = BADFD;
234 }
235
236 if (font_cache[i].chain.data[j])
237 TTF_CloseFont(font_cache[i].chain.data[j]);
238 }
239 free(font_cache[i].identifier);
240 memset(&font_cache[i], '\0', sizeof(font_cache[0]));
241 }
242
set_style(struct text_format * dst,struct font_entry * font)243 static void set_style(struct text_format* dst, struct font_entry* font)
244 {
245 dst->newline = dst->tab = dst->cr = 0;
246 dst->col[0] = dst->col[1] = dst->col[2] = dst->col[3] = 0xff;
247
248 /* if no font is specified, we fallback to the builtin- bitmap one */
249 update_style(dst, font);
250 }
251
grab_font(const char * fname,size_t size)252 static struct font_entry* grab_font(const char* fname, size_t size)
253 {
254 int leasti = 1, i, leastv = -1;
255 struct font_entry* font;
256
257 /* empty identifier - use default (slot 0) */
258 if (!fname){
259 fname = font_cache[0].identifier;
260
261 if (!fname)
262 return NULL;
263 }
264 /* special case, set default slot to loaded font */
265 else if (!font_cache[0].identifier){
266 int fd = open(fname, O_RDONLY);
267 if (BADFD == fd)
268 return NULL;
269
270 if (!arcan_video_defaultfont(fname, fd, size, 2, false))
271 close(fd);
272 }
273
274 /* match / track */
275 struct font_entry* matchf = NULL;
276 for (i = 0; i < font_cache_size && font_cache[i].chain.data[0] != NULL; i++){
277 if (i && font_cache[i].usecount < leastv &&
278 &font_cache[i] != last_style.font){
279 leasti = i;
280 leastv = font_cache[i].usecount;
281 }
282
283 if (strcmp(font_cache[i].identifier, fname) == 0){
284 if (font_cache[i].chain.fd[0] != BADFD){
285 matchf = &font_cache[i];
286 }
287
288 if (font_cache[i].size == size &&
289 fabs(font_cache[i].vdpi - default_vdpi) < EPSILON &&
290 fabs(font_cache[i].hdpi - default_hdpi) < EPSILON){
291 font_cache[i].usecount++;
292 font = &font_cache[i];
293 goto done;
294 }
295 }
296 }
297
298 /* we have an edge case here - there are fallback slots defined for the font
299 * that was found but had the wrong size so we need to rebuild the entire
300 * chain. Also note that not all (!default) slots have a font that is derived
301 * from an accessible file descriptor. */
302 struct font_entry_chain newch = {};
303
304 if (matchf){
305 if (i == font_cache_size){
306 i = leasti;
307 }
308 int count = 0;
309 for (size_t i = 0; i < matchf->chain.count; i++){
310 newch.data[count] = TTF_OpenFontFD(
311 matchf->chain.fd[i], size, default_hdpi, default_vdpi);
312 newch.fd[count] = BADFD;
313 if (!newch.data[count]){
314 arcan_warning("grab font(), couldn't duplicate entire "
315 "fallback chain (fail @ ind %d)\n", i);
316 }
317 else
318 count++;
319 }
320 newch.count = count;
321 }
322 else {
323 newch.data[0] = TTF_OpenFont(fname, size, default_hdpi, default_vdpi);
324 newch.fd[0] = BADFD;
325 if (newch.data[0])
326 newch.count = 1;
327 }
328
329 if (newch.count == 0){
330 arcan_warning("grab_font(), Open Font (%s,%d) failed\n", fname, size);
331 return NULL;
332 }
333
334 /* replace? */
335 if (i == font_cache_size){
336 i = leasti;
337 zap_slot(i);
338 }
339
340 /* update counters */
341 font_cache[i].identifier = strdup(fname);
342 font_cache[i].usecount++;
343 font_cache[i].size = size;
344 font_cache[i].vdpi = default_vdpi;
345 font_cache[i].hdpi = default_hdpi;
346 font_cache[i].chain = newch;
347 font = &font_cache[i];
348
349 done:
350 for (size_t i=0; i < font->chain.count; i++)
351 TTF_SetFontHinting(font->chain.data[i], default_hint);
352 return font;
353 }
354
arcan_video_defaultfont(const char * ident,file_handle fd,int sz,int hint,bool append)355 bool arcan_video_defaultfont(const char* ident,
356 file_handle fd, int sz, int hint, bool append)
357 {
358 if (BADFD == fd)
359 return false;
360
361 /* try to load */
362 TTF_Font* font = TTF_OpenFontFD(fd, sz, default_hdpi, default_vdpi);
363 if (!font)
364 return false;
365
366 if (-1 != default_hint){
367 default_hint = hint;
368 }
369
370 if (!append){
371 zap_slot(0);
372 font_cache[0].identifier = strdup(ident);
373 font_cache[0].size = sz;
374 font_cache[0].chain.data[0] = font;
375 font_cache[0].chain.fd[0] = fd;
376 font_cache[0].chain.count = 1;
377 set_style(&last_style, &font_cache[0]);
378 }
379 else{
380 int dst_i = font_cache[0].chain.count;
381 size_t lim = COUNT_OF(font_cache[0].chain.data);
382 if (dst_i == lim){
383 close(font_cache[0].chain.fd[dst_i-1]);
384 TTF_CloseFont(font_cache[0].chain.data[dst_i-1]);
385 }
386 else
387 dst_i++;
388
389 font_cache[0].chain.count = dst_i;
390 font_cache[0].chain.fd[dst_i-1] = fd;
391 font_cache[0].chain.data[dst_i-1] = font;
392 }
393
394 return true;
395 }
396
arcan_video_reset_fontcache()397 void arcan_video_reset_fontcache()
398 {
399 static bool init;
400
401 /* only first time, builtin- bitmap font will match application lifespan */
402 if (!init){
403 init = true;
404 builtin_bitmap.bitmap = tui_pixelfont_open(64);
405 for (int i = 0; i < ARCAN_FONT_CACHE_LIMIT; i++)
406 font_cache[i].chain.fd[0] = BADFD;
407 }
408 else{
409 for (int i = 0; i < ARCAN_FONT_CACHE_LIMIT; i++)
410 zap_slot(i);
411 }
412 }
413
414 #ifndef TEXT_EMBEDDEDICON_MAXW
415 #define TEXT_EMBEDDEDICON_MAXW 256
416 #endif
417
418 #ifndef TEXT_EMBEDDEDICON_MAXH
419 #define TEXT_EMBEDDEDICON_MAXH 256
420 #endif
421
text_loadimage(struct text_format * dst,const char * const infn,img_cons cons)422 static void text_loadimage(struct text_format* dst,
423 const char* const infn, img_cons cons)
424 {
425 char* path = arcan_find_resource(infn,
426 RESOURCE_SYS_FONT | RESOURCE_APPL_SHARED | RESOURCE_APPL, ARES_FILE);
427
428 data_source inres = arcan_open_resource(path);
429
430 free(path);
431 if (inres.fd == BADFD)
432 return;
433
434 map_region inmem = arcan_map_resource(&inres, false);
435 if (inmem.ptr == NULL){
436 arcan_release_resource(&inres);
437 return;
438 }
439
440 struct arcan_img_meta meta = {0};
441 uint32_t* imgbuf; /* set in _decode */
442 size_t inw, inh;
443
444 arcan_errc rv = arcan_img_decode(infn, inmem.ptr,
445 inmem.sz, &imgbuf, &inw, &inh, &meta, false);
446
447 arcan_release_map(inmem);
448 arcan_release_resource(&inres);
449
450 /* repack if the system format doesn't match */
451 if (imgbuf)
452 imgbuf = arcan_img_repack(imgbuf, inw, inh);
453
454 if (!imgbuf || rv != ARCAN_OK)
455 return;
456
457 if (cons.w > TEXT_EMBEDDEDICON_MAXW ||
458 (cons.w == 0 && inw > TEXT_EMBEDDEDICON_MAXW))
459 cons.w = TEXT_EMBEDDEDICON_MAXW;
460
461 if (cons.h > TEXT_EMBEDDEDICON_MAXH ||
462 (cons.h == 0 && inh > TEXT_EMBEDDEDICON_MAXH))
463 cons.h = TEXT_EMBEDDEDICON_MAXH;
464
465 /* if blit to a specific size is requested, use that */
466 if ((cons.w != 0 && cons.h != 0) && (inw != cons.w || inh != cons.h)){
467 dst->surf.buf = arcan_alloc_mem(cons.w * cons.h * sizeof(av_pixel),
468 ARCAN_MEM_VBUFFER, ARCAN_MEM_NONFATAL, ARCAN_MEMALIGN_PAGE);
469 arcan_renderfun_stretchblit(
470 (char*)imgbuf, inw, inh, dst->surf.buf, cons.w, cons.h, false);
471 arcan_mem_free(imgbuf);
472 dst->surf.w = cons.w;
473 dst->surf.h = cons.h;
474 }
475 /* otherwise just keep the entire buffer */
476 else {
477 dst->surf.w = inw;
478 dst->surf.h = inh;
479 dst->surf.buf = imgbuf;
480 }
481 }
482
extract_color(struct text_format * prev,char * base)483 static char* extract_color(struct text_format* prev, char* base){
484 char cbuf[3];
485
486 /* scan 6 characters to the right, check for valid hex */
487 for (int i = 0; i < 6; i++) {
488 if (!isxdigit(*base++)){
489 arcan_warning("arcan_video_renderstring(),"
490 "couldn't scan font colour directive (#rrggbb, 0-9, a-f)\n");
491 return NULL;
492 }
493 }
494
495 /* now we know 6 valid chars are there, time to collect. */
496 cbuf[0] = *(base - 6); cbuf[1] = *(base - 5); cbuf[2] = 0;
497 prev->col[0] = strtol(cbuf, 0, 16);
498
499 cbuf[0] = *(base - 4); cbuf[1] = *(base - 3); cbuf[2] = 0;
500 prev->col[1] = strtol(cbuf, 0, 16);
501
502 cbuf[0] = *(base - 2); cbuf[1] = *(base - 1); cbuf[2] = 0;
503 prev->col[2] = strtol(cbuf, 0, 16);
504
505 return base;
506 }
507
extract_font(struct text_format * prev,char * base)508 static char* extract_font(struct text_format* prev, char* base){
509 char* fontbase = base, (* numbase), (* orig) = base;
510
511 int relsign = 0;
512 /* find fontname vs fontsize separator */
513 while (*base != ',') {
514 if (*base == 0) {
515 arcan_warning("arcan_video_renderstring(), couldn't scan font "
516 "directive '%s (%s)'\n", fontbase, orig);
517 return NULL;
518 }
519 base++;
520 }
521 *base++ = 0;
522
523 if (*base == '+'){
524 relsign = 1;
525 base++;
526 }
527 else if (*base == '-'){
528 relsign = -1;
529 base++;
530 }
531
532 /* fontbase points to full fontname, find the size */
533 numbase = base;
534 while (*base != 0 && isdigit(*base))
535 base++;
536
537 /* error state, no size specifier */
538 if (numbase == base){
539 arcan_warning("arcan_video_renderstring(), missing size argument "
540 "in font specification (%s).\n", orig);
541 return base;
542 }
543
544 char ch = *base;
545 *base = 0;
546
547 /* we allow a 'default font size' \f,+n or \f,-n where n is relative
548 * to the default set font and size */
549 struct font_entry* font = NULL;
550 int font_sz = strtoul(numbase, NULL, 10);
551 if (relsign != 0 || font_sz == 0)
552 font_sz = font_cache[0].size + relsign * font_sz;
553
554 /* but also force a sane default if that becomes problematic, 9 pt is
555 * a common UI element standard font size */
556 if (!font_sz)
557 font_sz = 9;
558
559 /*
560 * use current 'default-font' if just size is provided
561 */
562 if (*fontbase == '\0'){
563 font = grab_font(NULL, font_sz);
564 *base = ch;
565 prev->pt_size = font_sz;
566 update_style(prev, font);
567 return base;
568 }
569
570 /*
571 * SECURITY NOTE, TTF is a complex format with a rich history of decoding
572 * vulnerabilities. To lessen this we use a specific namespace for fonts.
573 * This is currently not strongly enforced as it will break some older
574 * applications.
575 */
576 char* fname = arcan_find_resource(fontbase,
577 RESOURCE_SYS_FONT | RESOURCE_APPL_SHARED | RESOURCE_APPL, ARES_FILE);
578
579 if (!fname)
580 arcan_warning("arcan_video_renderstring(), couldn't find "
581 "font (%s) (%s)\n", fontbase, orig);
582 else if (!font && !(font = grab_font(fname, font_sz)))
583 arcan_warning("arcan_video_renderstring(), couldn't load "
584 "font (%s) (%s), (%d)\n", fname, orig, font_sz);
585 else
586 update_style(prev, font);
587
588 arcan_mem_free(fname);
589 *base = ch;
590 return base;
591 }
592
getnum(char ** base,unsigned long * dst)593 static bool getnum(char** base, unsigned long* dst)
594 {
595 char* wbase = *base;
596
597 while(**base && isdigit(**base))
598 (*base)++;
599
600 if (strlen(wbase) == 0)
601 return false;
602
603 char ch = **base;
604 **base = '\0';
605 *dst = strtoul(wbase, NULL, 10);
606 **base = ch;
607 (*base)++;
608 return true;
609 }
610
extract_vidref(struct text_format * prev,char * base,bool ext)611 static char* extract_vidref(struct text_format* prev, char* base, bool ext)
612 {
613 unsigned long vid;
614 if (!getnum(&base, &vid)){
615 arcan_warning("arcan_video_renderstring(\\evid), missing vid-ref\n");
616 return NULL;
617 }
618
619 arcan_vobject* vobj = arcan_video_getobject(vid - vid_ofs);
620 if (!vobj){
621 arcan_warning(
622 "arcan_video_renderstring(\\evid), missing or bad vid-ref (%lu)\n", vid);
623 return NULL;
624 }
625
626 struct agp_vstore* vs = vobj->vstore;
627 if (vs->txmapped != TXSTATE_TEX2D){
628 arcan_warning(
629 "arcan_video_renderstring(\\evid), invalid backing store for vid-ref\n");
630 return NULL;
631 }
632
633 /* get w, h */
634 unsigned long w, h;
635 if (!getnum(&base, &w) || !getnum(&base, &h)){
636 arcan_warning("arcan_video_renderstring(\\evid,w,h) couldn't get dimension\n");
637 return NULL;
638 }
639
640 /* if ext, also get x1, y1, x2, y2 */
641 unsigned long x1 = 0, y1 = 0, x2 = 0, y2 = 0;
642 if (ext){
643 if (
644 !getnum(&base, &x1) || !getnum(&base, &y1) ||
645 !getnum(&base, &x2) || !getnum(&base, &y2)){
646 arcan_warning("arcan_video_renderstring("
647 "\\E,w,h,x1,y1,x2,y2 - couldn't get dimensions\n");
648 return NULL;
649 }
650 }
651
652 /* no local copy? readback - the 'nice' thing here for memory conservative use
653 * would be to drop the cpu- local copy as well, but for something like an icon
654 * blit-copy-cache, it might not be worthwhile */
655 if (!vs->vinf.text.raw){
656 agp_readback_synchronous(vs);
657 if (!vs->vinf.text.raw){
658 arcan_warning("arcan_video_renderstring(), couldn't synch vid-ref store\n");
659 return NULL;
660 }
661 }
662
663 size_t dw = w, dh = h;
664 if (w > TEXT_EMBEDDEDICON_MAXW ||
665 (w == 0 && vs->w > TEXT_EMBEDDEDICON_MAXW))
666 dw = TEXT_EMBEDDEDICON_MAXW;
667 else if (w == 0)
668 dw = vs->w;
669
670 if (h > TEXT_EMBEDDEDICON_MAXH ||
671 (h == 0 && vs->h > TEXT_EMBEDDEDICON_MAXH))
672 dh = TEXT_EMBEDDEDICON_MAXH;
673 else if (h == 0)
674 dh = vs->h;
675
676 unsigned char* inbuf = (unsigned char*) vs->vinf.text.raw;
677
678 /* stretch + copy or just copy */
679 size_t dsz = dw * dh * sizeof(av_pixel);
680 prev->surf.buf = arcan_alloc_mem(dsz,
681 ARCAN_MEM_VBUFFER, ARCAN_MEM_NONFATAL, ARCAN_MEMALIGN_PAGE);
682 if (!prev->surf.buf)
683 return NULL;
684
685 if (!ext && dw == vs->w && dh == vs->h){
686 memcpy(prev->surf.buf, vs->vinf.text.raw, dsz);
687 prev->surf.w = dw;
688 prev->surf.h = dh;
689 return base;
690 }
691
692 /* adjust offsets to match x1/y1/x2/y2 */
693 size_t stride = 0;
694 if (ext){
695 /* but first clamp */
696 if (x1 > x2)
697 x1 = 0;
698 if (y1 > y2)
699 y1 = 0;
700 if (y2 > vs->h || y2 == 0)
701 y2 = vs->h;
702 if (x2 > vs->w || x2 == 0)
703 x2 = vs->w;
704
705 stride = vs->w * sizeof(av_pixel);
706 inbuf += y1 * stride + x1 * sizeof(av_pixel);
707 }
708 else {
709 x2 = vs->w;
710 y2 = vs->h;
711 }
712 /* and blit */
713 stbir_resize_uint8(inbuf, (x2 - x1), (y2 - y1), stride,
714 (unsigned char*) prev->surf.buf, dw, dh, 0, sizeof(av_pixel));
715
716 prev->surf.w = dw;
717 prev->surf.h = dh;
718 prev->imgcons.w = prev->surf.w;
719 prev->imgcons.h = prev->surf.h;
720
721 return base;
722 }
723
extract_image_simple(struct text_format * prev,char * base)724 static char* extract_image_simple(struct text_format* prev, char* base){
725 char* wbase = base;
726
727 while (*base && *base != ',') base++;
728 if (*base)
729 *base++ = 0;
730
731 if (!strlen(wbase)){
732 arcan_warning("arcan_video_renderstring(), missing resource name.\n");
733 return NULL;
734 }
735
736 prev->imgcons.w = prev->imgcons.h = 0;
737 prev->surf.buf = NULL;
738
739 text_loadimage(prev, wbase, prev->imgcons);
740
741 if (prev->surf.buf){
742 prev->imgcons.w = prev->surf.w;
743 prev->imgcons.h = prev->surf.h;
744 }
745 else
746 arcan_warning(
747 "arcan_video_renderstring(), couldn't load icon (%s)\n", wbase);
748
749 return base;
750 }
751
extract_image(struct text_format * prev,char * base)752 static char* extract_image(struct text_format* prev, char* base)
753 {
754 int forcew = 0, forceh = 0;
755
756 char* widbase = base;
757 while (*base && *base != ',' && isdigit(*base)) base++;
758 if (*base && strlen(widbase) > 0)
759 *base++ = 0;
760 else {
761 arcan_warning("arcan_video_renderstring(), width scan failed,"
762 " premature end in sized image scan directive (%s)\n", widbase);
763 return NULL;
764 }
765 forcew = strtol(widbase, 0, 10);
766 if (forcew <= 0 || forcew > 1024){
767 arcan_warning("arcan_video_renderstring(), width scan failed,"
768 " unreasonable width (%d) specified in sized image scan "
769 "directive (%s)\n", forcew, widbase);
770 return NULL;
771 }
772
773 char* hghtbase = base;
774 while (*base && *base != ',' && isdigit(*base)) base++;
775 if (*base && strlen(hghtbase) > 0)
776 *base++ = 0;
777 else {
778 arcan_warning("arcan_video_renderstring(), height scan failed, "
779 "premature end in sized image scan directive (%s)\n", hghtbase);
780 return NULL;
781 }
782 forceh = strtol(hghtbase, 0, 10);
783 if (forceh <= 0 || forceh > 1024){
784 arcan_warning("arcan_video_renderstring(), height scan failed, "
785 "unreasonable height (%d) specified in sized image scan "
786 "directive (%s)\n", forceh, hghtbase);
787 return NULL;
788 }
789
790 char* wbase = base;
791 while (*base && *base != ',') base++;
792 if (*base == ','){
793 *base++ = 0;
794 }
795 else {
796 arcan_warning("arcan_video_renderstring(), missing resource name"
797 " terminator (,) in sized image scan directive (%s)\n", wbase);
798 return NULL;
799 }
800
801 if (strlen(wbase) > 0){
802 prev->imgcons.w = forcew;
803 prev->imgcons.h = forceh;
804 prev->surf.buf = NULL;
805 text_loadimage(prev, wbase, prev->imgcons);
806
807 return base;
808 }
809 else{
810 arcan_warning("arcan_video_renderstring(), missing resource name.\n");
811 return NULL;
812 }
813 }
814
formatend(char * base,struct text_format prev,char * orig,bool * ok)815 static struct text_format formatend(char* base, struct text_format prev,
816 char* orig, bool* ok) {
817 struct text_format failed = {0};
818 /* don't carry caret modifiers */
819 prev.newline = prev.tab = prev.cr = 0;
820 bool inv = false;
821 bool whskip = false;
822
823 while (*base) {
824 /* skip first whitespace (avoid situation where;
825 * \ffonts/test,181889 when writing 1889.. and still
826 * allow for dynamic input dialogs etc. */
827 if (whskip == false && isspace(*base)) {
828 base++;
829 whskip = true;
830 continue;
831 }
832
833 /* out of formatstring */
834 if (*base != '\\') { prev.endofs = base; break; }
835
836 char cmd;
837
838 retry:
839 cmd = *(base+1);
840 base += 2;
841
842 switch (cmd){
843 /* the ! prefix is a special case, meaning that we invert the next character */
844 case '!':
845 inv = true;
846 base--; *base = '\\';
847 goto retry;
848 break;
849 case 't':
850 prev.tab++;
851 break;
852 case 'n':
853 prev.newline++;
854 break;
855 case 'r':
856 prev.cr = true;
857 break;
858 case 'u':
859 prev.style = (inv ?
860 prev.style & TTF_STYLE_UNDERLINE : prev.style | TTF_STYLE_UNDERLINE);
861 break;
862 case 'b':
863 prev.style = (inv ?
864 prev.style & !TTF_STYLE_BOLD : prev.style | TTF_STYLE_BOLD);
865 break;
866 case 'i': prev.style = (inv ?
867 prev.style & !TTF_STYLE_ITALIC : prev.style | TTF_STYLE_ITALIC);
868 break;
869 case 'e':
870 base = extract_vidref(&prev, base, false);
871 break;
872 case 'E':
873 base = extract_vidref(&prev, base, true);
874 break;
875 case 'v':
876 break;
877 case 'T':
878 break;
879 case 'H':
880 break;
881 case 'V':
882 break;
883 case 'p':
884 base = extract_image_simple(&prev, base);
885 break;
886 case 'P':
887 base = extract_image(&prev, base);
888 break;
889 case '#':
890 base = extract_color(&prev, base);
891 break;
892 case 'f':
893 base = extract_font(&prev, base);
894 break;
895 default:
896 arcan_warning("arcan_video_renderstring(), "
897 "unknown escape sequence: '\\%c' (%s)\n", *(base+1), orig);
898 *ok = false;
899 return failed;
900 }
901
902 if (!base){
903 *ok = false;
904 return failed;
905 }
906
907 inv = false;
908 }
909
910 if (*base == 0)
911 prev.endofs = base;
912
913 *ok = true;
914 return prev;
915 }
916
917 /*
918 * arcan_lua.c
919 */
920 #ifndef CONST_MAX_SURFACEW
921 #define CONST_MAX_SURFACEW 8192
922 #endif
923
924 #ifndef CONST_MAX_SURFACEH
925 #define CONST_MAX_SURFACEH 4096
926 #endif
927
928 /* in arcan_ttf.c */
draw_builtin(struct rcell * cnode,const char * const base,struct text_format * style,int w,int h)929 static void draw_builtin(struct rcell* cnode,
930 const char* const base, struct text_format* style, int w, int h)
931 {
932 /* set size will have been called for the destination already as part
933 * of size chain that is used for allocation */
934 size_t len = strlen(base);
935 uint32_t ucs4[len+1];
936 UTF8_to_UTF32(ucs4, (const uint8_t* const) base, len);
937
938 for(size_t i = 0; i < len; i++){
939 tui_pixelfont_draw(
940 builtin_bitmap.bitmap, cnode->data.surf.buf, w, ucs4[i], i * style->px_skip, 0,
941 RGBA(style->col[0], style->col[1], style->col[2], style->col[3]),
942 0, w, h, true
943 );
944 }
945 }
946
render_alloc(struct rcell * cnode,const char * const base,struct text_format * style)947 static bool render_alloc(struct rcell* cnode,
948 const char* const base, struct text_format* style)
949 {
950 int w, h;
951
952 if (size_font_chain(style, base, &w, &h)){
953 arcan_warning("arcan_video_renderstring(), couldn't size node.\n");
954 return false;
955 }
956 /* easy to circumvent here by just splitting into larger nodes, need
957 * to do some accumulation for this to be less pointless */
958 if (w == 0 || w > CONST_MAX_SURFACEW || h == 0 || h > CONST_MAX_SURFACEH){
959 return false;
960 }
961
962 cnode->data.surf.buf = arcan_alloc_mem(w * h * sizeof(av_pixel),
963 ARCAN_MEM_VBUFFER, ARCAN_MEM_NONFATAL, ARCAN_MEMALIGN_PAGE);
964 if (!cnode->data.surf.buf){
965 arcan_warning("arcan_video_renderstring(%d,%d), failed alloc.\n",w, h);
966 return false;
967 }
968
969 /* we manually clear the buffer here because BZERO on VBUFFER sets FULLALPHA,
970 * that means we need to repack etc. to handle kerning and that costs more */
971 for (size_t i=0; i < w * h; i++)
972 cnode->data.surf.buf[i] = 0;
973
974 /* if there is no font, we use the built-in default */
975 if (!style->font){
976 draw_builtin(cnode, base, style, w, h);
977 }
978 else if (!TTF_RenderUTF8chain(cnode->data.surf.buf, w, h, w,
979 style->font->chain.data, style->font->chain.count,
980 base, style->col, style->style)){
981 arcan_warning("arcan_video_renderstring(), failed to render.\n");
982 arcan_mem_free(cnode->data.surf.buf);
983 cnode->data.surf.buf = NULL;
984 return false;
985 }
986
987 cnode->data.surf.w = w;
988 cnode->data.surf.h = h;
989 cnode->ascent = style->ascent;
990 cnode->height = style->height;
991 cnode->descent = style->descent;
992 cnode->skipv = style->skip;
993
994 return true;
995 }
996
currstyle_cnode(struct text_format * curr_style,const char * const base,struct rcell * cnode,bool sizeonly)997 static inline void currstyle_cnode(struct text_format* curr_style,
998 const char* const base, struct rcell* cnode, bool sizeonly)
999 {
1000 if (sizeonly){
1001 if (curr_style->font){
1002 int dw, dh;
1003 size_font_chain(curr_style, base, &dw, &dh);
1004 cnode->ascent = TTF_FontAscent(curr_style->font->chain.data[0]);
1005 cnode->width = dw;
1006 cnode->descent = TTF_FontDescent(curr_style->font->chain.data[0]);
1007 cnode->height = TTF_FontHeight(curr_style->font->chain.data[0]);
1008 }
1009 else{
1010 cnode->width = curr_style->imgcons.w;
1011 cnode->height = curr_style->imgcons.h;
1012 }
1013 return;
1014 }
1015
1016 /* image or render font */
1017 if (curr_style->surf.buf){
1018 cnode->data.surf.buf = curr_style->surf.buf;
1019 cnode->data.surf.w = curr_style->surf.w;
1020 cnode->data.surf.h = curr_style->surf.h;
1021 curr_style->surf.buf = NULL;
1022 return;
1023 }
1024
1025 if (!render_alloc(cnode, base, curr_style))
1026 goto reset;
1027
1028 return;
1029 reset:
1030 set_style(&last_style, &font_cache[0]);
1031 }
1032
trystep(struct rcell * cnode,bool force)1033 static struct rcell* trystep(struct rcell* cnode, bool force)
1034 {
1035 if (force || cnode->data.surf.buf)
1036 cnode = cnode->next = arcan_alloc_mem(sizeof(struct rcell),
1037 ARCAN_MEM_VSTRUCT, ARCAN_MEM_TEMPORARY | ARCAN_MEM_BZERO,
1038 ARCAN_MEMALIGN_NATURAL
1039 );
1040 return cnode;
1041 }
1042
1043 /* a */
build_textchain(char * message,struct rcell * root,bool sizeonly,bool nolast,bool reset)1044 static int build_textchain(char* message,
1045 struct rcell* root, bool sizeonly, bool nolast, bool reset)
1046 {
1047 int rv = 0;
1048 /*
1049 * if we don't want this to be stateful, we can just go:
1050 * set_style(&curr_style, font_cache[0].data);
1051 */
1052 struct text_format* curr_style = &last_style;
1053 if (reset)
1054 set_style(curr_style, &font_cache[0]);
1055
1056 struct rcell* cnode = root;
1057 char* current = message;
1058 char* base = message;
1059 int msglen = 0;
1060
1061 /* outer loop, find first split- point */
1062 while (*current) {
1063 if (*current == '\\') {
1064 /* special case, escape \ */
1065 if (*(current+1) == '\\') {
1066 memmove(current, current+1, strlen(current));
1067 current += 1;
1068 msglen++;
1069 }
1070 /* split-point (one or several formatting arguments) found */
1071 else {
1072 if (msglen > 0){
1073 *current = 0;
1074 /* render surface and slide window */
1075 currstyle_cnode(curr_style, base, cnode, sizeonly);
1076
1077 /* slide- alloc list of rendered blocks */
1078 cnode = trystep(cnode, false);
1079 *current = '\\';
1080 }
1081
1082 /* scan format- options and slide to end */
1083 bool okstatus;
1084 *curr_style = formatend(current, *curr_style, message, &okstatus);
1085 if (!okstatus)
1086 return -1;
1087
1088 /* caret modifiers need to be separately chained to avoid (three?) nasty
1089 * little edge conditions */
1090 if (curr_style->newline || curr_style->tab || curr_style->cr) {
1091 cnode = trystep(cnode, false);
1092 rv += curr_style->newline;
1093 cnode->data.format.newline = curr_style->newline;
1094 cnode->data.format.tab = curr_style->tab;
1095 cnode->data.format.cr = curr_style->cr;
1096 cnode = trystep(cnode, true);
1097 }
1098
1099 if (curr_style->surf.buf){
1100 currstyle_cnode(curr_style, base, cnode, sizeonly);
1101 cnode = trystep(cnode, false);
1102 }
1103
1104 current = base = curr_style->endofs;
1105 /* note, may this be a condition for a break rather than a return? */
1106 if (current == NULL)
1107 return -1;
1108
1109 msglen = 0;
1110 }
1111 }
1112 else {
1113 msglen += 1;
1114 current++;
1115 }
1116 }
1117
1118 /* last element .. */
1119 if (msglen) {
1120 cnode->next = NULL;
1121 if (sizeonly){
1122 size_font_chain(curr_style, base, (int*) &cnode->width, (int*) &cnode->height);
1123 }
1124 else
1125 render_alloc(cnode, base, curr_style);
1126 }
1127
1128 /* special handling needed for longer append chains */
1129 if (!nolast){
1130 cnode = trystep(cnode, true);
1131 cnode->data.format.newline = 1;
1132 rv++;
1133 }
1134
1135 return rv;
1136 }
1137
round_mult(unsigned num,unsigned int mult)1138 static unsigned int round_mult(unsigned num, unsigned int mult)
1139 {
1140 if (num == 0 || mult == 0)
1141 return mult; /* intended ;-) */
1142 unsigned int remain = num % mult;
1143 return remain ? num + mult - remain : num;
1144 }
1145
get_tabofs(int offset,int tabc,int8_t tab_spacing)1146 static unsigned int get_tabofs(int offset, int tabc, int8_t tab_spacing)
1147 {
1148 return PT_TO_HPX( tab_spacing ?
1149 round_mult(offset, tab_spacing) + ((tabc - 1) * tab_spacing) : offset );
1150
1151 return PT_TO_HPX(offset);
1152 }
1153
1154 /*
1155 * should really have a fast blit path, but since font rendering is expected to
1156 * be mostly replaced/complemented with a mix of in-place rendering and proper
1157 * packing and vertex buffers in 0.7ish we just leave it like this
1158 */
copy_rect(av_pixel * dst,size_t dst_sz,struct rcell * surf,int width,int height,int x,int y)1159 static inline void copy_rect(av_pixel* dst, size_t dst_sz,
1160 struct rcell* surf, int width, int height, int x, int y)
1161 {
1162 uintptr_t high = (sizeof(av_pixel) * height * width);
1163 if (high > dst_sz){
1164 arcan_warning("arcan_video_renderstring():copy_rect OOB, %zu/%zu\n",
1165 high, dst_sz);
1166 return;
1167 }
1168
1169 for (int row = 0; row < surf->data.surf.h && row < height - y; row++)
1170 memcpy(
1171 &dst[(y+row)*width+x],
1172 &surf->data.surf.buf[row*surf->data.surf.w],
1173 surf->data.surf.w * sizeof(av_pixel)
1174 );
1175 }
1176
cleanup_chain(struct rcell * root)1177 static void cleanup_chain(struct rcell* root)
1178 {
1179 while (root){
1180 assert(root != (void*) 0xdeadbeef);
1181 if (root->data.surf.buf){
1182 arcan_mem_free(root->data.surf.buf);
1183 root->data.surf.buf = (void*) 0xfeedface;
1184 }
1185
1186 struct rcell* prev = root;
1187 root = root->next;
1188 prev->next = (void*) 0xdeadbeef;
1189 arcan_mem_free(prev);
1190 }
1191 }
1192
process_chain(struct rcell * root,arcan_vobject * dst,size_t chainlines,bool norender,bool pot,unsigned int * n_lines,struct renderline_meta ** lineheights,size_t * dw,size_t * dh,uint32_t * d_sz,size_t * maxw,size_t * maxh)1193 static av_pixel* process_chain(struct rcell* root, arcan_vobject* dst,
1194 size_t chainlines, bool norender, bool pot,
1195 unsigned int* n_lines, struct renderline_meta** lineheights, size_t* dw,
1196 size_t* dh, uint32_t* d_sz, size_t* maxw, size_t* maxh)
1197 {
1198 struct rcell* cnode = root;
1199 unsigned int linecount = 0;
1200 *maxw = 0;
1201 *maxh = 0;
1202 int lineh = 0, fonth = 0, ascenth = 0, descenth = 0;
1203 int curw = 0;
1204
1205 int line_spacing = 0;
1206 bool fixed_spacing = false;
1207 /* if (line_spacing == 0){
1208 fixed_spacing = false;
1209 } */
1210
1211 /* note, linecount is overflow */
1212 struct renderline_meta* lines = arcan_alloc_mem(sizeof(
1213 struct renderline_meta) * (chainlines + 1), ARCAN_MEM_VSTRUCT,
1214 ARCAN_MEM_BZERO | ARCAN_MEM_TEMPORARY, ARCAN_MEMALIGN_NATURAL
1215 );
1216
1217 /* (A) figure out visual constraints */
1218 struct rcell* linestart = cnode;
1219
1220 while (cnode) {
1221 /* data node */
1222 if (cnode->data.surf.buf) {
1223 if (!fixed_spacing)
1224 line_spacing = cnode->skipv;
1225
1226 if (lineh + line_spacing <= 0 || cnode->data.surf.h > lineh + line_spacing)
1227 lineh = cnode->data.surf.h;
1228
1229 if (cnode->ascent > ascenth){
1230 ascenth = cnode->ascent;
1231 }
1232
1233 if (cnode->descent > descenth){
1234 descenth = cnode->descent;
1235 }
1236
1237 if (cnode->height > fonth){
1238 fonth = cnode->height;
1239 }
1240
1241 /* track dimensions, may want to use them later */
1242 curw += cnode->data.surf.w;
1243 }
1244 /* format node */
1245 else {
1246 if (cnode->data.format.cr){
1247 curw = 0;
1248 }
1249
1250 if (cnode->data.format.tab)
1251 curw = get_tabofs(curw, cnode->data.format.tab, /* tab_spacing */ 0);
1252
1253 if (cnode->data.format.newline > 0)
1254 for (int i = cnode->data.format.newline; i > 0; i--) {
1255 lines[linecount].ystart = *maxh;
1256 lines[linecount].height = fonth;
1257 lines[linecount++].ascent = ascenth;
1258 *maxh += lineh + line_spacing;
1259 ascenth = fonth = lineh = 0;
1260 }
1261 }
1262
1263 if (curw > *maxw)
1264 *maxw = curw;
1265
1266 cnode = cnode->next;
1267 }
1268
1269 /* (B) render into destination buffers, possibly pad to reduce number
1270 * of needed relocations on dynamic resizing from small changes */
1271 *dw = pot ? nexthigher(*maxw) : *maxw;
1272 *dh = pot ? nexthigher(*maxh) : *maxh;
1273
1274 *d_sz = *dw * *dh * sizeof(av_pixel);
1275
1276 if (norender)
1277 return (cleanup_chain(root), NULL);
1278
1279 /* if we have a vobj set, re-use that backing store, and treat
1280 * it as a source-stream resize (so scaling factors etc. get reapplied) */
1281
1282 av_pixel* raw = NULL;
1283
1284 if (dst){
1285 /* manually resize the local buffer so the video_resizefeed call won't
1286 * do dual agp_update_vstore synchs */
1287 struct agp_vstore* s = dst->vstore;
1288
1289 if (s->vinf.text.raw)
1290 arcan_mem_free(s->vinf.text.raw);
1291
1292 raw = s->vinf.text.raw = arcan_alloc_mem(*d_sz,
1293 ARCAN_MEM_VBUFFER, 0, ARCAN_MEMALIGN_PAGE);
1294 s->vinf.text.s_raw = *d_sz;
1295 s->w = *dw;
1296 s->h = *dh;
1297 }
1298 else{
1299 raw = arcan_alloc_mem(*d_sz, ARCAN_MEM_VBUFFER,
1300 ARCAN_MEM_NONFATAL, ARCAN_MEMALIGN_PAGE);
1301 }
1302
1303 if (!raw || !*d_sz)
1304 return (cleanup_chain(root), raw);
1305
1306 memset(raw, '\0', *d_sz);
1307 cnode = root;
1308 curw = 0;
1309 int line = 0;
1310
1311 while (cnode) {
1312 if (cnode->data.surf.buf) {
1313 copy_rect(raw, *d_sz, cnode, *dw, *dh, curw, lines[line].ystart);
1314 curw += cnode->data.surf.w;
1315 }
1316 else {
1317 if (cnode->data.format.tab > 0)
1318 curw = get_tabofs(curw, cnode->data.format.tab, /* tab_spacing */ 0);
1319
1320 if (cnode->data.format.cr)
1321 curw = 0;
1322
1323 if (cnode->data.format.newline > 0)
1324 line += cnode->data.format.newline;
1325 }
1326 cnode = cnode->next;
1327 }
1328
1329 if (n_lines)
1330 *n_lines = linecount;
1331
1332 if (lineheights)
1333 *lineheights = lines;
1334 else
1335 arcan_mem_free(lines);
1336
1337 if (dst){
1338 agp_resize_vstore(dst->vstore, *dw, *dh);
1339 dst->vstore->vinf.text.hppcm = default_hdpi / 2.54;
1340 dst->vstore->vinf.text.vppcm = default_vdpi / 2.54;
1341 }
1342
1343 return (cleanup_chain(root), raw);
1344 }
1345
arcan_renderfun_renderfmtstr_extended(const char ** msgarray,arcan_vobj_id dstore,bool pot,unsigned int * n_lines,struct renderline_meta ** lineheights,size_t * dw,size_t * dh,uint32_t * d_sz,size_t * maxw,size_t * maxh,bool norender)1346 av_pixel* arcan_renderfun_renderfmtstr_extended(const char** msgarray,
1347 arcan_vobj_id dstore, bool pot,
1348 unsigned int* n_lines, struct renderline_meta** lineheights, size_t* dw,
1349 size_t* dh, uint32_t* d_sz, size_t* maxw, size_t* maxh, bool norender)
1350 {
1351 struct rcell* root = arcan_alloc_mem(sizeof(struct rcell),
1352 ARCAN_MEM_VSTRUCT, ARCAN_MEM_BZERO | ARCAN_MEM_TEMPORARY,
1353 ARCAN_MEMALIGN_NATURAL
1354 );
1355 if (!root || !msgarray || !msgarray[0])
1356 return NULL;
1357
1358 last_style.newline = 0;
1359 last_style.tab = 0;
1360 last_style.cr = false;
1361
1362 /* %2, build as text-chain, accumulate linechain view */
1363 size_t acc = 0, ind = 0;
1364
1365 struct rcell* cur = root;
1366 while (msgarray[ind]){
1367 if (msgarray[ind][0] == 0){
1368 ind++;
1369 continue;
1370 }
1371
1372 if (ind % 2 == 0){
1373 char* work = strdup(msgarray[ind]);
1374 int nlines = build_textchain(work, cur, false, true, ind == 0);
1375 arcan_mem_free(work);
1376 if (-1 == nlines)
1377 break;
1378 acc += nlines;
1379 while (cur->next != NULL)
1380 cur = cur->next;
1381 }
1382 /* %2+1, no format-string input, just treat as text */
1383 else{
1384 cur = cur->next = arcan_alloc_mem(sizeof(struct rcell),
1385 ARCAN_MEM_VSTRUCT, ARCAN_MEM_BZERO | ARCAN_MEM_TEMPORARY,
1386 ARCAN_MEMALIGN_NATURAL
1387 );
1388 currstyle_cnode(&last_style, msgarray[ind], cur, false);
1389 }
1390 ind++;
1391 }
1392
1393 /* append newline */
1394 cur = cur->next = arcan_alloc_mem(
1395 sizeof(struct rcell), ARCAN_MEM_VSTRUCT,
1396 ARCAN_MEM_TEMPORARY | ARCAN_MEM_BZERO,
1397 ARCAN_MEMALIGN_NATURAL
1398 );
1399 cur->data.format.newline = 1;
1400
1401 return process_chain(root, arcan_video_getobject(dstore),
1402 acc+1, norender, pot, n_lines,
1403 lineheights, dw, dh, d_sz, maxw, maxh
1404 );
1405 }
1406
arcan_renderfun_renderfmtstr(const char * message,arcan_vobj_id dstore,bool pot,unsigned int * n_lines,struct renderline_meta ** lineheights,size_t * dw,size_t * dh,uint32_t * d_sz,size_t * maxw,size_t * maxh,bool norender)1407 av_pixel* arcan_renderfun_renderfmtstr(const char* message,
1408 arcan_vobj_id dstore,
1409 bool pot, unsigned int* n_lines, struct renderline_meta** lineheights,
1410 size_t* dw, size_t* dh, uint32_t* d_sz,
1411 size_t* maxw, size_t* maxh, bool norender)
1412 {
1413 if (!message)
1414 return NULL;
1415
1416 av_pixel* raw = NULL;
1417
1418 /* (A) parse format string and build chains of renderblocks */
1419 struct rcell* root = arcan_alloc_mem(sizeof(struct rcell),
1420 ARCAN_MEM_VSTRUCT, ARCAN_MEM_BZERO | ARCAN_MEM_TEMPORARY,
1421 ARCAN_MEMALIGN_NATURAL
1422 );
1423
1424 char* work = strdup(message);
1425 last_style.newline = 0;
1426 last_style.tab = 0;
1427 last_style.cr = false;
1428
1429 int chainlines = build_textchain(work, root, false, false, true);
1430 arcan_mem_free(work);
1431
1432 if (chainlines > 0){
1433 raw = process_chain(root, arcan_video_getobject(dstore),
1434 chainlines, norender, pot, n_lines, lineheights,
1435 dw, dh, d_sz, maxw, maxh
1436 );
1437 }
1438
1439 return raw;
1440 }
1441
arcan_renderfun_stretchblit(char * src,int inw,int inh,uint32_t * dst,size_t dstw,size_t dsth,int flipv)1442 int arcan_renderfun_stretchblit(char* src, int inw, int inh,
1443 uint32_t* dst, size_t dstw, size_t dsth, int flipv)
1444 {
1445 const int pack_tight = 0;
1446 const int rgba_ch = 4;
1447
1448 if (1 != stbir_resize_uint8((unsigned char*)src,
1449 inw, inh, pack_tight, (unsigned char*)dst, dstw, dsth, pack_tight, rgba_ch))
1450 return -1;
1451
1452 if (!flipv)
1453 return 1;
1454
1455 uint32_t row[dstw];
1456 size_t stride = dstw * 4;
1457 for (size_t y = 0; y < dsth >> 1; y++){
1458 if (y == (dsth - 1 - y))
1459 continue;
1460
1461 memcpy(row, &dst[y*dstw], stride);
1462 memcpy(&dst[y*dstw], &dst[(dsth-1-y)*dstw], stride);
1463 memcpy(&dst[(dsth-1-y)*dstw], row, stride);
1464 }
1465
1466 return 1;
1467 }
1468
1469 struct arcan_renderfun_fontgroup {
1470 struct tui_font* font;
1471 struct tui_raster_context* raster;
1472 size_t used;
1473 size_t w, h;
1474 float ppcm;
1475 float size_mm;
1476
1477 /* when adding atlas support, reference the internal font here instead */
1478 };
1479
1480 static void build_font_group(
1481 struct arcan_renderfun_fontgroup* grp, int* fds, size_t n_fonts);
1482
close_font_slot(struct arcan_renderfun_fontgroup * grp,int slot)1483 static void close_font_slot(struct arcan_renderfun_fontgroup* grp, int slot)
1484 {
1485 if (!(grp->font[slot].fd && grp->font[slot].fd != BADFD) ||
1486 grp->font == &builtin_bitmap)
1487 return;
1488
1489 close(grp->font[slot].fd);
1490 grp->font[slot].fd = -1;
1491 if (grp->font[slot].vector){
1492 TTF_CloseFont(grp->font[slot].truetype);
1493 grp->font[slot].truetype = NULL;
1494 }
1495 else{
1496 tui_pixelfont_close(grp->font[slot].bitmap);
1497 grp->font[slot].bitmap = NULL;
1498 }
1499 }
1500
consume_pixel_font(struct arcan_renderfun_fontgroup * grp,int fd)1501 static int consume_pixel_font(struct arcan_renderfun_fontgroup* grp, int fd)
1502 {
1503 data_source src = {
1504 .fd = fd,
1505 .source = NULL
1506 };
1507
1508 map_region map = arcan_map_resource(&src, false);
1509 if (!map.ptr || map.sz < 32 || !tui_pixelfont_valid(map.u8, 23)){
1510 arcan_release_map(map);
1511 return 0;
1512 }
1513
1514 /* prohibit mixing and matching vector/bitmap */
1515 if (grp->font[0].truetype && grp->font[0].vector){
1516 close_font_slot(grp, 0);
1517 grp->font[0].vector = false;
1518 }
1519
1520 for (size_t i = 1; i < grp->used; i++){
1521 close_font_slot(grp, i);
1522 }
1523
1524 /* re-use current bitmap group or build one if it isn't there */
1525 if (!grp->font[0].bitmap &&
1526 !(grp->font[0].bitmap = tui_pixelfont_open(64))){
1527 close(fd);
1528 arcan_release_map(map);
1529 return -1;
1530 }
1531
1532 close(fd);
1533 return 1;
1534 }
1535
font_group_ptpx(struct arcan_renderfun_fontgroup * grp,size_t * pt,size_t * px)1536 static void font_group_ptpx(
1537 struct arcan_renderfun_fontgroup* grp, size_t* pt, size_t* px)
1538 {
1539 float pt_size = roundf((float)grp->size_mm * 2.8346456693f);
1540 if (pt_size < 4)
1541 pt_size = 4;
1542
1543 if (pt)
1544 *pt = pt_size;
1545
1546 if (px)
1547 *px = roundf((float)pt_size * 0.03527778 * grp->ppcm);
1548 }
1549
set_font_slot(struct arcan_renderfun_fontgroup * grp,int slot,int fd)1550 static void set_font_slot(
1551 struct arcan_renderfun_fontgroup* grp, int slot, int fd)
1552 {
1553 /* don't do anything to a bad font */
1554 if (fd == -1)
1555 return;
1556
1557 /* the builtin- bitmap is immutable */
1558 if (grp->font == &builtin_bitmap){
1559 close(fd);
1560 return;
1561 }
1562
1563 /* first check for a supported pixel font format, early-out on found/io error */
1564 int pfstat = consume_pixel_font(grp, fd);
1565 if (pfstat)
1566 return;
1567
1568 /* assume vector and try TTF - next step here is to use this to resolve the
1569 * font / size against the existing cache using the inode, careful with the
1570 * refcount in that case though */
1571 size_t pt_sz;
1572 font_group_ptpx(grp, &pt_sz, NULL);
1573 float dpi = grp->ppcm * 2.54f;
1574
1575 /* OpenFontFD duplicates internally */
1576 grp->font[slot].truetype = TTF_OpenFontFD(fd, pt_sz, dpi, dpi);
1577 if (!grp->font[slot].truetype){
1578 close(fd);
1579 grp->font[slot].vector = false;
1580 grp->font[slot].fd = -1;
1581 return;
1582 }
1583
1584 grp->font[slot].fd = fd;
1585 grp->font[slot].vector = true;
1586 TTF_SetFontStyle(grp->font[slot].truetype, TTF_STYLE_NORMAL);
1587 TTF_SetFontStyle(grp->font[slot].truetype, TTF_HINTING_NORMAL);
1588 }
1589
1590 /* option to consider is if we do refcount + sharing of group, or do that
1591 * on the font level, likely the latter given how that worked out for vobjs
1592 * with null_surface sharing vstores vs. the headache that was instancing */
arcan_renderfun_fontgroup(int * fds,size_t n_fonts)1593 struct arcan_renderfun_fontgroup* arcan_renderfun_fontgroup(int* fds, size_t n_fonts)
1594 {
1595 /* we take ownership of descriptors */
1596 struct arcan_renderfun_fontgroup* grp =
1597 arcan_alloc_mem(
1598 sizeof(struct arcan_renderfun_fontgroup),
1599 ARCAN_MEM_VSTRUCT,
1600 ARCAN_MEM_BZERO,
1601 ARCAN_MEMALIGN_NATURAL
1602 );
1603
1604 if (!grp)
1605 return NULL;
1606
1607 build_font_group(grp, fds, n_fonts);
1608
1609 return grp;
1610 }
1611
arcan_renderfun_fontgroup_replace(struct arcan_renderfun_fontgroup * group,int slot,int new)1612 void arcan_renderfun_fontgroup_replace(
1613 struct arcan_renderfun_fontgroup* group, int slot, int new)
1614 {
1615 if (!group)
1616 return;
1617
1618 /* always invalidate any cached raster */
1619 if (group->raster){
1620 tui_raster_free(group->raster);
1621 group->raster = NULL;
1622 }
1623
1624 /* can't accomodate, ignore */
1625 if (slot >= group->used){
1626 if (new != -1)
1627 close(new);
1628 return;
1629 }
1630
1631 set_font_slot(group, slot, new);
1632 }
1633
arcan_renderfun_release_fontgroup(struct arcan_renderfun_fontgroup * group)1634 void arcan_renderfun_release_fontgroup(struct arcan_renderfun_fontgroup* group)
1635 {
1636 if (!group)
1637 return;
1638
1639 for (size_t i = 0; i < group->used; i++)
1640 close_font_slot(group, i);
1641
1642 group->used = 0;
1643
1644 tui_raster_free(group->raster);
1645
1646 if (group->font != &builtin_bitmap){
1647 arcan_mem_free(group->font);
1648 }
1649 arcan_mem_free(group);
1650 }
1651
arcan_renderfun_fontgroup_size(struct arcan_renderfun_fontgroup * group,float size_mm,float ppcm,size_t * w,size_t * h)1652 void arcan_renderfun_fontgroup_size(
1653 struct arcan_renderfun_fontgroup* group,
1654 float size_mm, float ppcm, size_t* w, size_t* h)
1655 {
1656
1657 /* the 'raster' context derives from the group in a certain state, and can be
1658 * dependent on a shared glyph atlas that changes between invocations - so
1659 * invalidate on size change */
1660 if (group->raster){
1661 tui_raster_free(group->raster);
1662 group->raster = NULL;
1663 }
1664
1665 if (size_mm > EPSILON)
1666 group->size_mm = size_mm;
1667
1668 if (ppcm > EPSILON)
1669 group->ppcm = ppcm;
1670
1671 /* recalculate new sizes so that we can update the group itself */
1672 size_t pt, px;
1673 font_group_ptpx(group, &pt, &px);
1674 float dpi = group->ppcm * 2.54;
1675
1676 /* re-open each font for the new pt */
1677 for (size_t i = 0; i < group->used; i++){
1678 if (group->font[i].vector && group->font[i].truetype)
1679 {
1680 group->font[i].truetype =
1681 TTF_ReplaceFont(group->font[i].truetype, pt, dpi, dpi);
1682 }
1683 }
1684
1685 /* reprobe based on first slot */
1686 if (group->font[0].vector){
1687 group->w = group->h = 0;
1688 TTF_ProbeFont(group->font[0].truetype, &group->w, &group->h);
1689 }
1690 else {
1691 tui_pixelfont_setsz(group->font[0].bitmap, px, &group->w, &group->h);
1692 }
1693
1694 *w = group->w;
1695 *h = group->h;
1696 }
1697
1698 struct tui_raster_context*
arcan_renderfun_fontraster(struct arcan_renderfun_fontgroup * group)1699 arcan_renderfun_fontraster(struct arcan_renderfun_fontgroup* group)
1700 {
1701 /* if we don't have a valid font in the font group, the raster is pointless */
1702 if ((group->font[0].vector && !group->font[0].truetype) ||
1703 (!group->font[0].vector && !group->font[0].bitmap))
1704 return NULL;
1705
1706 /* if we have a cached raster, return */
1707 if (group->raster){
1708 return group->raster;
1709 }
1710
1711 /* otherwise, build a new list (_setup will copy internally) */
1712 struct tui_font* lst[group->used];
1713 for (size_t i = 0; i < group->used; i++){
1714 lst[i] = &group->font[i];
1715 }
1716
1717 group->raster = tui_raster_setup(group->w, group->h);
1718 tui_raster_setfont(group->raster, lst, group->used);
1719
1720 return group->raster;
1721 }
1722
build_font_group(struct arcan_renderfun_fontgroup * grp,int * fds,size_t n_fonts)1723 static void build_font_group(
1724 struct arcan_renderfun_fontgroup* grp, int* fds, size_t n_fonts)
1725 {
1726 /* safe defaults */
1727 grp->ppcm = 37.795276;
1728 grp->size_mm = 3.527780;
1729
1730 /* default requested */
1731 if (!n_fonts || fds[0] == -1)
1732 goto fallback_bitmap;
1733
1734 grp->used = n_fonts;
1735 grp->font = arcan_alloc_mem(
1736 sizeof(struct tui_font) * n_fonts,
1737 ARCAN_MEM_VSTRUCT,
1738 ARCAN_MEM_BZERO,
1739 ARCAN_MEMALIGN_NATURAL
1740 );
1741 if (!grp->font)
1742 goto fallback_bitmap;
1743
1744 /* probe types, fill out the font structure accordingly */
1745 for (size_t i = 0; i < n_fonts; i++){
1746 set_font_slot(grp, i, fds[i]);
1747 }
1748 return;
1749
1750 fallback_bitmap:
1751 grp->used = 1;
1752 arcan_mem_free(grp->font);
1753 grp->font = &builtin_bitmap;
1754 }
1755