/src/libwebsockets/lib/misc/dlo/dlo-text.c
Line | Count | Source |
1 | | /* |
2 | | * lws abstract display |
3 | | * |
4 | | * Copyright (C) 2019 - 2022 Andy Green <andy@warmcat.com> |
5 | | * |
6 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | | * of this software and associated documentation files (the "Software"), to |
8 | | * deal in the Software without restriction, including without limitation the |
9 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
10 | | * sell copies of the Software, and to permit persons to whom the Software is |
11 | | * furnished to do so, subject to the following conditions: |
12 | | * |
13 | | * The above copyright notice and this permission notice shall be included in |
14 | | * all copies or substantial portions of the Software. |
15 | | * |
16 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
21 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
22 | | * IN THE SOFTWARE. |
23 | | * |
24 | | * Display List Object: text |
25 | | */ |
26 | | |
27 | | #include <private-lib-core.h> |
28 | | #include "private-lib-drivers-display-dlo.h" |
29 | | |
30 | | size_t |
31 | | utf8_bytes(uint8_t u) |
32 | 0 | { |
33 | 0 | if ((u & 0x80) == 0) |
34 | 0 | return 1; |
35 | | |
36 | 0 | if ((u & 0xe0) == 0xc0) |
37 | 0 | return 2; |
38 | | |
39 | 0 | if ((u & 0xf0) == 0xe0) |
40 | 0 | return 3; |
41 | | |
42 | 0 | if ((u & 0xf8) == 0xf0) |
43 | 0 | return 4; |
44 | | |
45 | 0 | return 0; |
46 | 0 | } |
47 | | |
48 | | static int |
49 | | utf8_unicode(const char *utf8, size_t *utf8_len, uint32_t *unicode) |
50 | 0 | { |
51 | 0 | size_t glyph_len = utf8_bytes((uint8_t)*utf8); |
52 | 0 | size_t n; |
53 | |
|
54 | 0 | if (!glyph_len || glyph_len > *utf8_len) { |
55 | 0 | (*utf8_len)--; |
56 | 0 | return 1; |
57 | 0 | } |
58 | | |
59 | 0 | if (glyph_len == 1) |
60 | 0 | *unicode = (uint32_t)*utf8++; |
61 | 0 | else { |
62 | 0 | *unicode = (uint32_t)((*utf8++) & (0x7f >> glyph_len)); |
63 | 0 | for (n = 1; n < glyph_len; n++) |
64 | 0 | *unicode = (*unicode << 6) | ((*utf8++) & 0x3f); |
65 | 0 | } |
66 | |
|
67 | 0 | *utf8_len -= glyph_len; |
68 | |
|
69 | 0 | return 0; |
70 | 0 | } |
71 | | |
72 | | void |
73 | | lws_display_dlo_text_destroy(struct lws_dlo *dlo) |
74 | 0 | { |
75 | 0 | lws_dlo_text_t *text = lws_container_of(dlo, lws_dlo_text_t, dlo); |
76 | |
|
77 | 0 | lws_free_set_NULL(text->kern); |
78 | 0 | lws_free_set_NULL(text->text); |
79 | |
|
80 | 0 | lwsac_free(&text->ac_glyphs); |
81 | 0 | } |
82 | | |
83 | | int |
84 | | lws_display_dlo_text_update(lws_dlo_text_t *text, lws_display_colour_t dc, |
85 | | lws_fx_t indent, const char *utf8, size_t text_len) |
86 | 0 | { |
87 | 0 | const char *last_utf8 = utf8, *outf8 = utf8; |
88 | 0 | size_t last_bp_n = 0, tlen = text_len; |
89 | 0 | lws_fx_t t1, eff, last_bp_eff, t2; |
90 | 0 | uint8_t r = 0; |
91 | 0 | char uc; |
92 | |
|
93 | 0 | if (text->kern) |
94 | 0 | lws_free_set_NULL(text->kern); |
95 | |
|
96 | 0 | if (text->text) |
97 | 0 | lws_free_set_NULL(text->text); |
98 | |
|
99 | 0 | lws_dll2_owner_clear(&text->glyphs); |
100 | 0 | lwsac_free(&text->ac_glyphs); |
101 | |
|
102 | 0 | text->indent = indent; |
103 | 0 | text->dlo.dc = dc; |
104 | |
|
105 | 0 | lws_fx_set(eff, 0, 0); |
106 | | |
107 | | /* |
108 | | * Let's go through the new string glyph by glyph, we want to |
109 | | * calculate effective kerned widths, and optionally deal with wrapping. |
110 | | * |
111 | | * But we don't want to instantiate the glyph objects until we are |
112 | | * engaged with rendering them. Otherwise we will carry around the |
113 | | * whole page-worth's of glyphs at once needlessly, which won't scale |
114 | | * for text-heavy pages. lws_display_dlo_text_attach_glyphs() does the |
115 | | * same flow as this but to create the glyphs and is called later |
116 | | * as the text dlo becomes rasterized during rendering. |
117 | | */ |
118 | | |
119 | | /* { char b1[22]; lwsl_err("eff %s\n", lws_fx_string(&eff, b1, sizeof(b1))); } |
120 | | { char b1[22]; lwsl_err("indent %s\n", lws_fx_string(&indent, b1, sizeof(b1))); } |
121 | | { char b1[22]; lwsl_err("boxw %s\n", lws_fx_string(&text->dlo.box.w, b1, sizeof(b1))); } */ |
122 | |
|
123 | 0 | while (tlen && |
124 | 0 | lws_fx_comp(lws_fx_add(&t1, &eff, &indent), &text->dlo.box.w) < 0) { |
125 | 0 | size_t ot = tlen; |
126 | 0 | uint32_t unicode; |
127 | |
|
128 | 0 | if (!utf8_unicode(utf8, &tlen, &unicode)) { |
129 | 0 | text->font->image_glyph(text, unicode, 0); |
130 | |
|
131 | 0 | uc = *utf8; |
132 | 0 | utf8 += (ot - tlen); |
133 | |
|
134 | 0 | if (uc == ' ') { /* act to snip it if used */ |
135 | 0 | last_utf8 = utf8; |
136 | 0 | last_bp_n = tlen; |
137 | 0 | last_bp_eff = eff; |
138 | 0 | } |
139 | |
|
140 | 0 | if (!lws_display_font_mcufont_getcwidth(text, unicode, &t2)) |
141 | 0 | lws_fx_add(&eff, &eff, &t2); |
142 | |
|
143 | 0 | if (uc == '-' || uc == ',' || uc == ';' || uc == ':') { |
144 | | /* act to leave it in */ |
145 | 0 | last_utf8 = utf8; |
146 | 0 | last_bp_n = tlen; |
147 | 0 | last_bp_eff = eff; |
148 | 0 | } |
149 | 0 | } else |
150 | 0 | lwsl_err("%s: missing glyph\n", __func__); |
151 | 0 | } |
152 | |
|
153 | 0 | if (last_bp_n && |
154 | 0 | lws_fx_comp(lws_fx_add(&t1, &eff, &indent), &text->dlo.box.w) >= 0) { |
155 | 0 | eff = last_bp_eff; |
156 | 0 | utf8 = last_utf8; |
157 | 0 | tlen = last_bp_n; |
158 | 0 | r = 1; |
159 | 0 | } |
160 | |
|
161 | 0 | text->text_len = text_len - tlen; |
162 | 0 | if (tlen == text_len) { |
163 | 0 | lwsl_notice("we couldn't fit anything in there, newline\n"); |
164 | 0 | return 2; |
165 | 0 | } |
166 | | |
167 | 0 | text->text = lws_malloc(text->text_len + 1, __func__); |
168 | 0 | if (!text->text) |
169 | 0 | return -1; |
170 | | |
171 | 0 | memcpy(text->text, outf8, text->text_len); |
172 | 0 | text->text[text->text_len] = '\0'; |
173 | |
|
174 | 0 | memset(&text->bounding_box, 0, sizeof(text->bounding_box)); |
175 | 0 | text->bounding_box.w = eff; |
176 | 0 | text->bounding_box.h.whole = text->font_height; |
177 | 0 | text->bounding_box.h.frac = 0; |
178 | |
|
179 | 0 | return r; |
180 | 0 | } |
181 | | |
182 | | int |
183 | | lws_display_dlo_text_attach_glyphs(lws_dlo_text_t *text) |
184 | 0 | { |
185 | 0 | const char *utf8 = text->text; |
186 | 0 | size_t tlen = text->text_len; |
187 | 0 | lws_font_glyph_t *g = NULL; |
188 | 0 | uint32_t unicode; |
189 | 0 | lws_fx_t eff; |
190 | 0 | uint8_t r = 0; |
191 | |
|
192 | 0 | lws_fx_set(eff, 0, 0); |
193 | |
|
194 | 0 | while (tlen) { |
195 | 0 | size_t ot = tlen; |
196 | |
|
197 | 0 | g = NULL; |
198 | 0 | if (!utf8_unicode(utf8, &tlen, &unicode)) |
199 | | /* instantiate the glyphs this time */ |
200 | 0 | g = text->font->image_glyph(text, unicode, 1); |
201 | 0 | if (g == NULL) { |
202 | 0 | lwsl_warn("%s: no glyph for 0x%02X '%c'\n", __func__, (unsigned int)*utf8, *utf8); |
203 | 0 | break; |
204 | 0 | } |
205 | | |
206 | 0 | utf8 += (ot - tlen); |
207 | 0 | g->xpx = eff; |
208 | 0 | lws_fx_add(&eff, &eff, &g->cwidth); |
209 | 0 | } |
210 | |
|
211 | 0 | return r; |
212 | 0 | } |
213 | | |
214 | | lws_dlo_text_t * |
215 | | lws_display_dlo_text_new(lws_displaylist_t *dl, lws_dlo_t *dlo_parent, |
216 | | lws_box_t *box, const lws_display_font_t *font) |
217 | 0 | { |
218 | 0 | lws_dlo_text_t *text = lws_zalloc(sizeof(*text), __func__); |
219 | |
|
220 | 0 | if (!text) |
221 | 0 | return NULL; |
222 | | |
223 | 0 | text->dlo.render = font->renderer; |
224 | 0 | text->dlo._destroy = lws_display_dlo_text_destroy; |
225 | 0 | text->dlo.box = *box; |
226 | 0 | text->font = font; |
227 | |
|
228 | 0 | lws_display_dlo_add(dl, dlo_parent, &text->dlo); |
229 | |
|
230 | 0 | return text; |
231 | 0 | } |
232 | | |
233 | | static const char * |
234 | | castrstr(const char *haystack, const char *needle) |
235 | 0 | { |
236 | 0 | size_t sn = strlen(needle), h = strlen(haystack) - sn + 1, n; |
237 | 0 | char c, c1; |
238 | |
|
239 | 0 | while (1) { |
240 | 0 | for (n = 0; n < sn; n++) { |
241 | 0 | c = (char)((haystack[h + n] >= 'A' && haystack[h + n] <= 'Z') ? |
242 | 0 | haystack[h + n] + ('a' - 'A') : haystack[h + n]); |
243 | 0 | c1 = (char)((needle[n] >= 'A' && needle[n] <= 'Z') ? |
244 | 0 | needle[n] + ('a' - 'A') : needle[n]); |
245 | 0 | if (c != c1) |
246 | 0 | break; |
247 | 0 | } |
248 | 0 | if (n == sn) |
249 | 0 | return &haystack[h]; |
250 | | |
251 | 0 | if (!h) |
252 | 0 | break; |
253 | 0 | h--; |
254 | 0 | } |
255 | | |
256 | 0 | return NULL; |
257 | 0 | } |
258 | | |
259 | | int |
260 | | lws_font_register(struct lws_context *cx, const uint8_t *data, size_t data_len) |
261 | 0 | { |
262 | 0 | lws_display_font_t *a; |
263 | |
|
264 | 0 | if (lws_ser_ru32be(data) != LWS_FOURCC('M', 'C', 'U', 'F')) |
265 | 0 | return 1; |
266 | | |
267 | 0 | a = lws_zalloc(sizeof(*a), __func__); |
268 | 0 | if (!a) |
269 | 0 | return 1; |
270 | | |
271 | 0 | a->choice.family_name = (const char *)data + |
272 | 0 | lws_ser_ru32be(data + MCUFO_FOFS_FULLNAME); |
273 | |
|
274 | 0 | if (castrstr(a->choice.family_name, "serif") || |
275 | 0 | castrstr(a->choice.family_name, "roman")) |
276 | 0 | a->choice.generic_name = "serif"; |
277 | 0 | else |
278 | 0 | a->choice.generic_name = "sans"; |
279 | |
|
280 | 0 | if (castrstr(a->choice.family_name, "italic") || |
281 | 0 | castrstr(a->choice.family_name, "oblique")) |
282 | 0 | a->choice.style = 1; |
283 | |
|
284 | 0 | if (castrstr(a->choice.family_name, "extrabold") || |
285 | 0 | castrstr(a->choice.family_name, "extra bold")) |
286 | 0 | a->choice.weight = 900; |
287 | 0 | else |
288 | 0 | if (castrstr(a->choice.family_name, "bold")) |
289 | 0 | a->choice.weight = 700; |
290 | 0 | else |
291 | 0 | if (castrstr(a->choice.family_name, "extralight") || |
292 | 0 | castrstr(a->choice.family_name, "extra light")) |
293 | 0 | a->choice.weight = 200; |
294 | 0 | else |
295 | 0 | if (castrstr(a->choice.family_name, "light")) |
296 | 0 | a->choice.weight = 300; |
297 | 0 | else |
298 | 0 | a->choice.weight = 400; |
299 | |
|
300 | 0 | a->choice.fixed_height = lws_ser_ru16be(data + MCUFO16_LINE_HEIGHT); |
301 | |
|
302 | 0 | a->data = data; |
303 | 0 | a->data_len = data_len; |
304 | 0 | a->renderer = lws_display_font_mcufont_render; |
305 | 0 | a->image_glyph = lws_display_font_mcufont_image_glyph; |
306 | |
|
307 | 0 | { |
308 | 0 | lws_dlo_text_t t; |
309 | |
|
310 | 0 | memset(&t, 0, sizeof(t)); |
311 | 0 | t.font = a; |
312 | |
|
313 | 0 | lws_display_font_mcufont_getcwidth(&t, 'm', &a->em); |
314 | 0 | a->ex.whole = a->choice.fixed_height; |
315 | 0 | a->ex.frac = 0; |
316 | 0 | } |
317 | |
|
318 | 0 | lws_dll2_clear(&a->list); |
319 | 0 | lws_dll2_add_tail(&a->list, &cx->fonts); |
320 | |
|
321 | 0 | return 0; |
322 | 0 | } |
323 | | |
324 | | static int |
325 | | lws_font_destroy(struct lws_dll2 *d, void *user) |
326 | 0 | { |
327 | 0 | lws_free(d); |
328 | 0 | return 0; |
329 | 0 | } |
330 | | |
331 | | void |
332 | | lws_fonts_destroy(struct lws_context *cx) |
333 | 0 | { |
334 | 0 | lws_dll2_foreach_safe(&cx->fonts, NULL, lws_font_destroy); |
335 | 0 | } |
336 | | |
337 | | struct track { |
338 | | const lws_font_choice_t *hints; |
339 | | const lws_display_font_t *best; |
340 | | int best_score; |
341 | | }; |
342 | | |
343 | | static int |
344 | | lws_fonts_score(struct lws_dll2 *d, void *user) |
345 | 0 | { |
346 | 0 | const lws_display_font_t *f = lws_container_of(d, lws_display_font_t, |
347 | 0 | list); |
348 | 0 | struct track *t = (struct track *)user; |
349 | 0 | struct lws_tokenize ts; |
350 | 0 | int score = 1000; |
351 | |
|
352 | 0 | if (t->hints->family_name) { |
353 | 0 | memset(&ts, 0, sizeof(ts)); |
354 | 0 | ts.start = t->hints->family_name; |
355 | 0 | ts.len = strlen(ts.start); |
356 | 0 | ts.flags = LWS_TOKENIZE_F_COMMA_SEP_LIST; |
357 | |
|
358 | 0 | do { |
359 | 0 | ts.e = (int8_t)lws_tokenize(&ts); |
360 | 0 | if (ts.e == LWS_TOKZE_TOKEN) { |
361 | 0 | if (!strncmp(f->choice.family_name, ts.token, |
362 | 0 | ts.token_len)) { |
363 | 0 | score = 0; |
364 | 0 | break; |
365 | 0 | } |
366 | | |
367 | 0 | if (f->choice.generic_name && |
368 | 0 | !strncmp(f->choice.generic_name, ts.token, |
369 | 0 | ts.token_len)) { |
370 | 0 | score -= 500; |
371 | 0 | break; |
372 | 0 | } |
373 | |
|
374 | 0 | } |
375 | |
|
376 | 0 | } while (ts.e > 0); |
377 | 0 | } |
378 | |
|
379 | 0 | if (t->hints->weight) |
380 | 0 | score += (t->hints->weight > f->choice.weight ? |
381 | 0 | (t->hints->weight - f->choice.weight) : |
382 | 0 | (f->choice.weight - t->hints->weight)) / 100; |
383 | |
|
384 | 0 | if (t->hints->style != f->choice.style) |
385 | 0 | score += 100; |
386 | |
|
387 | 0 | if (t->hints->fixed_height) |
388 | 0 | score += 10 * (t->hints->fixed_height > f->choice.fixed_height ? |
389 | 0 | (t->hints->fixed_height - f->choice.fixed_height) : |
390 | 0 | (f->choice.fixed_height - t->hints->fixed_height)); |
391 | |
|
392 | 0 | if (score < t->best_score) { |
393 | 0 | t->best_score = score; |
394 | 0 | t->best = f; |
395 | 0 | } |
396 | |
|
397 | 0 | return 0; |
398 | 0 | } |
399 | | |
400 | | const lws_display_font_t * |
401 | | lws_font_choose(struct lws_context *cx, const lws_font_choice_t *hints) |
402 | 0 | { |
403 | 0 | struct track t; |
404 | |
|
405 | 0 | t.hints = hints; |
406 | 0 | t.best = (const lws_display_font_t *)cx->fonts.head; |
407 | 0 | t.best_score = 99999999; |
408 | |
|
409 | 0 | if (t.hints) |
410 | 0 | lws_dll2_foreach_safe(&cx->fonts, &t, lws_fonts_score); |
411 | |
|
412 | 0 | return t.best; |
413 | 0 | } |