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;
|
|
}
|