mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-24 01:39:33 +00:00
e3dca87f23
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.
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;
|
|
}
|