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