414 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * lws abstract display
 | |
|  *
 | |
|  * Copyright (C) 2019 - 2022 Andy Green <andy@warmcat.com>
 | |
|  *
 | |
|  * Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
|  * of this software and associated documentation files (the "Software"), to
 | |
|  * deal in the Software without restriction, including without limitation the
 | |
|  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 | |
|  * sell copies of the Software, and to permit persons to whom the Software is
 | |
|  * furnished to do so, subject to the following conditions:
 | |
|  *
 | |
|  * The above copyright notice and this permission notice shall be included in
 | |
|  * all copies or substantial portions of the Software.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | |
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | |
|  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 | |
|  * IN THE SOFTWARE.
 | |
|  *
 | |
|  * Display List Object: text
 | |
|  */
 | |
| 
 | |
| #include <private-lib-core.h>
 | |
| #include "private-lib-drivers-display-dlo.h"
 | |
| 
 | |
| size_t
 | |
| utf8_bytes(uint8_t u)
 | |
| {
 | |
| 	if ((u & 0x80) == 0)
 | |
| 		return 1;
 | |
| 
 | |
| 	if ((u & 0xe0) == 0xc0)
 | |
| 		return 2;
 | |
| 
 | |
| 	if ((u & 0xf0) == 0xe0)
 | |
| 		return 3;
 | |
| 
 | |
| 	if ((u & 0xf8) == 0xf0)
 | |
| 		return 4;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| utf8_unicode(const char *utf8, size_t *utf8_len, uint32_t *unicode)
 | |
| {
 | |
| 	size_t glyph_len = utf8_bytes((uint8_t)*utf8);
 | |
| 	size_t n;
 | |
| 
 | |
| 	if (!glyph_len || glyph_len > *utf8_len) {
 | |
| 		(*utf8_len)--;
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	if (glyph_len == 1)
 | |
| 		*unicode = (uint32_t)*utf8++;
 | |
| 	else {
 | |
| 		*unicode = (uint32_t)((*utf8++) & (0x7f >> glyph_len));
 | |
| 		for (n = 1; n < glyph_len; n++)
 | |
| 			*unicode = (*unicode << 6) | ((*utf8++) & 0x3f);
 | |
| 	}
 | |
| 
 | |
| 	*utf8_len -= glyph_len;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void
 | |
| lws_display_dlo_text_destroy(struct lws_dlo *dlo)
 | |
| {
 | |
| 	lws_dlo_text_t *text = lws_container_of(dlo, lws_dlo_text_t, dlo);
 | |
| 
 | |
| 	lws_free_set_NULL(text->kern);
 | |
| 	lws_free_set_NULL(text->text);
 | |
| 
 | |
| 	lwsac_free(&text->ac_glyphs);
 | |
| }
 | |
| 
 | |
| int
 | |
| lws_display_dlo_text_update(lws_dlo_text_t *text, lws_display_colour_t dc,
 | |
| 			    lws_fx_t indent, const char *utf8, size_t text_len)
 | |
| {
 | |
| 	const char *last_utf8 = utf8, *outf8 = utf8;
 | |
| 	size_t last_bp_n = 0, tlen = text_len;
 | |
| 	lws_fx_t t1, eff, last_bp_eff, t2;
 | |
| 	uint8_t r = 0;
 | |
| 	char uc;
 | |
| 
 | |
| 	if (text->kern)
 | |
| 		lws_free_set_NULL(text->kern);
 | |
| 
 | |
| 	if (text->text)
 | |
| 		lws_free_set_NULL(text->text);
 | |
| 
 | |
| 	lws_dll2_owner_clear(&text->glyphs);
 | |
| 	lwsac_free(&text->ac_glyphs);
 | |
| 
 | |
| 	text->indent = indent;
 | |
| 	text->dlo.dc = dc;
 | |
| 
 | |
| 	lws_fx_set(eff, 0, 0);
 | |
| 
 | |
| 	/*
 | |
| 	 * Let's go through the new string glyph by glyph, we want to
 | |
| 	 * calculate effective kerned widths, and optionally deal with wrapping.
 | |
| 	 *
 | |
| 	 * But we don't want to instantiate the glyph objects until we are
 | |
| 	 * engaged with rendering them.  Otherwise we will carry around the
 | |
| 	 * whole page-worth's of glyphs at once needlessly, which won't scale
 | |
| 	 * for text-heavy pages.  lws_display_dlo_text_attach_glyphs() does the
 | |
| 	 * same flow as this but to create the glyphs and is called later
 | |
| 	 * as the text dlo becomes rasterized during rendering.
 | |
| 	 */
 | |
| 
 | |
| /*	{ char b1[22]; lwsl_err("eff %s\n", lws_fx_string(&eff, b1, sizeof(b1))); }
 | |
| 	{ char b1[22]; lwsl_err("indent %s\n", lws_fx_string(&indent, b1, sizeof(b1))); }
 | |
| 	{ char b1[22]; lwsl_err("boxw %s\n", lws_fx_string(&text->dlo.box.w, b1, sizeof(b1))); } */
 | |
| 
 | |
| 	while (tlen &&
 | |
| 	       lws_fx_comp(lws_fx_add(&t1, &eff, &indent), &text->dlo.box.w) < 0) {
 | |
| 		size_t ot = tlen;
 | |
| 		uint32_t unicode;
 | |
| 
 | |
| 		if (!utf8_unicode(utf8, &tlen, &unicode)) {
 | |
| 			text->font->image_glyph(text, unicode, 0);
 | |
| 
 | |
| 			uc = *utf8;
 | |
| 			utf8 += (ot - tlen);
 | |
| 
 | |
| 			if (uc == ' ') { /* act to snip it if used */
 | |
| 				last_utf8 = utf8;
 | |
| 				last_bp_n = tlen;
 | |
| 				last_bp_eff = eff;
 | |
| 			}
 | |
| 
 | |
| 			if (!lws_display_font_mcufont_getcwidth(text, unicode, &t2))
 | |
| 				lws_fx_add(&eff, &eff, &t2);
 | |
| 
 | |
| 			if (uc == '-' || uc == ',' || uc == ';' || uc == ':') {
 | |
| 				/* act to leave it in */
 | |
| 				last_utf8 = utf8;
 | |
| 				last_bp_n = tlen;
 | |
| 				last_bp_eff = eff;
 | |
| 			}
 | |
| 		} else
 | |
| 			lwsl_err("%s: missing glyph\n", __func__);
 | |
| 	}
 | |
| 
 | |
| 	if (last_bp_n &&
 | |
| 	    lws_fx_comp(lws_fx_add(&t1, &eff, &indent), &text->dlo.box.w) >= 0) {
 | |
| 		eff = last_bp_eff;
 | |
| 		utf8 = last_utf8;
 | |
| 		tlen = last_bp_n;
 | |
| 		r = 1;
 | |
| 	}
 | |
| 
 | |
| 	text->text_len = text_len - tlen;
 | |
| 	if (tlen == text_len) {
 | |
| 		lwsl_notice("we couldn't fit anything in there, newline\n");
 | |
| 		return 2;
 | |
| 	}
 | |
| 
 | |
| 	text->text = lws_malloc(text->text_len + 1, __func__);
 | |
| 	if (!text->text)
 | |
| 		return -1;
 | |
| 
 | |
| 	memcpy(text->text, outf8, text->text_len);
 | |
| 	text->text[text->text_len] = '\0';
 | |
| 
 | |
| 	memset(&text->bounding_box, 0, sizeof(text->bounding_box));
 | |
| 	text->bounding_box.w = eff;
 | |
| 	text->bounding_box.h.whole = text->font_height;
 | |
| 	text->bounding_box.h.frac = 0;
 | |
| 
 | |
| 	return r;
 | |
| }
 | |
| 
 | |
| int
 | |
| lws_display_dlo_text_attach_glyphs(lws_dlo_text_t *text)
 | |
| {
 | |
| 	const char *utf8 = text->text;
 | |
| 	size_t tlen = text->text_len;
 | |
| 	lws_font_glyph_t *g = NULL;
 | |
| 	uint32_t unicode;
 | |
| 	lws_fx_t eff;
 | |
| 	uint8_t r = 0;
 | |
| 
 | |
| 	lws_fx_set(eff, 0, 0);
 | |
| 
 | |
| 	while (tlen) {
 | |
| 		size_t ot = tlen;
 | |
| 
 | |
| 		g = NULL;
 | |
| 		if (!utf8_unicode(utf8, &tlen, &unicode))
 | |
| 			/* instantiate the glyphs this time */
 | |
| 			g = text->font->image_glyph(text, unicode, 1);
 | |
| 		if (g == NULL) {
 | |
| 			lwsl_warn("%s: no glyph for 0x%02X '%c'\n", __func__, (unsigned int)*utf8, *utf8);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		utf8 += (ot - tlen);
 | |
| 		g->xpx = eff;
 | |
| 		lws_fx_add(&eff, &eff, &g->cwidth);
 | |
| 	}
 | |
| 
 | |
| 	return r;
 | |
| }
 | |
| 
 | |
| lws_dlo_text_t *
 | |
| lws_display_dlo_text_new(lws_displaylist_t *dl, lws_dlo_t *dlo_parent,
 | |
| 			 lws_box_t *box, const lws_display_font_t *font)
 | |
| {
 | |
| 	lws_dlo_text_t *text = lws_zalloc(sizeof(*text), __func__);
 | |
| 
 | |
| 	if (!text)
 | |
| 		return NULL;
 | |
| 
 | |
| 	text->dlo.render = font->renderer;
 | |
| 	text->dlo._destroy = lws_display_dlo_text_destroy;
 | |
| 	text->dlo.box = *box;
 | |
| 	text->font = font;
 | |
| 
 | |
| 	lws_display_dlo_add(dl, dlo_parent, &text->dlo);
 | |
| 
 | |
| 	return text;
 | |
| }
 | |
| 
 | |
| static const char *
 | |
| castrstr(const char *haystack, const char *needle)
 | |
| {
 | |
| 	size_t sn = strlen(needle), h = strlen(haystack) - sn + 1, n;
 | |
| 	char c, c1;
 | |
| 
 | |
| 	while (1) {
 | |
| 		for (n = 0; n < sn; n++) {
 | |
| 			c = (char)((haystack[h + n] >= 'A' && haystack[h + n] <= 'Z') ?
 | |
| 				haystack[h + n] + ('a' - 'A') : haystack[h + n]);
 | |
| 			c1 = (char)((needle[n] >= 'A' && needle[n] <= 'Z') ?
 | |
| 				needle[n] + ('a' - 'A') : needle[n]);
 | |
| 			if (c != c1)
 | |
| 				break;
 | |
| 		}
 | |
| 		if (n == sn)
 | |
| 			return &haystack[h];
 | |
| 
 | |
| 		if (!h)
 | |
| 			break;
 | |
| 		h--;
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| int
 | |
| lws_font_register(struct lws_context *cx, const uint8_t *data, size_t data_len)
 | |
| {
 | |
| 	lws_display_font_t *a;
 | |
| 
 | |
| 	if (lws_ser_ru32be(data) != LWS_FOURCC('M', 'C', 'U', 'F'))
 | |
| 		return 1;
 | |
| 
 | |
| 	a = lws_zalloc(sizeof(*a), __func__);
 | |
| 	if (!a)
 | |
| 		return 1;
 | |
| 
 | |
| 	a->choice.family_name = (const char *)data +
 | |
| 				lws_ser_ru32be(data + MCUFO_FOFS_FULLNAME);
 | |
| 
 | |
| 	if (castrstr(a->choice.family_name, "serif") ||
 | |
| 	    castrstr(a->choice.family_name, "roman"))
 | |
| 		a->choice.generic_name = "serif";
 | |
| 	else
 | |
| 		a->choice.generic_name = "sans";
 | |
| 
 | |
| 	if (castrstr(a->choice.family_name, "italic") ||
 | |
| 	    castrstr(a->choice.family_name, "oblique"))
 | |
| 		a->choice.style = 1;
 | |
| 
 | |
| 	if (castrstr(a->choice.family_name, "extrabold") ||
 | |
| 	    castrstr(a->choice.family_name, "extra bold"))
 | |
| 		a->choice.weight = 900;
 | |
| 	else
 | |
| 		if (castrstr(a->choice.family_name, "bold"))
 | |
| 		    a->choice.weight = 700;
 | |
| 		else
 | |
| 			if (castrstr(a->choice.family_name, "extralight") ||
 | |
| 			    castrstr(a->choice.family_name, "extra light"))
 | |
| 				a->choice.weight = 200;
 | |
| 			else
 | |
| 				if (castrstr(a->choice.family_name, "light"))
 | |
| 					a->choice.weight = 300;
 | |
| 				else
 | |
| 					a->choice.weight = 400;
 | |
| 
 | |
| 	a->choice.fixed_height = lws_ser_ru16be(data + MCUFO16_LINE_HEIGHT);
 | |
| 
 | |
| 	a->data = data;
 | |
| 	a->data_len = data_len;
 | |
| 	a->renderer = lws_display_font_mcufont_render;
 | |
| 	a->image_glyph = lws_display_font_mcufont_image_glyph;
 | |
| 
 | |
| 	{
 | |
| 		lws_dlo_text_t t;
 | |
| 
 | |
| 		memset(&t, 0, sizeof(t));
 | |
| 		t.font = a;
 | |
| 
 | |
| 		lws_display_font_mcufont_getcwidth(&t, 'm', &a->em);
 | |
| 		a->ex.whole = a->choice.fixed_height;
 | |
| 		a->ex.frac = 0;
 | |
| 	}
 | |
| 
 | |
| 	lws_dll2_clear(&a->list);
 | |
| 	lws_dll2_add_tail(&a->list, &cx->fonts);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| lws_font_destroy(struct lws_dll2 *d, void *user)
 | |
| {
 | |
| 	lws_free(d);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void
 | |
| lws_fonts_destroy(struct lws_context *cx)
 | |
| {
 | |
| 	lws_dll2_foreach_safe(&cx->fonts, NULL, lws_font_destroy);
 | |
| }
 | |
| 
 | |
| struct track {
 | |
| 	const lws_font_choice_t 	*hints;
 | |
| 	const lws_display_font_t	*best;
 | |
| 	int				best_score;
 | |
| };
 | |
| 
 | |
| static int
 | |
| lws_fonts_score(struct lws_dll2 *d, void *user)
 | |
| {
 | |
| 	const lws_display_font_t *f = lws_container_of(d, lws_display_font_t,
 | |
| 						       list);
 | |
| 	struct track *t = (struct track *)user;
 | |
| 	struct lws_tokenize ts;
 | |
| 	int score = 1000;
 | |
| 
 | |
| 	if (t->hints->family_name) {
 | |
| 		memset(&ts, 0, sizeof(ts));
 | |
| 		ts.start = t->hints->family_name;
 | |
| 		ts.len = strlen(ts.start);
 | |
| 		ts.flags = LWS_TOKENIZE_F_COMMA_SEP_LIST;
 | |
| 
 | |
| 		do {
 | |
| 			ts.e = (int8_t)lws_tokenize(&ts);
 | |
| 			if (ts.e == LWS_TOKZE_TOKEN) {
 | |
| 				if (!strncmp(f->choice.family_name, ts.token,
 | |
| 					     ts.token_len)) {
 | |
| 					score = 0;
 | |
| 					break;
 | |
| 				}
 | |
| 
 | |
| 				if (f->choice.generic_name &&
 | |
| 				    !strncmp(f->choice.generic_name, ts.token,
 | |
| 							     ts.token_len)) {
 | |
| 					score -= 500;
 | |
| 					break;
 | |
| 				}
 | |
| 
 | |
| 			}
 | |
| 
 | |
| 		} while (ts.e > 0);
 | |
| 	}
 | |
| 
 | |
| 	if (t->hints->weight)
 | |
| 		score += (t->hints->weight > f->choice.weight ?
 | |
| 			(t->hints->weight - f->choice.weight) :
 | |
| 			(f->choice.weight - t->hints->weight)) / 100;
 | |
| 
 | |
| 	if (t->hints->style != f->choice.style)
 | |
| 		score += 100;
 | |
| 
 | |
| 	if (t->hints->fixed_height)
 | |
| 		score += 10 * (t->hints->fixed_height > f->choice.fixed_height ?
 | |
| 				(t->hints->fixed_height - f->choice.fixed_height) :
 | |
| 				(f->choice.fixed_height - t->hints->fixed_height));
 | |
| 
 | |
| 	if (score < t->best_score) {
 | |
| 		t->best_score = score;
 | |
| 		t->best = f;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| const lws_display_font_t *
 | |
| lws_font_choose(struct lws_context *cx, const lws_font_choice_t *hints)
 | |
| {
 | |
| 	struct track t;
 | |
| 
 | |
| 	t.hints			= hints;
 | |
| 	t.best			= (const lws_display_font_t *)cx->fonts.head;
 | |
| 	t.best_score		= 99999999;
 | |
| 
 | |
| 	if (t.hints)
 | |
| 		lws_dll2_foreach_safe(&cx->fonts, &t, lws_fonts_score);
 | |
| 
 | |
| 	return t.best;
 | |
| }
 |