libwebsockets/lib/misc/dlo/dlo-text.c
Andy Green e3dca87f23 lws_display: add display list / DLO support
This adds optional display list support to lws_display, using DLOs (Display
List Objects).  DLOs for rectangle / rounded rectangle (with circle as the
degenerate case), PNGs, JPEG and compressed, antialiased bitmapped fonts
and text primitives are provided.

Logical DLOs are instantiated on heap and listed into an lws_display_list
owner, DLOs handle attributes like position, bounding box, colour +
opacity, and local error diffusion backing buffer.

When the display list is complete, it can be rasterized a line at a time,
with scoped error diffusion resolved, such that no allocation for the
framebuffer is required at any point.  DLOs are freed as the rasterization
moves beyond their bounding box.

Adds a platform registry binding names and other metadata to lws_display
fonts / PNGs / JPEGs.  Provides registration, destruction and best match
selection apis.
2022-03-25 08:18:29 +00:00

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