1 #include "fcft/fcft.h"
2
3 #include <stdlib.h>
4 #include <stdint.h>
5 #include <stdbool.h>
6 #include <wchar.h>
7 #include <math.h>
8 #include <assert.h>
9 #include <threads.h>
10 #include <locale.h>
11
12 #include <pthread.h>
13
14 #include <ft2build.h>
15 #include FT_FREETYPE_H
16 #include FT_MODULE_H
17 #include FT_LCD_FILTER_H
18 #include FT_TRUETYPE_TABLES_H
19 #include FT_SYNTHESIS_H
20 #include <fontconfig/fontconfig.h>
21
22 #if defined(FCFT_HAVE_HARFBUZZ)
23 #include <harfbuzz/hb.h>
24 #include <harfbuzz/hb-ft.h>
25 #endif
26 #if defined(FCFT_HAVE_UTF8PROC)
27 #include <utf8proc.h>
28 #endif
29
30 #include <tllist.h>
31
32 #define LOG_MODULE "fcft"
33 #define LOG_ENABLE_DBG 0
34 #include "log.h"
35 #include "fcft/stride.h"
36
37 #include "emoji-data.h"
38 #include "unicode-compose-table.h"
39 #include "version.h"
40
41 static_assert(sizeof(wchar_t) >= 4, "wchar_t is not wide enough for Unicode");
42
43 #define min(x, y) ((x) < (y) ? (x) : (y))
44 #define max(x, y) ((x) > (y) ? (x) : (y))
45 #define ALEN(v) (sizeof(v) / sizeof((v)[0]))
46
47 static FT_Error ft_lib_err;
48 static FT_Library ft_lib;
49 static mtx_t ft_lock;
50 static bool can_set_lcd_filter = false;
51 static enum fcft_scaling_filter scaling_filter = FCFT_SCALING_FILTER_CUBIC;
52
53 static const size_t glyph_cache_initial_size = 256;
54 #if defined(FCFT_HAVE_HARFBUZZ)
55 static const size_t grapheme_cache_initial_size = 256;
56 #endif
57
58 #if defined(_DEBUG)
59 static size_t glyph_cache_lookups = 0;
60 static size_t glyph_cache_collisions = 0;
61
62 #if defined(FCFT_HAVE_HARFBUZZ)
63 static size_t grapheme_cache_lookups = 0;
64 static size_t grapheme_cache_collisions = 0;
65 #endif
66 #endif
67
68 struct glyph_priv {
69 struct fcft_glyph public;
70 enum fcft_subpixel subpixel;
71 bool valid;
72 };
73
74 struct grapheme_priv {
75 struct fcft_grapheme public;
76
77 size_t len;
78 wchar_t *cluster; /* ‘len’ characters */
79
80 enum fcft_subpixel subpixel;
81 bool valid;
82 };
83
84 struct instance {
85 char *path;
86 FT_Face face;
87 int load_flags;
88
89 #if defined(FCFT_HAVE_HARFBUZZ)
90 hb_font_t *hb_font;
91 hb_buffer_t *hb_buf;
92 hb_feature_t hb_feats[32];
93 size_t hb_feats_count;
94 #endif
95
96 bool antialias;
97 bool embolden;
98 bool is_color;
99 int render_flags_normal;
100 int render_flags_subpixel;
101
102 FT_LcdFilter lcd_filter;
103
104 double pixel_size_fixup; /* Scale factor - should only be used with ARGB32 glyphs */
105 bool pixel_fixup_estimated;
106 bool bgr; /* True for FC_RGBA_BGR and FC_RGBA_VBGR */
107
108 struct fcft_font metrics;
109 };
110
111 struct fallback {
112 FcPattern *pattern;
113 FcCharSet *charset;
114 FcLangSet *langset;
115 struct instance *font;
116
117 /* User-requested size(s) - i.e. sizes from *base* pattern */
118 double req_pt_size;
119 double req_px_size;
120 };
121
122 struct font_priv {
123 /* Must be first */
124 struct fcft_font public;
125
126 mtx_t lock;
127 pthread_rwlock_t glyph_cache_lock;
128 struct {
129 struct glyph_priv **table;
130 size_t size;
131 size_t count;
132 } glyph_cache;
133
134 #if defined(FCFT_HAVE_HARFBUZZ)
135 pthread_rwlock_t grapheme_cache_lock;
136 struct {
137 struct grapheme_priv **table;
138 size_t size;
139 size_t count;
140 } grapheme_cache;
141 #endif
142
143 tll(struct fallback) fallbacks;
144 enum fcft_emoji_presentation emoji_presentation;
145 size_t ref_counter;
146 };
147
148 /* Global font cache */
149 struct fcft_font_cache_entry {
150 uint64_t hash;
151 struct font_priv *font;
152
153 int waiters;
154 cnd_t cond;
155 };
156 static tll(struct fcft_font_cache_entry) font_cache = tll_init();
157 static mtx_t font_cache_lock;
158
159 FCFT_EXPORT enum fcft_capabilities
fcft_capabilities(void)160 fcft_capabilities(void)
161 {
162 enum fcft_capabilities ret = 0;
163
164 #if defined(FCFT_HAVE_HARFBUZZ)
165 ret |= FCFT_CAPABILITY_GRAPHEME_SHAPING;
166 #endif
167 #if defined(FCFT_HAVE_HARFBUZZ) && defined(FCFT_HAVE_UTF8PROC)
168 ret |= FCFT_CAPABILITY_TEXT_RUN_SHAPING;
169 #endif
170
171 return ret;
172 }
173
174 static const char *
ft_error_string(FT_Error err)175 ft_error_string(FT_Error err)
176 {
177 #undef FTERRORS_H_
178 #undef __FTERRORS_H__
179 #define FT_ERRORDEF( e, v, s ) case e: return s;
180 #define FT_ERROR_START_LIST switch (err) {
181 #define FT_ERROR_END_LIST }
182 #include FT_ERRORS_H
183 return "unknown error";
184 }
185
186 static void __attribute__((constructor))
init(void)187 init(void)
188 {
189 FcInit();
190 ft_lib_err = FT_Init_FreeType(&ft_lib);
191
192 /*
193 * Some FreeType builds use the older ClearType-style subpixel
194 * rendering. This mode requires an LCD filter to be set, or it
195 * will produce *severe* color fringes.
196 *
197 * Which subpixel rendering mode to use depends on a FreeType
198 * build-time #define, FT_CONFIG_OPTION_SUBPIXEL_RENDERING. By
199 * default, it is *unset*, meaning FreeType will use the newer
200 * 'Harmony' subpixel rendering mode (i.e. disabling
201 * FT_CONFIG_OPTION_SUBPIXEL_RENDERING does *not* disable subpixel
202 * rendering).
203 *
204 * If defined, ClearType-style subpixel rendering is instead enabled.
205 *
206 * The FT_Library_SetLcdFilter() configures a library instance
207 * global LCD filter. This call will *fail* if
208 * FT_CONFIG_OPTION_SUBPIXEL_RENDERING has not been set.
209 *
210 * In theory, different fonts can have different LCD filters, and
211 * for this reason we need to set the filter *each* time we need
212 * to render a glyph. Since FT_Library_SetLcdFilter() is per
213 * library instance, this means taking the library global lock.
214 *
215 * For performance reasons, we want to skip this altogether if we
216 * know that the call will fail (i.e. when FreeType is using
217 * Harmony). So, detect here in init(), whether we can set an LCD
218 * filter or not.
219 *
220 * When rendering a glyph, we will only configure the LCD filter
221 * (and thus take the library global lock) if we know we *can* set
222 * the filter, and if we need to (the rendering mode includes
223 * FT_RENDER_MODE_LCD{,_V}).
224 */
225
226 FT_Error err = FT_Library_SetLcdFilter(ft_lib, FT_LCD_FILTER_DEFAULT);
227 can_set_lcd_filter = err == 0;
228 LOG_DBG("can set LCD filter: %s (%d)", ft_error_string(err), err);
229
230 if (can_set_lcd_filter)
231 FT_Library_SetLcdFilter(ft_lib, FT_LCD_FILTER_NONE);
232
233 #if defined(FCFT_HAVE_HARFBUZZ)
234 /* Not thread safe first time called */
235 hb_language_get_default();
236 #endif
237
238 mtx_init(&ft_lock, mtx_plain);
239 mtx_init(&font_cache_lock, mtx_plain);
240 }
241
242 static void __attribute__((destructor))
fini(void)243 fini(void)
244 {
245 while (tll_length(font_cache) > 0) {
246 if (tll_front(font_cache).font == NULL)
247 tll_pop_front(font_cache);
248 else
249 fcft_destroy(&tll_front(font_cache).font->public);
250 }
251
252 assert(tll_length(font_cache) == 0);
253
254 mtx_destroy(&font_cache_lock);
255 mtx_destroy(&ft_lock);
256
257 if (ft_lib_err == FT_Err_Ok)
258 FT_Done_FreeType(ft_lib);
259
260 FcFini();
261
262 LOG_DBG("glyph cache: lookups=%zu, collisions=%zu",
263 glyph_cache_lookups, glyph_cache_collisions);
264
265 #if defined(FCFT_HAVE_HARFBUZZ)
266 LOG_DBG("grapheme cache: lookups=%zu, collisions=%zu",
267 grapheme_cache_lookups, grapheme_cache_collisions);
268 #endif
269 }
270
271 static bool
is_assertions_enabled(void)272 is_assertions_enabled(void)
273 {
274 #if defined(NDEBUG)
275 return false;
276 #else
277 return true;
278 #endif
279 }
280
281 static void
log_version_information(void)282 log_version_information(void)
283 {
284 static bool has_already_logged = false;
285
286 mtx_lock(&font_cache_lock);
287 if (has_already_logged) {
288 mtx_unlock(&font_cache_lock);
289 return;
290 }
291 has_already_logged = true;
292 mtx_unlock(&font_cache_lock);
293
294 enum fcft_capabilities caps = fcft_capabilities();
295
296 static char caps_str[256];
297 snprintf(
298 caps_str, sizeof(caps_str),
299 "%cgraphemes %cruns %cassertions",
300 caps & FCFT_CAPABILITY_GRAPHEME_SHAPING ? '+' : '-',
301 caps & FCFT_CAPABILITY_TEXT_RUN_SHAPING ? '+' : '-',
302 is_assertions_enabled() ? '+' : '-');
303
304 LOG_INFO("fcft: %s %s", FCFT_VERSION, caps_str);
305
306 {
307 int raw_version = FcGetVersion();
308
309 /* See FC_VERSION in <fontconfig/fontconfig.h> */
310 const int major = raw_version / 10000; raw_version %= 10000;
311 const int minor = raw_version / 100; raw_version %= 100;
312 const int patch = raw_version;
313
314 LOG_INFO("fontconfig: %d.%d.%d", major, minor, patch);
315 }
316
317 {
318 int major, minor, patch;
319 FT_Library_Version(ft_lib, &major, &minor, &patch);
320 LOG_INFO("freetype: %d.%d.%d", major, minor, patch);
321 }
322
323 }
324
325 FCFT_EXPORT bool
fcft_set_scaling_filter(enum fcft_scaling_filter filter)326 fcft_set_scaling_filter(enum fcft_scaling_filter filter)
327 {
328 switch (filter) {
329 case FCFT_SCALING_FILTER_NONE:
330 case FCFT_SCALING_FILTER_NEAREST:
331 case FCFT_SCALING_FILTER_BILINEAR:
332 case FCFT_SCALING_FILTER_CUBIC:
333 case FCFT_SCALING_FILTER_LANCZOS3:
334 scaling_filter = filter;
335 return true;
336 }
337
338 return false;
339 }
340
341 static void
glyph_destroy_private(struct glyph_priv * glyph)342 glyph_destroy_private(struct glyph_priv *glyph)
343 {
344 if (glyph->valid) {
345 void *image = pixman_image_get_data(glyph->public.pix);
346 pixman_image_unref(glyph->public.pix);
347 free(image);
348 }
349
350 free(glyph);
351 }
352
353 static void
glyph_destroy(const struct fcft_glyph * glyph)354 glyph_destroy(const struct fcft_glyph *glyph)
355 {
356 glyph_destroy_private((struct glyph_priv *)glyph);
357 }
358
359 static void
instance_destroy(struct instance * inst)360 instance_destroy(struct instance *inst)
361 {
362 if (inst == NULL)
363 return;
364
365 #if defined(FCFT_HAVE_HARFBUZZ)
366 hb_font_destroy(inst->hb_font);
367 hb_buffer_destroy(inst->hb_buf);
368 #endif
369
370 mtx_lock(&ft_lock);
371 FT_Done_Face(inst->face);
372 mtx_unlock(&ft_lock);
373
374 free(inst->path);
375 free(inst);
376 }
377
378 static void
fallback_destroy(struct fallback * fallback)379 fallback_destroy(struct fallback *fallback)
380 {
381 FcPatternDestroy(fallback->pattern);
382 FcCharSetDestroy(fallback->charset);
383 if (fallback->langset != NULL)
384 FcLangSetDestroy(fallback->langset);
385 instance_destroy(fallback->font);
386 }
387
388 static void
underline_strikeout_metrics(FT_Face ft_face,struct fcft_font * font)389 underline_strikeout_metrics(FT_Face ft_face, struct fcft_font *font)
390 {
391 double y_scale = ft_face->size->metrics.y_scale / 65536.;
392 double ascent = ft_face->size->metrics.ascender / 64.;
393 double descent = ft_face->size->metrics.descender / 64.;
394
395 double underline_position = ft_face->underline_position * y_scale / 64.;
396 double underline_thickness = ft_face->underline_thickness * y_scale / 64.;
397
398 if (underline_position == 0.) {
399 LOG_DBG("estimating underline position and thickness");
400 underline_thickness = fabs(descent / 5.);
401
402 //underline_position = descent / 2.; /* Alacritty's algorithm */
403 underline_position = -2 * underline_thickness; /* My own */
404 }
405
406 /*
407 * Position refers to the line's center, thus we need to take the
408 * thickness into account to determine the line top.
409 *
410 * Since a *negative* number means the position is *under* the
411 * baseline, we need to *add* half the width to adjust the
412 * position "upwards".
413 *
414 * When rounding the thickness, take care not go below 1.0 as that
415 * would make it invisible.
416 */
417 font->underline.position = trunc(underline_position + underline_thickness / 2.);
418 font->underline.thickness = round(max(1., underline_thickness));
419
420 LOG_DBG("underline: pos=%f, thick=%f -> pos=%f, pos=%d, thick=%d",
421 underline_position, underline_thickness,
422 underline_position + underline_thickness / 2.,
423 font->underline.position, font->underline.thickness);
424
425 double strikeout_position = 0., strikeout_thickness = 0.;
426 TT_OS2 *os2 = FT_Get_Sfnt_Table(ft_face, ft_sfnt_os2);
427 if (os2 != NULL) {
428 strikeout_position = os2->yStrikeoutPosition * y_scale / 64.;
429 strikeout_thickness = os2->yStrikeoutSize * y_scale / 64.;
430 }
431
432 if (strikeout_position == 0.) {
433 LOG_DBG("estimating strikeout position and thickness");
434 strikeout_thickness = underline_thickness;
435
436 //strikeout_position = height / 2. + descent; /* Alacritty's algorithm */
437 strikeout_position = 3. * ascent / 8. - underline_thickness / 2.; /* xterm's algorithm */
438 }
439
440 font->strikeout.position = trunc(strikeout_position + strikeout_thickness / 2.);
441 font->strikeout.thickness = round(max(1., strikeout_thickness));
442
443 LOG_DBG("strikeout: pos=%f, thick=%f -> pos=%f, pos=%d, thick=%d",
444 strikeout_position, strikeout_thickness,
445 strikeout_position + strikeout_thickness / 2.,
446 font->strikeout.position, font->strikeout.thickness);
447 }
448
449 static FcPattern *
base_pattern_from_name(const char * name,FcFontSet ** set)450 base_pattern_from_name(const char *name, FcFontSet **set)
451 {
452 /* Fontconfig fails to parse floating point values unless locale
453 * is e.g C, or en_US.UTF-8 */
454 assert(strcmp(setlocale(LC_NUMERIC, NULL), "C") == 0);
455 FcPattern *pattern = FcNameParse((const unsigned char *)name);
456
457 if (pattern == NULL) {
458 LOG_ERR("%s: failed to lookup font", name);
459 return NULL;
460 }
461
462 if (!FcConfigSubstitute(NULL, pattern, FcMatchPattern)) {
463 LOG_ERR("%s: failed to do config substitution", name);
464 FcPatternDestroy(pattern);
465 return NULL;
466 }
467
468 FcDefaultSubstitute(pattern);
469
470 FcResult result;
471 *set = FcFontSort(NULL, pattern, FcTrue, NULL, &result);
472 if (result != FcResultMatch) {
473 LOG_ERR("%s: failed to match font", name);
474 FcPatternDestroy(pattern);
475 return NULL;
476 }
477
478 return pattern;
479 }
480
481 static FcPattern *
pattern_from_font_set(FcPattern * base_pattern,FcFontSet * set,size_t idx)482 pattern_from_font_set(FcPattern *base_pattern, FcFontSet *set, size_t idx)
483 {
484 FcPattern *pattern = FcFontRenderPrepare(NULL, base_pattern, set->fonts[idx]);
485 if (pattern == NULL) {
486 LOG_ERR("failed to prepare 'final' pattern");
487 return NULL;
488 }
489
490 return pattern;
491 }
492
493 static bool
instantiate_pattern(FcPattern * pattern,double req_pt_size,double req_px_size,struct instance * font)494 instantiate_pattern(FcPattern *pattern, double req_pt_size, double req_px_size,
495 struct instance *font)
496 {
497 FcChar8 *face_file = NULL;
498 if (FcPatternGetString(pattern, FC_FT_FACE, 0, &face_file) != FcResultMatch &&
499 FcPatternGetString(pattern, FC_FILE, 0, &face_file) != FcResultMatch)
500 {
501 LOG_ERR("no face file path in pattern");
502 return false;
503 }
504
505 double dpi;
506 if (FcPatternGetDouble(pattern, FC_DPI, 0, &dpi) != FcResultMatch)
507 dpi = 75;
508
509 double size;
510 if (FcPatternGetDouble(pattern, FC_SIZE, 0, &size) != FcResultMatch)
511 LOG_WARN("%s: failed to get size", face_file);
512
513 double pixel_size;
514 if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &pixel_size) != FcResultMatch) {
515 LOG_ERR("%s: failed to get pixel size", face_file);
516 return false;
517 }
518
519 int face_index;
520 if (FcPatternGetInteger(pattern, FC_INDEX, 0, &face_index) != FcResultMatch) {
521 LOG_WARN("%s: failed to get face index", face_file);
522 face_index = 0;
523 }
524
525 mtx_lock(&ft_lock);
526 FT_Face ft_face;
527 FT_Error ft_err = FT_New_Face(ft_lib, (const char *)face_file, face_index, &ft_face);
528 mtx_unlock(&ft_lock);
529 if (ft_err != 0) {
530 LOG_ERR("%s: failed to create FreeType face; %s",
531 face_file, ft_error_string(ft_err));
532 return false;
533 }
534
535 if ((ft_err = FT_Set_Pixel_Sizes(ft_face, 0, round(pixel_size))) != 0) {
536 LOG_ERR("%s: failed to set character size: %s",
537 face_file, ft_error_string(ft_err));
538 goto err_done_face;
539 }
540
541 FcBool scalable;
542 if (FcPatternGetBool(pattern, FC_SCALABLE, 0, &scalable) != FcResultMatch)
543 scalable = FcTrue;
544
545 FcBool outline;
546 if (FcPatternGetBool(pattern, FC_OUTLINE, 0, &outline) != FcResultMatch)
547 outline = FcTrue;
548
549 FcBool is_color;
550 if (FcPatternGetBool(pattern, FC_COLOR, 0, &is_color) != FcResultMatch)
551 is_color = FcFalse;
552
553 double pixel_fixup = 1.;
554 bool fixup_estimated = false;
555 if (FcPatternGetDouble(pattern, "pixelsizefixupfactor", 0, &pixel_fixup) != FcResultMatch) {
556 /*
557 * Force a fixup factor on scalable bitmap fonts (typically
558 * emoji fonts). The fixup factor is
559 * requested-pixel-size / actual-pixels-size
560 */
561 if (scalable && !outline) {
562 if (req_px_size < 0.)
563 req_px_size = req_pt_size * dpi / 72.;
564
565 pixel_fixup = req_px_size / ft_face->size->metrics.y_ppem;
566 fixup_estimated = true;
567 LOG_DBG("estimated pixel fixup factor to %f (from pixel size: %f)",
568 pixel_fixup, req_px_size);
569 } else
570 pixel_fixup = 1.;
571 }
572
573 #if 0
574 LOG_DBG("FIXED SIZES: %d", ft_face->num_fixed_sizes);
575 for (int i = 0; i < ft_face->num_fixed_sizes; i++)
576 LOG_DBG(" #%d: height=%d, y_ppem=%f", i, ft_face->available_sizes[i].height, ft_face->available_sizes[i].y_ppem / 64.);
577 #endif
578
579 FcBool fc_hinting;
580 if (FcPatternGetBool(pattern, FC_HINTING,0, &fc_hinting) != FcResultMatch)
581 fc_hinting = FcTrue;
582
583 FcBool fc_antialias;
584 if (FcPatternGetBool(pattern, FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch)
585 fc_antialias = FcTrue;
586
587 int fc_hintstyle;
588 if (FcPatternGetInteger(pattern, FC_HINT_STYLE, 0, &fc_hintstyle) != FcResultMatch)
589 fc_hintstyle = FC_HINT_SLIGHT;
590
591 int fc_rgba;
592 if (FcPatternGetInteger(pattern, FC_RGBA, 0, &fc_rgba) != FcResultMatch)
593 fc_rgba = FC_RGBA_UNKNOWN;
594
595 int load_flags = FT_LOAD_DEFAULT;
596 int load_target = FT_LOAD_TARGET_NORMAL;
597
598 if (!fc_antialias) {
599 if (!fc_hinting || fc_hintstyle == FC_HINT_NONE)
600 load_flags |= FT_LOAD_NO_HINTING;
601 else
602 load_target = FT_LOAD_TARGET_MONO;
603
604 load_flags |= FT_LOAD_MONOCHROME;
605 }
606
607 else {
608 if (!fc_hinting || fc_hintstyle == FC_HINT_NONE)
609 load_flags |= FT_LOAD_NO_HINTING;
610
611 else if (fc_hintstyle == FC_HINT_SLIGHT)
612 load_target = FT_LOAD_TARGET_LIGHT;
613
614 else if (fc_hintstyle == FC_HINT_MEDIUM)
615 ;
616
617 else if (fc_rgba == FC_RGBA_RGB || fc_rgba == FC_RGBA_BGR)
618 load_target = FT_LOAD_TARGET_LCD;
619
620 else if (fc_rgba == FC_RGBA_VRGB || fc_rgba == FC_RGBA_VBGR)
621 load_target = FT_LOAD_TARGET_LCD_V;
622 }
623
624 FcBool fc_embeddedbitmap;
625 if (FcPatternGetBool(pattern, FC_EMBEDDED_BITMAP, 0, &fc_embeddedbitmap) != FcResultMatch)
626 fc_embeddedbitmap = FcTrue;
627
628 if (!fc_embeddedbitmap && outline)
629 load_flags |= FT_LOAD_NO_BITMAP;
630
631 FcBool fc_autohint;
632 if (FcPatternGetBool(pattern, FC_AUTOHINT, 0, &fc_autohint) != FcResultMatch)
633 fc_autohint = FcFalse;
634
635 if (fc_autohint)
636 load_flags |= FT_LOAD_FORCE_AUTOHINT;
637
638 int render_flags_normal, render_flags_subpixel;
639 if (!fc_antialias)
640 render_flags_normal = render_flags_subpixel = FT_RENDER_MODE_MONO;
641
642 else {
643 if (fc_rgba == FC_RGBA_RGB || fc_rgba == FC_RGBA_BGR) {
644 render_flags_subpixel = FT_RENDER_MODE_LCD;
645 render_flags_normal = FT_RENDER_MODE_NORMAL;
646 }
647
648 else if (fc_rgba == FC_RGBA_VRGB || fc_rgba == FC_RGBA_VBGR) {
649 render_flags_subpixel = FT_RENDER_MODE_LCD_V;
650 render_flags_normal = FT_RENDER_MODE_NORMAL;
651 }
652
653 else
654 render_flags_normal = render_flags_subpixel = FT_RENDER_MODE_NORMAL;
655 }
656
657 int fc_lcdfilter;
658 if (FcPatternGetInteger(pattern, FC_LCD_FILTER, 0, &fc_lcdfilter) != FcResultMatch)
659 fc_lcdfilter = FC_LCD_DEFAULT;
660
661 switch (fc_lcdfilter) {
662 case FC_LCD_NONE: font->lcd_filter = FT_LCD_FILTER_NONE; break;
663 case FC_LCD_DEFAULT: font->lcd_filter = FT_LCD_FILTER_DEFAULT; break;
664 case FC_LCD_LIGHT: font->lcd_filter = FT_LCD_FILTER_LIGHT; break;
665 case FC_LCD_LEGACY: font->lcd_filter = FT_LCD_FILTER_LEGACY; break;
666 }
667
668 FcBool fc_embolden;
669 if (FcPatternGetBool(pattern, FC_EMBOLDEN, 0, &fc_embolden) != FcResultMatch)
670 fc_embolden = FcFalse;
671
672 FcMatrix *fc_matrix;
673 if (FcPatternGetMatrix(pattern, FC_MATRIX, 0, &fc_matrix) == FcResultMatch) {
674 FT_Matrix m = {
675 .xx = fc_matrix->xx * 0x10000,
676 .xy = fc_matrix->xy * 0x10000,
677 .yx = fc_matrix->yx * 0x10000,
678 .yy = fc_matrix->yy * 0x10000,
679 };
680 FT_Set_Transform(ft_face, &m, NULL);
681 }
682
683 font->path = strdup((char *)face_file);
684 if (font->path == NULL)
685 goto err_done_face;
686
687 font->face = ft_face;
688 font->load_flags = load_target | load_flags | FT_LOAD_COLOR;
689 font->antialias = fc_antialias;
690 font->embolden = fc_embolden;
691 font->is_color = is_color;
692 font->render_flags_normal = render_flags_normal;
693 font->render_flags_subpixel = render_flags_subpixel;
694 font->pixel_size_fixup = pixel_fixup;
695 font->pixel_fixup_estimated = fixup_estimated;
696 font->bgr = fc_rgba == FC_RGBA_BGR || fc_rgba == FC_RGBA_VBGR;
697
698 /* For logging: e.g. "+ss01 -dlig" */
699 char features[256] = {0};
700
701 #if defined(FCFT_HAVE_HARFBUZZ)
702 font->hb_font = hb_ft_font_create_referenced(ft_face);
703 if (font->hb_font == NULL) {
704 LOG_ERR("%s: failed to instantiate harfbuzz font", face_file);
705 goto err_free_path;
706 }
707
708 font->hb_buf = hb_buffer_create();
709 if (font->hb_buf == NULL) {
710 LOG_ERR("%s: failed to instantiate harfbuzz buffer", face_file);
711 goto err_hb_font_destroy;
712 }
713
714 for (font->hb_feats_count = 0; font->hb_feats_count < ALEN(font->hb_feats); ) {
715 FcChar8 *fc_feat;
716 if (FcPatternGetString(
717 pattern, FC_FONT_FEATURES,
718 font->hb_feats_count, &fc_feat) != FcResultMatch)
719 {
720 break;
721 }
722
723 hb_feature_t *feat = &font->hb_feats[font->hb_feats_count];
724
725 if (hb_feature_from_string((const char *)fc_feat, -1, feat)) {
726 const char tag[4] = {
727 (feat->tag >> 24) & 0xff,
728 (feat->tag >> 16) & 0xff,
729 (feat->tag >> 8) & 0xff,
730 (feat->tag >> 0) & 0xff,
731 };
732
733 size_t ofs = font->hb_feats_count * 6;
734 snprintf(&features[ofs], sizeof(features) - ofs,
735 " %c%.4s", feat->value ? '+' : '-', tag);
736
737 LOG_DBG("feature: %.4s=%s", tag, feat->value ? "on" : "off");
738 font->hb_feats_count++;
739 }
740 }
741 #endif
742
743 double max_x_advance = ft_face->size->metrics.max_advance / 64.;
744 double max_y_advance = ft_face->size->metrics.height / 64.;
745 double height = ft_face->size->metrics.height / 64.;
746 double descent = ft_face->size->metrics.descender / 64.;
747 double ascent = ft_face->size->metrics.ascender / 64.;
748
749 font->metrics.height = ceil(height * font->pixel_size_fixup);
750 font->metrics.descent = ceil(-descent * font->pixel_size_fixup);
751 font->metrics.ascent = ceil(ascent * font->pixel_size_fixup);
752 font->metrics.max_advance.x = ceil(max_x_advance * font->pixel_size_fixup);
753 font->metrics.max_advance.y = ceil(max_y_advance * font->pixel_size_fixup);
754 font->metrics.antialias = fc_antialias;
755 font->metrics.subpixel = !fc_antialias
756 ? FCFT_SUBPIXEL_NONE
757 : (fc_rgba == FC_RGBA_RGB ? FCFT_SUBPIXEL_HORIZONTAL_RGB :
758 fc_rgba == FC_RGBA_BGR ? FCFT_SUBPIXEL_HORIZONTAL_BGR :
759 fc_rgba == FC_RGBA_VRGB ? FCFT_SUBPIXEL_VERTICAL_RGB :
760 fc_rgba == FC_RGBA_VBGR ? FCFT_SUBPIXEL_VERTICAL_BGR :
761 FCFT_SUBPIXEL_NONE);
762
763 /*
764 * Some fonts (Noto Sans Mono, for example) provides bad
765 * max_advance values for grid-based applications, like terminal
766 * emulators.
767 *
768 * For this reason we also provide the width of a regular space
769 * character, to help these applications determine the cell size.
770 */
771 FT_UInt idx = FT_Get_Char_Index(font->face, L' ');
772 if (idx != 0 &&
773 (ft_err = FT_Load_Glyph(font->face, idx, font->load_flags | FT_LOAD_BITMAP_METRICS_ONLY)) == 0)
774 {
775 if (fc_embolden && font->face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)
776 FT_GlyphSlot_Embolden(font->face->glyph);
777
778 font->metrics.space_advance.x = ceil(
779 font->face->glyph->advance.x / 64. *
780 (font->pixel_fixup_estimated ? font->pixel_size_fixup : 1.));
781 font->metrics.space_advance.y = ceil(
782 font->face->glyph->advance.y / 64. *
783 (font->pixel_fixup_estimated ? font->pixel_size_fixup : 1.));
784 } else {
785 font->metrics.space_advance.x = -1;
786 font->metrics.space_advance.y = -1;
787 }
788
789 underline_strikeout_metrics(ft_face, &font->metrics);
790
791 #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
792 LOG_DBG("%s: size=%.2fpt/%.2fpx, dpi=%.2f, fixup-factor: %.4f, "
793 "line-height: %dpx, ascent: %dpx, descent: %dpx, "
794 "x-advance (max/space): %d/%dpx, features:%s",
795 font->path, size, pixel_size, dpi, font->pixel_size_fixup,
796 font->metrics.height, font->metrics.ascent, font->metrics.descent,
797 font->metrics.max_advance.x, font->metrics.space_advance.x,
798 features);
799 #else
800 LOG_INFO("%s: size=%.2fpt/%dpx, dpi=%.2f%s",
801 font->path, size, (int)round(pixel_size), dpi, features);
802 #endif
803
804 return true;
805
806 #if defined(FCFT_HAVE_HARFBUZZ)
807 err_hb_font_destroy:
808 hb_font_destroy(font->hb_font);
809 err_free_path:
810 free(font->path);
811 #endif
812
813 err_done_face:
814 mtx_lock(&ft_lock);
815 FT_Done_Face(ft_face);
816 mtx_unlock(&ft_lock);
817 return false;
818 }
819
820 static uint64_t
sdbm_hash(const char * s)821 sdbm_hash(const char *s)
822 {
823 uint64_t hash = 0;
824
825 for (; *s != '\0'; s++) {
826 int c = *s;
827 hash = c + (hash << 6) + (hash << 16) - hash;
828 }
829
830 return hash;
831 }
832
833 static uint64_t
font_hash(size_t count,const char * names[static count],const char * attributes)834 font_hash(size_t count, const char *names[static count], const char *attributes)
835 {
836 uint64_t hash = 0;
837 for (size_t i = 0; i < count; i++)
838 hash ^= sdbm_hash(names[i]);
839
840 if (attributes != NULL)
841 hash ^= sdbm_hash(attributes);
842
843 return hash;
844 }
845
846 FCFT_EXPORT struct fcft_font *
fcft_from_name(size_t count,const char * names[static count],const char * attributes)847 fcft_from_name(size_t count, const char *names[static count],
848 const char *attributes)
849 {
850 log_version_information();
851 if (ft_lib_err != FT_Err_Ok) {
852 LOG_ERR("failed to initialize FreeType: %s", ft_error_string(ft_lib_err));
853 return NULL;
854 }
855
856 if (count == 0)
857 return NULL;
858
859 uint64_t hash = font_hash(count, names, attributes);
860 struct fcft_font_cache_entry *cache_entry = NULL;
861
862 mtx_lock(&font_cache_lock);
863 tll_foreach(font_cache, it) {
864 if (it->item.hash != hash)
865 continue;
866
867 struct fcft_font_cache_entry *e = &it->item;
868
869 if (e->font != (void *)(uintptr_t)-1) {
870 /* Font has already been fully initialized */
871
872 if (e->font != NULL) {
873 mtx_lock(&e->font->lock);
874 e->font->ref_counter++;
875 mtx_unlock(&e->font->lock);
876 }
877
878 mtx_unlock(&font_cache_lock);
879 return e->font != NULL ? &e->font->public : NULL;
880 }
881
882 /*
883 * Cache entry is only a reservation - font hasn't yet
884 * been initialized.
885 */
886
887 /* Let instantiating thread know (yet) another thread
888 * wants a reference to it */
889 e->waiters++;
890
891 while (e->font == (void *)(uintptr_t)-1)
892 cnd_wait(&e->cond, &font_cache_lock);
893 mtx_unlock(&font_cache_lock);
894 return e->font == NULL ? NULL : &e->font->public;
895 }
896
897 /* Pre-allocate entry */
898 tll_push_back(
899 font_cache,
900 ((struct fcft_font_cache_entry){.hash = hash, .font = (void *)(uintptr_t)-1}));
901
902 cache_entry = &tll_back(font_cache);
903
904 if (cnd_init(&cache_entry->cond) != thrd_success) {
905 LOG_ERR("%s: failed to instantiate font cache condition variable", names[0]);
906 tll_pop_back(font_cache);
907 mtx_unlock(&font_cache_lock);
908 return NULL;
909 }
910 mtx_unlock(&font_cache_lock);
911
912 struct font_priv *font = NULL;
913
914 bool have_attrs = attributes != NULL && strlen(attributes) > 0;
915 size_t attr_len = have_attrs ? strlen(attributes) + 1 : 0;
916
917 tll(struct fallback) fc_fallbacks = tll_init();
918
919 bool first = true;
920 for (size_t i = 0; i < count; i++) {
921 const char *base_name = names[i];
922
923 char name[strlen(base_name) + attr_len + 1];
924 strcpy(name, base_name);
925 if (have_attrs) {
926 strcat(name, ":");
927 strcat(name, attributes);
928 }
929
930 FcFontSet *set;
931 FcPattern *base_pattern = base_pattern_from_name(name, &set);
932 if (base_pattern == NULL)
933 break;
934
935 FcPattern *pattern = pattern_from_font_set(base_pattern, set, 0);
936 if (pattern == NULL)
937 break;
938
939 FcCharSet *charset;
940 if (FcPatternGetCharSet(base_pattern, FC_CHARSET, 0, &charset) != FcResultMatch &&
941 FcPatternGetCharSet(pattern, FC_CHARSET, 0, &charset) != FcResultMatch)
942 {
943 LOG_ERR("%s: failed to get charset", name);
944 FcPatternDestroy(pattern);
945 FcPatternDestroy(base_pattern);
946 FcFontSetDestroy(set);
947 break;
948 }
949
950 FcLangSet *langset;
951 if (FcPatternGetLangSet(pattern, FC_LANG, 0, &langset) != FcResultMatch)
952 langset = NULL;
953
954 double req_px_size = -1., req_pt_size = -1.;
955 FcPatternGetDouble(base_pattern, FC_PIXEL_SIZE, 0, &req_px_size);
956 FcPatternGetDouble(base_pattern, FC_SIZE, 0, &req_pt_size);
957
958 if (first) {
959 first = false;
960
961 bool lock_failed = true;
962 bool glyph_cache_lock_failed = true;
963 bool grapheme_cache_lock_failed = true;
964 bool pattern_failed = true;
965
966 mtx_t lock;
967 if (mtx_init(&lock, mtx_plain) != thrd_success)
968 LOG_WARN("%s: failed to instantiate mutex", name);
969 else
970 lock_failed = false;
971
972 pthread_rwlock_t glyph_cache_lock;
973 if (pthread_rwlock_init(&glyph_cache_lock, NULL) != 0)
974 LOG_WARN("%s: failed to instantiate glyph cache rwlock", name);
975 else
976 glyph_cache_lock_failed = false;
977
978 struct instance *primary = malloc(sizeof(*primary));
979 if (primary == NULL ||
980 !instantiate_pattern(pattern, req_pt_size, req_px_size, primary))
981 {
982 ;
983 } else
984 pattern_failed = false;
985
986 font = calloc(1, sizeof(*font));
987 struct glyph_priv **glyph_cache_table = calloc(
988 glyph_cache_initial_size, sizeof(glyph_cache_table[0]));
989
990 #if defined(FCFT_HAVE_HARFBUZZ)
991 pthread_rwlock_t grapheme_cache_lock;
992 if (pthread_rwlock_init(&grapheme_cache_lock, NULL) != 0)
993 LOG_WARN("%s: failed to instantiate grapheme cache rwlock", name);
994 else
995 grapheme_cache_lock_failed = false;
996
997 struct grapheme_priv **grapheme_cache_table = calloc(
998 grapheme_cache_initial_size, sizeof(grapheme_cache_table[0]));
999 #else
1000 struct grapheme_priv **grapheme_cache_table = NULL;
1001 grapheme_cache_lock_failed = false;
1002 #endif
1003
1004 /* Handle failure(s) */
1005 if (lock_failed || glyph_cache_lock_failed ||
1006 grapheme_cache_lock_failed || pattern_failed ||
1007 font == NULL || glyph_cache_table == NULL
1008 #if defined(FCFT_HAVE_HARFBUZZ)
1009 || grapheme_cache_table == NULL
1010 #endif
1011 )
1012 {
1013 if (!lock_failed)
1014 mtx_destroy(&lock);
1015 if (!glyph_cache_lock_failed)
1016 pthread_rwlock_destroy(&glyph_cache_lock);
1017 #if defined(FCFT_HAVE_HARFBUZZ)
1018 if (!grapheme_cache_lock_failed)
1019 pthread_rwlock_destroy(&grapheme_cache_lock);
1020 #endif
1021 if (!pattern_failed)
1022 free(primary);
1023 free(font);
1024 free(glyph_cache_table);
1025 free(grapheme_cache_table);
1026 if (langset != NULL)
1027 FcLangSetDestroy(langset);
1028 FcCharSetDestroy(charset);
1029 FcPatternDestroy(pattern);
1030 FcPatternDestroy(base_pattern);
1031 FcFontSetDestroy(set);
1032 break;
1033 }
1034
1035 font->ref_counter = 1;
1036 font->lock = lock;
1037 font->glyph_cache_lock = glyph_cache_lock;
1038 font->glyph_cache.size = glyph_cache_initial_size;
1039 font->glyph_cache.count = 0;
1040 font->glyph_cache.table = glyph_cache_table;
1041 font->emoji_presentation = FCFT_EMOJI_PRESENTATION_DEFAULT;
1042 font->public = primary->metrics;
1043
1044 #if defined(FCFT_HAVE_HARFBUZZ)
1045 font->grapheme_cache_lock = grapheme_cache_lock;
1046 font->grapheme_cache.size = grapheme_cache_initial_size;
1047 font->grapheme_cache.count = 0;
1048 font->grapheme_cache.table = grapheme_cache_table;
1049 #endif
1050
1051 tll_push_back(font->fallbacks, ((struct fallback){
1052 .pattern = pattern,
1053 .charset = FcCharSetCopy(charset),
1054 .langset = langset != NULL ? FcLangSetCopy(langset) : NULL,
1055 .font = primary,
1056 .req_px_size = req_px_size,
1057 .req_pt_size = req_pt_size}));
1058
1059 for (size_t i = 1; i < set->nfont; i++) {
1060 FcPattern *fallback_pattern = pattern_from_font_set(base_pattern, set, i);
1061 if (fallback_pattern == NULL)
1062 continue;
1063
1064 FcCharSet *fallback_charset;
1065 if (FcPatternGetCharSet(base_pattern, FC_CHARSET, 0, &fallback_charset) != FcResultMatch &&
1066 FcPatternGetCharSet(fallback_pattern, FC_CHARSET, 0, &fallback_charset) != FcResultMatch)
1067 {
1068 LOG_ERR("%s: failed to get charset", name);
1069 FcPatternDestroy(fallback_pattern);
1070 continue;
1071 }
1072
1073 FcLangSet *fallback_langset;
1074 if (FcPatternGetLangSet(fallback_pattern, FC_LANG, 0, &fallback_langset) != FcResultMatch)
1075 fallback_langset = NULL;
1076
1077 FcPatternGetDouble(base_pattern, FC_PIXEL_SIZE, 0, &req_px_size);
1078 FcPatternGetDouble(base_pattern, FC_SIZE, 0, &req_pt_size);
1079
1080 tll_push_back(fc_fallbacks, ((struct fallback){
1081 .pattern = fallback_pattern,
1082 .charset = FcCharSetCopy(fallback_charset),
1083 .langset = fallback_langset != NULL ? FcLangSetCopy(fallback_langset) : NULL,
1084 .req_px_size = req_px_size,
1085 .req_pt_size = req_pt_size}));
1086 }
1087
1088 } else {
1089 assert(font != NULL);
1090 tll_push_back(font->fallbacks, ((struct fallback){
1091 .pattern = pattern,
1092 .charset = FcCharSetCopy(charset),
1093 .langset = langset != NULL ? FcLangSetCopy(langset) : NULL,
1094 .req_px_size = req_px_size,
1095 .req_pt_size = req_pt_size}));
1096 }
1097
1098 FcFontSetDestroy(set);
1099 FcPatternDestroy(base_pattern);
1100 }
1101
1102 /* Append fontconfig-based fallbacks at the end of our fallback list */
1103 if (font != NULL) {
1104 tll_foreach(fc_fallbacks, it)
1105 tll_push_back(font->fallbacks, it->item);
1106 tll_free(fc_fallbacks);
1107 }
1108
1109 mtx_lock(&font_cache_lock);
1110 cache_entry->font = font;
1111 if (cache_entry->font != NULL)
1112 cache_entry->font->ref_counter += cache_entry->waiters;
1113 cnd_broadcast(&cache_entry->cond);
1114 mtx_unlock(&font_cache_lock);
1115
1116 assert(font == NULL || (void *)&font->public == (void *)font);
1117 return font != NULL ? &font->public : NULL;
1118 }
1119
1120 FCFT_EXPORT struct fcft_font *
fcft_clone(const struct fcft_font * _font)1121 fcft_clone(const struct fcft_font *_font)
1122 {
1123 if (_font == NULL)
1124 return NULL;
1125
1126 struct font_priv *font = (struct font_priv *)_font;
1127
1128 mtx_lock(&font->lock);
1129 {
1130 assert(font->ref_counter >= 1);
1131 font->ref_counter++;
1132 }
1133 mtx_unlock(&font->lock);
1134
1135 return &font->public;
1136 }
1137
1138 FCFT_EXPORT struct fcft_font *
fcft_size_adjust(const struct fcft_font * _font,double amount)1139 fcft_size_adjust(const struct fcft_font *_font, double amount)
1140 {
1141 struct font_priv *new = calloc(1, sizeof(*new));
1142 if (new == NULL)
1143 return NULL;
1144
1145 struct glyph_priv **cache_table = calloc(glyph_cache_initial_size, sizeof(cache_table[0]));
1146 if (cache_table == NULL) {
1147 free(new);
1148 return NULL;
1149 }
1150
1151 mtx_init(&new->lock, mtx_plain);
1152 pthread_rwlock_init(&new->glyph_cache_lock, NULL);
1153
1154 new->ref_counter = 1;
1155 new->glyph_cache.size = glyph_cache_initial_size;
1156 new->glyph_cache.count = 0;
1157 new->glyph_cache.table = cache_table;
1158
1159 struct font_priv *font = (struct font_priv *)_font;
1160 tll_foreach(font->fallbacks, it) {
1161 FcPattern *pat = it->item.pattern;
1162 double size = it->item.req_pt_size;
1163
1164 if (size < 0. &&
1165 FcPatternGetDouble(pat, FC_SIZE, 0, &size) != FcResultMatch)
1166 {
1167 LOG_WARN("failed to get size");
1168 goto err;
1169 }
1170
1171 size += amount;
1172 if (size < 1.)
1173 goto err;
1174
1175 FcPattern *new_base = FcPatternDuplicate(pat);
1176 FcPatternRemove(new_base, FC_SIZE, 0);
1177 FcPatternRemove(new_base, FC_PIXEL_SIZE, 0);
1178 FcPatternRemove(new_base, FC_WEIGHT, 0);
1179 FcPatternRemove(new_base, FC_WIDTH, 0);
1180 FcPatternRemove(new_base, FC_FILE, 0);
1181 FcPatternRemove(new_base, FC_FT_FACE, 0);
1182
1183 FcPatternAddDouble(new_base, FC_SIZE, size);
1184
1185 if (!FcConfigSubstitute(NULL, new_base, FcMatchPattern)) {
1186 FcPatternDestroy(new_base);
1187 goto err;
1188 }
1189 FcDefaultSubstitute(new_base);
1190
1191 FcResult res;
1192 FcPattern *new_pat = FcFontMatch(NULL, new_base, &res);
1193
1194 tll_push_back(new->fallbacks, ((struct fallback){
1195 .pattern = new_pat,
1196 .charset = FcCharSetCopy(it->item.charset),
1197 .langset = it->item.langset != NULL ? FcLangSetCopy(it->item.langset) : NULL,
1198 .req_px_size = -1.,
1199 .req_pt_size = size}));
1200
1201 FcPatternDestroy(new_base);
1202 }
1203
1204 assert(tll_length(new->fallbacks) > 0);
1205 struct fallback *primary = &tll_front(new->fallbacks);
1206
1207 struct instance *inst = malloc(sizeof(*inst));
1208 if (inst == NULL ||
1209 !instantiate_pattern(
1210 primary->pattern, primary->req_pt_size, primary->req_px_size, inst))
1211 {
1212 free(inst);
1213 goto err;
1214 }
1215 primary->font = inst;
1216
1217 new->public = inst->metrics;
1218 return &new->public;
1219
1220 err:
1221 fcft_destroy(&new->public);
1222 return NULL;
1223
1224 }
1225
1226 static bool
glyph_for_index(const struct instance * inst,uint32_t index,enum fcft_subpixel subpixel,struct glyph_priv * glyph)1227 glyph_for_index(const struct instance *inst, uint32_t index,
1228 enum fcft_subpixel subpixel, struct glyph_priv *glyph)
1229 {
1230 glyph->valid = false;
1231 glyph->subpixel = subpixel;
1232
1233 pixman_image_t *pix = NULL;
1234 uint8_t *data = NULL;
1235
1236 FT_Error err;
1237 if ((err = FT_Load_Glyph(inst->face, index, inst->load_flags)) != 0) {
1238 LOG_ERR("%s: failed to load glyph #%d: %s",
1239 inst->path, index, ft_error_string(err));
1240 goto err;
1241 }
1242
1243 if (inst->embolden && inst->face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)
1244 FT_GlyphSlot_Embolden(inst->face->glyph);
1245
1246 int render_flags;
1247 bool bgr;
1248
1249 if (inst->antialias) {
1250 switch (subpixel) {
1251 case FCFT_SUBPIXEL_NONE:
1252 render_flags = inst->render_flags_normal;
1253 bgr = false;
1254 break;
1255
1256 case FCFT_SUBPIXEL_HORIZONTAL_RGB:
1257 case FCFT_SUBPIXEL_HORIZONTAL_BGR:
1258 render_flags = FT_RENDER_MODE_LCD;
1259 bgr = subpixel == FCFT_SUBPIXEL_HORIZONTAL_BGR;
1260 break;
1261
1262 case FCFT_SUBPIXEL_VERTICAL_RGB:
1263 case FCFT_SUBPIXEL_VERTICAL_BGR:
1264 render_flags = FT_RENDER_MODE_LCD_V;
1265 bgr = subpixel == FCFT_SUBPIXEL_VERTICAL_BGR;
1266 break;
1267
1268 case FCFT_SUBPIXEL_DEFAULT:
1269 default:
1270 render_flags = inst->render_flags_subpixel;
1271 bgr = inst->bgr;
1272 break;
1273 }
1274 } else {
1275 render_flags = inst->render_flags_normal;
1276 bgr = false;
1277 }
1278
1279 /*
1280 * LCD filter is per library instance, hence we need to re-set it
1281 * every time. But only if we need it, and only if we _can_, to
1282 * avoid locking a global mutex. See init().
1283 */
1284 bool unlock_ft_lock = false;
1285 if (can_set_lcd_filter &&
1286 ((render_flags & FT_RENDER_MODE_LCD) ||
1287 (render_flags & FT_RENDER_MODE_LCD_V)))
1288 {
1289 mtx_lock(&ft_lock);
1290 unlock_ft_lock = true;
1291
1292 FT_Error err = FT_Library_SetLcdFilter(ft_lib, inst->lcd_filter);
1293
1294 if (err != 0) {
1295 LOG_ERR("failed to set LCD filter: %s", ft_error_string(err));
1296 mtx_unlock(&ft_lock);
1297 goto err;
1298 }
1299 }
1300
1301 if (inst->face->glyph->format != FT_GLYPH_FORMAT_BITMAP) {
1302 if ((err = FT_Render_Glyph(inst->face->glyph, render_flags)) != 0) {
1303 LOG_ERR("%s: failed to render glyph: %s",
1304 inst->path, ft_error_string(err));
1305 if (unlock_ft_lock)
1306 mtx_unlock(&ft_lock);
1307 goto err;
1308 }
1309 }
1310
1311 if (unlock_ft_lock)
1312 mtx_unlock(&ft_lock);
1313
1314 if (inst->face->glyph->format != FT_GLYPH_FORMAT_BITMAP) {
1315 LOG_ERR("%s: rasterized glyph is not a bitmap", inst->path);
1316 goto err;
1317 }
1318
1319 const FT_Bitmap *bitmap = &inst->face->glyph->bitmap;
1320 pixman_format_code_t pix_format;
1321 int width;
1322 int rows;
1323
1324 switch (bitmap->pixel_mode) {
1325 case FT_PIXEL_MODE_MONO:
1326 pix_format = PIXMAN_a1;
1327 width = bitmap->width;
1328 rows = bitmap->rows;
1329 break;
1330
1331 case FT_PIXEL_MODE_GRAY:
1332 pix_format = PIXMAN_a8;
1333 width = bitmap->width;
1334 rows = bitmap->rows;
1335 break;
1336
1337 case FT_PIXEL_MODE_LCD:
1338 pix_format = PIXMAN_x8r8g8b8;
1339 width = bitmap->width / 3;
1340 rows = bitmap->rows;
1341 break;
1342
1343 case FT_PIXEL_MODE_LCD_V:
1344 pix_format = PIXMAN_x8r8g8b8;
1345 width = bitmap->width;
1346 rows = bitmap->rows / 3;
1347 break;
1348
1349 case FT_PIXEL_MODE_BGRA:
1350 pix_format = PIXMAN_a8r8g8b8;
1351 width = bitmap->width;
1352 rows = bitmap->rows;
1353 break;
1354
1355 default:
1356 LOG_ERR("unimplemented: FT pixel mode: %d", bitmap->pixel_mode);
1357 goto err;
1358 break;
1359 }
1360
1361 int stride = stride_for_format_and_width(pix_format, width);
1362 assert(stride >= bitmap->pitch);
1363
1364 assert(bitmap->buffer != NULL || rows * stride == 0);
1365 data = malloc(rows * stride);
1366 if (data == NULL)
1367 goto err;
1368
1369 /* Convert FT bitmap to pixman image */
1370 switch (bitmap->pixel_mode) {
1371 case FT_PIXEL_MODE_MONO: /* PIXMAN_a1 */
1372 /*
1373 * FreeType: left-most pixel is stored in MSB ABCDEFGH IJKLMNOP
1374 * Pixman: LE: left-most pixel in LSB HGFEDCBA PONMLKJI
1375 * BE: left-most pixel in MSB ABCDEFGH IJKLMNOP
1376 *
1377 * Thus, we need to reverse each byte on little-endian systems.
1378 */
1379 for (size_t r = 0; r < bitmap->rows; r++) {
1380 for (size_t c = 0; c < (bitmap->width + 7) / 8; c++) {
1381 uint8_t v = bitmap->buffer[r * bitmap->pitch + c];
1382 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1383 uint8_t reversed = 0;
1384 for (size_t i = 0; i < min(8, bitmap->width - c * 8); i++)
1385 reversed |= ((v >> (7 - i)) & 1) << i;
1386
1387 data[r * stride + c] = reversed;
1388 #else
1389 data[r * stride + c] = v;
1390 #endif
1391 }
1392 }
1393 break;
1394
1395 case FT_PIXEL_MODE_GRAY: /* PIXMAN_a8 */
1396 /*
1397 * One pixel, one byte. No endianness to worry about
1398 */
1399 if (stride == bitmap->pitch) {
1400 if (bitmap->buffer != NULL)
1401 memcpy(data, bitmap->buffer, rows * stride);
1402 } else {
1403 for (size_t r = 0; r < bitmap->rows; r++) {
1404 for (size_t c = 0; c < bitmap->width; c++)
1405 data[r * stride + c] = bitmap->buffer[r * bitmap->pitch + c];
1406 }
1407 }
1408 break;
1409
1410 case FT_PIXEL_MODE_BGRA: /* PIXMAN_a8r8g8b8 */
1411 /*
1412 * FreeType: blue comes *first* in memory
1413 * Pixman: LE: blue comes *first* in memory
1414 * BE: alpha comes *first* in memory
1415 *
1416 * Pixman is ARGB *when loaded into a register*, assuming
1417 * machine native 32-bit loads. Thus, it’s in-memory layout
1418 * depends on the host’s endianness.
1419 */
1420 assert(stride == bitmap->pitch);
1421 for (size_t r = 0; r < bitmap->rows; r++) {
1422 for (size_t c = 0; c < bitmap->width * 4; c += 4) {
1423 unsigned char _b = bitmap->buffer[r * bitmap->pitch + c + 0];
1424 unsigned char _g = bitmap->buffer[r * bitmap->pitch + c + 1];
1425 unsigned char _r = bitmap->buffer[r * bitmap->pitch + c + 2];
1426 unsigned char _a = bitmap->buffer[r * bitmap->pitch + c + 3];
1427
1428 uint32_t *p = (uint32_t *)&data[r * stride + c];
1429 *p = (uint32_t)_a << 24 | _r << 16 | _g << 8 | _b;
1430 }
1431 }
1432 break;
1433
1434 case FT_PIXEL_MODE_LCD: /* PIXMAN_x8r8g8b8 */
1435 /*
1436 * FreeType: red comes *first* in memory
1437 * Pixman: LE: blue comes *first* in memory
1438 * BE: x comes *first* in memory
1439 *
1440 * Same as above, except that the FreeType data is now RGBx
1441 * instead of BGRA.
1442 */
1443 for (size_t r = 0; r < bitmap->rows; r++) {
1444 for (size_t c = 0; c < bitmap->width; c += 3) {
1445 unsigned char _r = bitmap->buffer[r * bitmap->pitch + c + (bgr ? 2 : 0)];
1446 unsigned char _g = bitmap->buffer[r * bitmap->pitch + c + 1];
1447 unsigned char _b = bitmap->buffer[r * bitmap->pitch + c + (bgr ? 0 : 2)];
1448
1449 uint32_t *p = (uint32_t *)&data[r * stride + 4 * (c / 3)];
1450 *p = _r << 16 | _g << 8 | _b;
1451 }
1452 }
1453 break;
1454
1455 case FT_PIXEL_MODE_LCD_V: /* PIXMAN_x8r8g8b8 */
1456 /*
1457 * FreeType: red comes *first* in memory
1458 * Pixman: LE: blue comes *first* in memory
1459 * BE: x comes *first* in memory
1460 *
1461 * Same as above, except that the FreeType data is now RGBx
1462 * instead of BGRA.
1463 */
1464 for (size_t r = 0; r < bitmap->rows; r += 3) {
1465 for (size_t c = 0; c < bitmap->width; c++) {
1466 unsigned char _r = bitmap->buffer[(r + (bgr ? 2 : 0)) * bitmap->pitch + c];
1467 unsigned char _g = bitmap->buffer[(r + 1) * bitmap->pitch + c];
1468 unsigned char _b = bitmap->buffer[(r + (bgr ? 0 : 2)) * bitmap->pitch + c];
1469
1470 uint32_t *p = (uint32_t *)&data[r / 3 * stride + 4 * c];
1471 *p = _r << 16 | _g << 8 | _b;
1472 }
1473 }
1474 break;
1475
1476 default:
1477 abort();
1478 break;
1479 }
1480
1481 if ((pix = pixman_image_create_bits_no_clear(
1482 pix_format, width, rows, (uint32_t *)data, stride)) == NULL)
1483 goto err;
1484
1485 pixman_image_set_component_alpha(
1486 pix,
1487 bitmap->pixel_mode == FT_PIXEL_MODE_LCD ||
1488 bitmap->pixel_mode == FT_PIXEL_MODE_LCD_V);
1489
1490 int x = inst->face->glyph->bitmap_left;
1491 int y = inst->face->glyph->bitmap_top;
1492
1493 if (inst->pixel_size_fixup == 0.)
1494 x = y = width = rows = 0;
1495
1496 else if (inst->pixel_size_fixup != 1.) {
1497 struct pixman_f_transform scale;
1498 pixman_f_transform_init_scale(
1499 &scale,
1500 1. / inst->pixel_size_fixup,
1501 1. / inst->pixel_size_fixup);
1502
1503 struct pixman_transform _scale;
1504 pixman_transform_from_pixman_f_transform(&_scale, &scale);
1505 pixman_image_set_transform(pix, &_scale);
1506
1507 const enum fcft_scaling_filter filter_to_use = inst->is_color
1508 ? scaling_filter
1509 : FCFT_SCALING_FILTER_BILINEAR;
1510
1511 switch (filter_to_use) {
1512 case FCFT_SCALING_FILTER_NONE:
1513 break;
1514
1515 case FCFT_SCALING_FILTER_NEAREST:
1516 pixman_image_set_filter(pix, PIXMAN_FILTER_NEAREST, NULL, 0);
1517 break;
1518
1519 case FCFT_SCALING_FILTER_BILINEAR:
1520 pixman_image_set_filter(pix, PIXMAN_FILTER_BILINEAR, NULL, 0);
1521 break;
1522
1523 case FCFT_SCALING_FILTER_CUBIC:
1524 case FCFT_SCALING_FILTER_LANCZOS3: {
1525 /*
1526 * TODO:
1527 * - find out how the subsample_bit_{x,y} parameters should be set
1528 */
1529 int param_count = 0;
1530 pixman_kernel_t kernel = filter_to_use == FCFT_SCALING_FILTER_CUBIC
1531 ? PIXMAN_KERNEL_CUBIC
1532 : PIXMAN_KERNEL_LANCZOS3;
1533
1534 pixman_fixed_t *params = pixman_filter_create_separable_convolution(
1535 ¶m_count,
1536 pixman_double_to_fixed(1. / inst->pixel_size_fixup),
1537 pixman_double_to_fixed(1. / inst->pixel_size_fixup),
1538 kernel, kernel,
1539 kernel, kernel,
1540 pixman_int_to_fixed(1),
1541 pixman_int_to_fixed(1));
1542
1543 pixman_image_set_filter(
1544 pix, PIXMAN_FILTER_SEPARABLE_CONVOLUTION,
1545 params, param_count);
1546 free(params);
1547 break;
1548 }
1549 }
1550
1551 int scaled_width = width / (1. / inst->pixel_size_fixup);
1552 int scaled_rows = rows / (1. / inst->pixel_size_fixup);
1553 int scaled_stride = stride_for_format_and_width(pix_format, scaled_width);
1554
1555 if (pix_format == PIXMAN_a8r8g8b8) {
1556 uint8_t *scaled_data = malloc(scaled_rows * scaled_stride);
1557 if (scaled_data == NULL)
1558 goto err;
1559
1560 pixman_image_t *scaled_pix = pixman_image_create_bits_no_clear(
1561 pix_format, scaled_width, scaled_rows,
1562 (uint32_t *)scaled_data, scaled_stride);
1563
1564 if (scaled_pix == NULL) {
1565 free(scaled_data);
1566 goto err;
1567 }
1568
1569 pixman_image_composite32(
1570 PIXMAN_OP_SRC, pix, NULL, scaled_pix, 0, 0, 0, 0,
1571 0, 0, scaled_width, scaled_rows);
1572
1573 pixman_image_unref(pix);
1574 free(data);
1575
1576 data = scaled_data;
1577 pix = scaled_pix;
1578 }
1579
1580 rows = scaled_rows;
1581 width = scaled_width;
1582 stride = scaled_stride;
1583
1584 x *= inst->pixel_size_fixup;
1585 y *= inst->pixel_size_fixup;
1586 }
1587
1588 *glyph = (struct glyph_priv){
1589 .public = {
1590 .pix = pix,
1591 .x = x,
1592 .y = y,
1593 .advance = {
1594 .x = (inst->face->glyph->advance.x / 64. *
1595 (inst->pixel_fixup_estimated ? inst->pixel_size_fixup : 1.)),
1596 .y = (inst->face->glyph->advance.y / 64. *
1597 (inst->pixel_fixup_estimated ? inst->pixel_size_fixup : 1.)),
1598 },
1599 .width = width,
1600 .height = rows,
1601 },
1602 .subpixel = subpixel,
1603 .valid = true,
1604 };
1605
1606 return true;
1607
1608 err:
1609 if (pix != NULL)
1610 pixman_image_unref(pix);
1611 free(data);
1612 assert(!glyph->valid);
1613 return false;
1614 }
1615
1616 static bool
glyph_for_wchar(const struct instance * inst,wchar_t wc,enum fcft_subpixel subpixel,struct glyph_priv * glyph)1617 glyph_for_wchar(const struct instance *inst, wchar_t wc,
1618 enum fcft_subpixel subpixel, struct glyph_priv *glyph)
1619 {
1620 FT_UInt idx = -1;
1621
1622 #if defined(FCFT_HAVE_HARFBUZZ)
1623 /*
1624 * Use HarfBuzz if we have any font features. If we don’t, there’s
1625 * no point in using HarfBuzz since it only slows things down, and
1626 * we can just lookup the glyph index directly using FreeType’s
1627 * API.x
1628 */
1629 if (inst->hb_feats_count > 0) {
1630 hb_buffer_add_utf32(inst->hb_buf, (const uint32_t *)&wc, 1, 0, 1);
1631 hb_buffer_guess_segment_properties(inst->hb_buf);
1632 hb_shape(inst->hb_font, inst->hb_buf, inst->hb_feats, inst->hb_feats_count);
1633
1634 unsigned count = hb_buffer_get_length(inst->hb_buf);
1635 if (count == 1) {
1636 const hb_glyph_info_t *info = hb_buffer_get_glyph_infos(inst->hb_buf, NULL);
1637 idx = info[0].codepoint;
1638 }
1639
1640 hb_buffer_clear_contents(inst->hb_buf);
1641 }
1642 #endif
1643
1644 if (idx == (FT_UInt)-1)
1645 idx = FT_Get_Char_Index(inst->face, wc);
1646
1647 bool ret = glyph_for_index(inst, idx, subpixel, glyph);
1648 glyph->public.wc = wc;
1649 glyph->public.cols = wcwidth(wc);
1650 return ret;
1651 }
1652
1653 static size_t
hash_index_for_size(size_t size,size_t v)1654 hash_index_for_size(size_t size, size_t v)
1655 {
1656 return (v * 2654435761) & (size - 1);
1657 }
1658
1659 static size_t
glyph_hash_index(const struct font_priv * font,size_t v)1660 glyph_hash_index(const struct font_priv *font, size_t v)
1661 {
1662 return hash_index_for_size(font->glyph_cache.size, v);
1663 }
1664
1665 static uint32_t
hash_value_for_wc(wchar_t wc,enum fcft_subpixel subpixel)1666 hash_value_for_wc(wchar_t wc, enum fcft_subpixel subpixel)
1667 {
1668 return subpixel << 29 | wc;
1669 }
1670
1671 static struct glyph_priv **
glyph_cache_lookup(struct font_priv * font,wchar_t wc,enum fcft_subpixel subpixel)1672 glyph_cache_lookup(struct font_priv *font, wchar_t wc,
1673 enum fcft_subpixel subpixel)
1674 {
1675 size_t idx = glyph_hash_index(font, hash_value_for_wc(wc, subpixel));
1676 struct glyph_priv **glyph = &font->glyph_cache.table[idx];
1677
1678 while (*glyph != NULL && !((*glyph)->public.wc == wc &&
1679 (*glyph)->subpixel == subpixel))
1680 {
1681 idx = (idx + 1) & (font->glyph_cache.size - 1);
1682 glyph = &font->glyph_cache.table[idx];
1683
1684 #if defined(_DEBUG)
1685 glyph_cache_collisions++;
1686 #endif
1687 }
1688
1689 #if defined(_DEBUG)
1690 glyph_cache_lookups++;
1691 #endif
1692 return glyph;
1693 }
1694
1695 static bool
glyph_cache_resize(struct font_priv * font)1696 glyph_cache_resize(struct font_priv *font)
1697 {
1698 if (font->glyph_cache.count * 100 / font->glyph_cache.size < 75)
1699 return false;
1700
1701 size_t size = 2 * font->glyph_cache.size;
1702 assert(__builtin_popcount(size) == 1);
1703
1704 struct glyph_priv **table = calloc(size, sizeof(table[0]));
1705 if (table == NULL)
1706 return false;
1707
1708 for (size_t i = 0; i < font->glyph_cache.size; i++) {
1709 struct glyph_priv *entry = font->glyph_cache.table[i];
1710
1711 if (entry == NULL)
1712 continue;
1713
1714 size_t idx = hash_index_for_size(
1715 size, hash_value_for_wc(entry->public.wc, entry->subpixel));
1716
1717 while (table[idx] != NULL) {
1718 assert(!(table[idx]->public.wc == entry->public.wc &&
1719 table[idx]->subpixel == entry->subpixel));
1720 idx = (idx + 1) & (size - 1);
1721 }
1722
1723 assert(table[idx] == NULL);
1724 table[idx] = entry;
1725 }
1726
1727 pthread_rwlock_wrlock(&font->glyph_cache_lock);
1728 {
1729 free(font->glyph_cache.table);
1730
1731 LOG_DBG("resized glyph cache from %zu to %zu", font->glyph_cache.size, size);
1732 font->glyph_cache.table = table;
1733 font->glyph_cache.size = size;
1734 }
1735 pthread_rwlock_unlock(&font->glyph_cache_lock);
1736 return true;
1737 }
1738
1739 static int
emoji_compare(const void * _key,const void * _emoji)1740 emoji_compare(const void *_key, const void *_emoji)
1741 {
1742 const uint32_t key = *(const uint32_t *)_key;
1743 const struct emoji *emoji = _emoji;
1744
1745 if (key < emoji->cp)
1746 return -1;
1747 if (key >= emoji->cp + emoji->count)
1748 return 1;
1749 assert(key >= emoji->cp && key < emoji->cp + emoji->count);
1750 return 0;
1751 }
1752
1753 static const struct emoji *
emoji_lookup(uint32_t cp)1754 emoji_lookup(uint32_t cp)
1755 {
1756 return bsearch(&cp, emojis, ALEN(emojis), sizeof(emojis[0]), &emoji_compare);
1757 }
1758
1759 #if defined(_DEBUG)
1760 static void __attribute__((constructor))
test_emoji_compare(void)1761 test_emoji_compare(void)
1762 {
1763 #if defined(FCFT_HAVE_HARFBUZZ)
1764 /* WHITE SMILING FACE */
1765 const struct emoji *e = emoji_lookup(0x263a);
1766
1767 assert(e != NULL);
1768 assert(0x263a >= e->cp);
1769 assert(0x263a < e->cp + e->count);
1770 assert(!e->emoji_presentation);
1771
1772 e = emoji_lookup(L'a');
1773 assert(e == NULL);
1774 #else
1775 assert(ALEN(emojis) == 0);
1776 #endif
1777 }
1778 #endif
1779
1780 FCFT_EXPORT const struct fcft_glyph *
fcft_glyph_rasterize(struct fcft_font * _font,wchar_t wc,enum fcft_subpixel subpixel)1781 fcft_glyph_rasterize(struct fcft_font *_font, wchar_t wc,
1782 enum fcft_subpixel subpixel)
1783 {
1784 struct font_priv *font = (struct font_priv *)_font;
1785
1786 pthread_rwlock_rdlock(&font->glyph_cache_lock);
1787 struct glyph_priv **entry = glyph_cache_lookup(font, wc, subpixel);
1788
1789 if (*entry != NULL) {
1790 const struct glyph_priv *glyph = *entry;
1791 pthread_rwlock_unlock(&font->glyph_cache_lock);
1792 return glyph->valid ? &glyph->public : NULL;
1793 }
1794
1795 pthread_rwlock_unlock(&font->glyph_cache_lock);
1796 mtx_lock(&font->lock);
1797
1798 /* Check again - another thread may have resized the cache, or
1799 * populated the entry while we acquired the write-lock */
1800 entry = glyph_cache_lookup(font, wc, subpixel);
1801 if (*entry != NULL) {
1802 const struct glyph_priv *glyph = *entry;
1803 mtx_unlock(&font->lock);
1804 return glyph->valid ? &glyph->public : NULL;
1805 }
1806
1807 if (glyph_cache_resize(font)) {
1808 /* Entry pointer is invalid if the cache was resized */
1809 entry = glyph_cache_lookup(font, wc, subpixel);
1810 }
1811
1812 struct glyph_priv *glyph = malloc(sizeof(*glyph));
1813 if (glyph == NULL) {
1814 mtx_unlock(&font->lock);
1815 return NULL;
1816 }
1817
1818 glyph->public.wc = wc;
1819 glyph->valid = false;
1820
1821 const struct emoji *emoji = emoji_lookup(wc);
1822 assert(emoji == NULL || (wc >= emoji->cp && wc < emoji->cp + emoji->count));
1823
1824 bool force_text_presentation = false;
1825 bool force_emoji_presentation = false;
1826 bool enforce_presentation_style = emoji != NULL;
1827
1828 if (emoji != NULL) {
1829 switch (font->emoji_presentation) {
1830 case FCFT_EMOJI_PRESENTATION_TEXT:
1831 force_text_presentation = true;
1832 force_emoji_presentation = false;
1833 break;
1834
1835 case FCFT_EMOJI_PRESENTATION_EMOJI:
1836 force_text_presentation = false;
1837 force_emoji_presentation = true;
1838 break;
1839
1840 case FCFT_EMOJI_PRESENTATION_DEFAULT:
1841 force_text_presentation = !emoji->emoji_presentation;
1842 force_emoji_presentation = emoji->emoji_presentation;
1843 break;
1844 }
1845 }
1846
1847 assert(tll_length(font->fallbacks) > 0);
1848
1849 bool no_one = true;
1850 bool got_glyph = false;
1851
1852 search_fonts:
1853
1854 tll_foreach(font->fallbacks, it) {
1855 if (!FcCharSetHasChar(it->item.charset, wc))
1856 continue;
1857
1858 static const FcChar8 *const lang_emoji = (const FcChar8 *)"und-zsye";
1859
1860 if (enforce_presentation_style && it->item.langset != NULL) {
1861 bool has_lang_emoji =
1862 FcLangSetHasLang(it->item.langset, lang_emoji) == FcLangEqual;
1863
1864 if (force_text_presentation && has_lang_emoji)
1865 continue;
1866 if (force_emoji_presentation && !has_lang_emoji)
1867 continue;
1868 }
1869
1870 if (it->item.font == NULL) {
1871 struct instance *inst = malloc(sizeof(*inst));
1872 if (inst == NULL)
1873 continue;
1874
1875 if (!instantiate_pattern(
1876 it->item.pattern,
1877 it->item.req_pt_size, it->item.req_px_size,
1878 inst))
1879 {
1880 /* Remove, so that we don't have to keep trying to
1881 * instantiate it */
1882 free(inst);
1883 fallback_destroy(&it->item);
1884 tll_remove(font->fallbacks, it);
1885 continue;
1886 }
1887
1888 it->item.font = inst;
1889 }
1890
1891 assert(it->item.font != NULL);
1892 got_glyph = glyph_for_wchar(it->item.font, wc, subpixel, glyph);
1893 no_one = false;
1894 break;
1895 }
1896
1897 if (no_one && enforce_presentation_style) {
1898 enforce_presentation_style = false;
1899 goto search_fonts;
1900 }
1901
1902 if (no_one) {
1903 /*
1904 * No font claimed this glyph - use the primary font anyway.
1905 */
1906 assert(tll_length(font->fallbacks) > 0);
1907 struct instance *inst = tll_front(font->fallbacks).font;
1908
1909 assert(inst != NULL);
1910 got_glyph = glyph_for_wchar(inst, wc, subpixel, glyph);
1911 }
1912
1913 assert(*entry == NULL);
1914 *entry = glyph;
1915 font->glyph_cache.count++;
1916
1917 mtx_unlock(&font->lock);
1918 return got_glyph ? &glyph->public : NULL;
1919 }
1920
1921 #if defined(FCFT_HAVE_HARFBUZZ)
1922
1923 static size_t
grapheme_hash_index(const struct font_priv * font,size_t v)1924 grapheme_hash_index(const struct font_priv *font, size_t v)
1925 {
1926 return hash_index_for_size(font->grapheme_cache.size, v);
1927 }
1928
1929 static uint64_t
sdbm_hash_wide(const wchar_t * s,size_t len)1930 sdbm_hash_wide(const wchar_t *s, size_t len)
1931 {
1932 uint64_t hash = 0;
1933
1934 for (size_t i = 0; i < len; i++, s++)
1935 hash = (hash << 4) ^ *s;
1936 return hash;
1937 }
1938
1939 static uint64_t
hash_value_for_grapheme(size_t len,const wchar_t grapheme[static len],enum fcft_subpixel subpixel)1940 hash_value_for_grapheme(size_t len, const wchar_t grapheme[static len],
1941 enum fcft_subpixel subpixel)
1942 {
1943 uint64_t hash = sdbm_hash_wide(grapheme, len);
1944 hash &= (1ull << 29) - 1;
1945 return subpixel << 29 | hash;
1946 }
1947
1948 static struct grapheme_priv **
grapheme_cache_lookup(struct font_priv * font,size_t len,const wchar_t cluster[static len],enum fcft_subpixel subpixel)1949 grapheme_cache_lookup(struct font_priv *font,
1950 size_t len, const wchar_t cluster[static len],
1951 enum fcft_subpixel subpixel)
1952 {
1953 size_t idx = grapheme_hash_index(
1954 font, hash_value_for_grapheme(len, cluster, subpixel));
1955 struct grapheme_priv **entry = &font->grapheme_cache.table[idx];
1956
1957 while (*entry != NULL && !(
1958 (*entry)->len == len &&
1959 wcsncmp((*entry)->cluster, cluster, len) == 0 &&
1960 (*entry)->subpixel == subpixel))
1961 {
1962 idx = (idx + 1) & (font->grapheme_cache.size - 1);
1963 entry = &font->grapheme_cache.table[idx];
1964
1965 #if defined(_DEBUG)
1966 grapheme_cache_collisions++;
1967 #endif
1968 }
1969
1970 #if defined(_DEBUG)
1971 grapheme_cache_lookups++;
1972 #endif
1973 return entry;
1974 }
1975
1976 static bool
grapheme_cache_resize(struct font_priv * font)1977 grapheme_cache_resize(struct font_priv *font)
1978 {
1979 if (font->grapheme_cache.count * 100 / font->grapheme_cache.size < 75)
1980 return false;
1981
1982 size_t size = 2 * font->grapheme_cache.size;
1983 assert(__builtin_popcount(size) == 1);
1984
1985 struct grapheme_priv **table = calloc(size, sizeof(table[0]));
1986 if (table == NULL)
1987 return false;
1988
1989 for (size_t i = 0; i < font->grapheme_cache.size; i++) {
1990 struct grapheme_priv *entry = font->grapheme_cache.table[i];
1991
1992 if (entry == NULL)
1993 continue;
1994
1995 size_t idx = hash_index_for_size(
1996 size, hash_value_for_grapheme(
1997 entry->len, entry->cluster, entry->subpixel));
1998
1999 while (table[idx] != NULL) {
2000 assert(
2001 !(table[idx]->len == entry->len &&
2002 wcsncmp(table[idx]->cluster, entry->cluster, entry->len) == 0 &&
2003 table[idx]->subpixel == entry->subpixel));
2004 idx = (idx + 1) & (size - 1);
2005 }
2006
2007 assert(table[idx] == NULL);
2008 table[idx] = entry;
2009 }
2010
2011 pthread_rwlock_wrlock(&font->grapheme_cache_lock);
2012 {
2013 free(font->grapheme_cache.table);
2014
2015 LOG_DBG("resized grapheme cache from %zu to %zu (count: %zu)", font->grapheme_cache.size, size, font->grapheme_cache.count);
2016 font->grapheme_cache.table = table;
2017 font->grapheme_cache.size = size;
2018 }
2019 pthread_rwlock_unlock(&font->grapheme_cache_lock);
2020 return true;
2021 }
2022
2023 /* Must only be called while font->lock is held */
2024 static bool
font_for_grapheme(struct font_priv * font,size_t len,const wchar_t cluster[static len],struct instance ** inst,bool enforce_presentation_style)2025 font_for_grapheme(struct font_priv *font,
2026 size_t len, const wchar_t cluster[static len],
2027 struct instance **inst, bool enforce_presentation_style)
2028 {
2029 static const FcChar8 *const lang_emoji = (const FcChar8 *)"und-zsye";
2030
2031 tll_foreach(font->fallbacks, it) {
2032 const bool has_lang_emoji = it->item.langset != NULL &&
2033 FcLangSetHasLang(it->item.langset, lang_emoji) == FcLangEqual;
2034
2035 bool has_all_code_points = true;
2036 for (size_t i = 0; i < len && has_all_code_points; i++) {
2037
2038 const struct emoji *emoji = emoji_lookup(cluster[i]);
2039 assert(emoji == NULL || (cluster[i] >= emoji->cp &&
2040 cluster[i] < emoji->cp + emoji->count));
2041
2042 if (enforce_presentation_style &&
2043 emoji != NULL &&
2044 (i + 1 >= len || (cluster[i + 1] != 0xfe0e &&
2045 cluster[i + 1] != 0xfe0f))) {
2046 /*
2047 * We have an emoji, that is either the last codepoint
2048 * in the grapheme, *or* is followed by a codepoint
2049 * that is *not* a presentation selector.
2050 */
2051 bool force_text_presentation = false;
2052 bool force_emoji_presentation = false;
2053
2054 switch (font->emoji_presentation) {
2055 case FCFT_EMOJI_PRESENTATION_TEXT:
2056 force_text_presentation = true;
2057 force_emoji_presentation = false;
2058 break;
2059
2060 case FCFT_EMOJI_PRESENTATION_EMOJI:
2061 force_text_presentation = false;
2062 force_emoji_presentation = true;
2063 break;
2064
2065 case FCFT_EMOJI_PRESENTATION_DEFAULT:
2066 force_text_presentation = !emoji->emoji_presentation;
2067 force_emoji_presentation = emoji->emoji_presentation;
2068 break;
2069 }
2070
2071 if (force_text_presentation && has_lang_emoji) {
2072 has_all_code_points = false;
2073 continue;
2074 }
2075
2076 if (force_emoji_presentation && !has_lang_emoji) {
2077 has_all_code_points = false;
2078 continue;
2079 }
2080 }
2081
2082 if (cluster[i] == 0x200d) {
2083 /* ZWJ */
2084 continue;
2085 }
2086
2087 if (cluster[i] == 0xfe0f) {
2088 /* Explicit emoji selector */
2089
2090 #if 0
2091 /* Require colored emoji? */
2092 if (!it->item.is_color) {
2093 /* Skip font if it is not a colored font */
2094 has_all_code_points = false;
2095 }
2096 #endif
2097 if (!has_lang_emoji) {
2098 /* Skip font if it isn't an emoji font */
2099 has_all_code_points = false;
2100 }
2101
2102 continue;
2103 }
2104
2105 else if (cluster[i] == 0xfe0e) {
2106 /* Explicit text selector */
2107
2108 if (has_lang_emoji) {
2109 /* Skip font if it is an emoji font */
2110 has_all_code_points = false;
2111 }
2112
2113 continue;
2114 }
2115
2116 if (!FcCharSetHasChar(it->item.charset, cluster[i])) {
2117 has_all_code_points = false;
2118 break;
2119 }
2120 }
2121
2122 if (has_all_code_points) {
2123 if (it->item.font == NULL) {
2124 *inst = malloc(sizeof(**inst));
2125 if (*inst == NULL)
2126 return false;
2127
2128 if (!instantiate_pattern(
2129 it->item.pattern,
2130 it->item.req_pt_size, it->item.req_px_size,
2131 *inst))
2132 {
2133 /* Remove, so that we don't have to keep trying to
2134 * instantiate it */
2135 free(*inst);
2136 fallback_destroy(&it->item);
2137 tll_remove(font->fallbacks, it);
2138 continue;
2139 }
2140
2141 it->item.font = *inst;
2142 } else
2143 *inst = it->item.font;
2144
2145 return true;
2146 }
2147 }
2148
2149 if (enforce_presentation_style)
2150 return font_for_grapheme(font, len, cluster, inst, false);
2151
2152 /* No font found, use primary font anyway */
2153 *inst = tll_front(font->fallbacks).font;
2154 return *inst != NULL;
2155 }
2156
2157 FCFT_EXPORT const struct fcft_grapheme *
fcft_grapheme_rasterize(struct fcft_font * _font,size_t len,const wchar_t cluster[static len],size_t tag_count,const struct fcft_layout_tag * tags,enum fcft_subpixel subpixel)2158 fcft_grapheme_rasterize(struct fcft_font *_font,
2159 size_t len, const wchar_t cluster[static len],
2160 size_t tag_count, const struct fcft_layout_tag *tags,
2161 enum fcft_subpixel subpixel)
2162 {
2163 struct font_priv *font = (struct font_priv *)_font;
2164 struct instance *inst = NULL;
2165
2166 pthread_rwlock_rdlock(&font->grapheme_cache_lock);
2167 struct grapheme_priv **entry = grapheme_cache_lookup(
2168 font, len, cluster, subpixel);
2169
2170 if (*entry != NULL) {
2171 const struct grapheme_priv *grapheme = *entry;
2172 pthread_rwlock_unlock(&font->grapheme_cache_lock);
2173 return grapheme->valid ? &grapheme->public : NULL;
2174 }
2175
2176 pthread_rwlock_unlock(&font->grapheme_cache_lock);
2177 mtx_lock(&font->lock);
2178
2179 /* Check again - another thread may have resized the cache, or
2180 * populated the entry while we acquired the write-lock */
2181 entry = grapheme_cache_lookup(font, len, cluster, subpixel);
2182 if (*entry != NULL) {
2183 const struct grapheme_priv *grapheme = *entry;
2184 mtx_unlock(&font->lock);
2185 return grapheme->valid ? &grapheme->public : NULL;
2186 }
2187
2188 if (grapheme_cache_resize(font)) {
2189 /* Entry pointer is invalid if the cache was resized */
2190 entry = grapheme_cache_lookup(font, len, cluster, subpixel);
2191 }
2192
2193 struct grapheme_priv *grapheme = malloc(sizeof(*grapheme));
2194 wchar_t *cluster_copy = malloc(len * sizeof(cluster_copy[0]));
2195 if (grapheme == NULL || cluster_copy == NULL) {
2196 /* Can’t update cache entry since we can’t store the cluster */
2197 free(grapheme);
2198 free(cluster_copy);
2199 mtx_unlock(&font->lock);
2200 return NULL;
2201 }
2202
2203 size_t glyph_idx = 0;
2204 wcsncpy(cluster_copy, cluster, len);
2205 grapheme->valid = false;
2206 grapheme->len = len;
2207 grapheme->cluster = cluster_copy;
2208 grapheme->subpixel = subpixel;
2209 grapheme->public.glyphs = NULL;
2210 grapheme->public.count = 0;
2211
2212 /* Find a font that has all codepoints in the grapheme */
2213 if (!font_for_grapheme(font, len, cluster, &inst, true))
2214 goto err;
2215
2216 assert(inst->hb_font != NULL);
2217
2218 hb_buffer_add_utf32(inst->hb_buf, (const uint32_t *)cluster, len, 0, len);
2219 hb_buffer_guess_segment_properties(inst->hb_buf);
2220
2221 hb_shape(inst->hb_font, inst->hb_buf, inst->hb_feats, inst->hb_feats_count);
2222
2223 unsigned count = hb_buffer_get_length(inst->hb_buf);
2224 const hb_glyph_info_t *info = hb_buffer_get_glyph_infos(inst->hb_buf, NULL);
2225 const hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(inst->hb_buf, NULL);
2226
2227 int grapheme_width = 0;
2228 int min_grapheme_width = 0;
2229 for (size_t i = 0; i < len; i++) {
2230 if (cluster[i] == 0xfe0f)
2231 min_grapheme_width = 2;
2232 grapheme_width += wcwidth(cluster[i]);
2233 }
2234
2235 LOG_DBG("length: %u", hb_buffer_get_length(inst->hb_buf));
2236 LOG_DBG("infos: %u", count);
2237
2238 struct fcft_glyph **glyphs = calloc(count, sizeof(glyphs[0]));
2239 if (glyphs == NULL)
2240 goto err;
2241
2242 grapheme->public.cols = max(grapheme_width, min_grapheme_width);
2243 grapheme->public.glyphs = (const struct fcft_glyph **)glyphs;
2244
2245 const unsigned count_from_the_beginning = count;
2246
2247 for (unsigned i = 0; i < count_from_the_beginning; i++) {
2248 LOG_DBG("code point: %04x, cluster: %u", info[i].codepoint, info[i].cluster);
2249 LOG_DBG("x-advance: %d, x-offset: %d, y-advance: %d, y-offset: %d",
2250 pos[i].x_advance, pos[i].x_offset,
2251 pos[i].y_advance, pos[i].y_offset);
2252
2253 struct glyph_priv *glyph = malloc(sizeof(*glyph));
2254 if (glyph == NULL ||
2255 !glyph_for_index(inst, info[i].codepoint, subpixel, glyph))
2256 {
2257 assert(glyph == NULL || !glyph->valid);
2258 free(glyph);
2259 goto err;
2260 }
2261
2262 assert(glyph->valid);
2263
2264 assert(info[i].cluster < len);
2265 glyph->public.wc = cluster[info[i].cluster];
2266 glyph->public.cols = wcwidth(glyph->public.wc);
2267
2268 #if 0
2269 LOG_DBG("grapheme: x: advance: %d -> %d, offset: %d -> %d",
2270 glyph->public.advance.x,
2271 (int)(pos[i].x_advance / 64. * inst->pixel_size_fixup),
2272 glyph->public.x,
2273 (int)(pos[i].x_offset / 64. * inst->pixel_size_fixup));
2274 LOG_DBG("grapheme: y: advance: %d -> %d, offset: %d -> %d",
2275 glyph->public.advance.y,
2276 (int)(pos[i].y_advance / 64. * inst->pixel_size_fixup),
2277 glyph->public.y,
2278 (int)(pos[i].y_offset / 64. * inst->pixel_size_fixup));
2279 #endif
2280
2281 glyph->public.x += pos[i].x_offset / 64. * inst->pixel_size_fixup;
2282 glyph->public.y += pos[i].y_offset / 64. * inst->pixel_size_fixup;
2283 glyph->public.advance.x = pos[i].x_advance / 64. * inst->pixel_size_fixup;
2284 glyph->public.advance.y = pos[i].y_advance / 64. * inst->pixel_size_fixup;
2285
2286 grapheme->public.glyphs[glyph_idx++] = &glyph->public;
2287 }
2288
2289 #if defined(_DEBUG)
2290 assert(glyph_idx == count);
2291 for (size_t i = 0; i < count; i++) {
2292 const struct glyph_priv *g
2293 = (const struct glyph_priv *)grapheme->public.glyphs[i];
2294
2295 assert(g != NULL);
2296 assert(g->valid);
2297 }
2298 #endif
2299
2300 hb_buffer_clear_contents(inst->hb_buf);
2301
2302 assert(*entry == NULL);
2303 grapheme->public.count = glyph_idx;
2304 grapheme->valid = true;
2305 *entry = grapheme;
2306 font->grapheme_cache.count++;
2307
2308 mtx_unlock(&font->lock);
2309 return &grapheme->public;
2310
2311 err:
2312 for (size_t i = 0; i < glyph_idx; i++)
2313 glyph_destroy(grapheme->public.glyphs[i]);
2314 free(grapheme->public.glyphs);
2315
2316 assert(*entry == NULL);
2317 assert(!grapheme->valid);
2318 grapheme->public.count = 0;
2319 grapheme->public.glyphs = NULL;
2320 *entry = grapheme;
2321 font->grapheme_cache.count++;
2322 mtx_unlock(&font->lock);
2323 return NULL;
2324 }
2325 #else /* !FCFT_HAVE_HARFBUZZ */
2326
2327 FCFT_EXPORT const struct fcft_grapheme *
fcft_grapheme_rasterize(struct fcft_font * _font,size_t len,const wchar_t cluster[static len],size_t tag_count,const struct fcft_layout_tag * tags,enum fcft_subpixel subpixel)2328 fcft_grapheme_rasterize(struct fcft_font *_font,
2329 size_t len, const wchar_t cluster[static len],
2330 size_t tag_count, const struct fcft_layout_tag *tags,
2331 enum fcft_subpixel subpixel)
2332 {
2333 return NULL;
2334 }
2335
2336 #endif
2337
2338 #if defined(FCFT_HAVE_HARFBUZZ) && defined(FCFT_HAVE_UTF8PROC)
2339 struct text_run {
2340 struct fcft_text_run *public;
2341 size_t size;
2342 };
2343
2344 static bool
rasterize_partial_run(struct text_run * run,const struct instance * inst,const uint32_t * text,size_t len,size_t run_start,size_t run_len,enum fcft_subpixel subpixel)2345 rasterize_partial_run(struct text_run *run, const struct instance *inst,
2346 const uint32_t *text, size_t len,
2347 size_t run_start, size_t run_len,
2348 enum fcft_subpixel subpixel)
2349 {
2350 hb_buffer_add_utf32(inst->hb_buf, text, len, run_start, run_len);
2351 hb_buffer_guess_segment_properties(inst->hb_buf);
2352
2353 hb_segment_properties_t props;
2354 hb_buffer_get_segment_properties(inst->hb_buf, &props);
2355
2356 assert(props.direction == HB_DIRECTION_LTR ||
2357 props.direction == HB_DIRECTION_RTL);
2358
2359 if (props.direction != HB_DIRECTION_LTR &&
2360 props.direction != HB_DIRECTION_RTL)
2361 {
2362 LOG_ERR("unimplemented: hb_direction=%d", props.direction);
2363 return false;
2364 }
2365
2366 hb_shape(inst->hb_font, inst->hb_buf, inst->hb_feats, inst->hb_feats_count);
2367
2368 unsigned count = hb_buffer_get_length(inst->hb_buf);
2369 const hb_glyph_info_t *infos = hb_buffer_get_glyph_infos(inst->hb_buf, NULL);
2370 const hb_glyph_position_t *poss = hb_buffer_get_glyph_positions(inst->hb_buf, NULL);
2371
2372 for (int i = 0; i < count; i++) {
2373 const hb_glyph_info_t *info = &infos[i];
2374 const hb_glyph_position_t *pos = &poss[i];
2375
2376 LOG_DBG("#%u: codepoint=%04x, cluster=%d", i, info->codepoint, info->cluster);
2377
2378 struct glyph_priv *glyph = malloc(sizeof(*glyph));
2379 if (glyph == NULL)
2380 return false;
2381
2382 if (!glyph_for_index(inst, info->codepoint, subpixel, glyph)) {
2383 free(glyph);
2384 continue;
2385 }
2386
2387 assert(info->cluster < len);
2388 glyph->public.wc = text[info->cluster];
2389 glyph->public.cols = wcwidth(glyph->public.wc);
2390
2391 glyph->public.x += pos->x_offset / 64. * inst->pixel_size_fixup;
2392 glyph->public.y += pos->y_offset / 64. * inst->pixel_size_fixup;
2393 glyph->public.advance.x = pos->x_advance / 64. * inst->pixel_size_fixup;
2394 glyph->public.advance.y = pos->y_advance / 64. * inst->pixel_size_fixup;
2395
2396 if (run->public->count >= run->size) {
2397 size_t new_glyphs_size = run->size * 2;
2398 const struct fcft_glyph **new_glyphs = realloc(
2399 run->public->glyphs, new_glyphs_size * sizeof(new_glyphs[0]));
2400 int *new_cluster = realloc(
2401 run->public->cluster, new_glyphs_size * sizeof(new_cluster[0]));
2402
2403 if (new_glyphs == NULL || new_cluster == NULL) {
2404 free(new_glyphs);
2405 free(new_cluster);
2406 return false;
2407 }
2408
2409 run->public->glyphs = new_glyphs;
2410 run->public->cluster = new_cluster;
2411 run->size = new_glyphs_size;
2412 }
2413
2414 assert(run->public->count < run->size);
2415 run->public->cluster[run->public->count] = info->cluster;
2416 run->public->glyphs[run->public->count] = &glyph->public;
2417 run->public->count++;
2418 }
2419
2420 return true;
2421 }
2422
2423 FCFT_EXPORT struct fcft_text_run *
fcft_text_run_rasterize(struct fcft_font * _font,size_t len,const wchar_t text[static len],enum fcft_subpixel subpixel)2424 fcft_text_run_rasterize(
2425 struct fcft_font *_font, size_t len, const wchar_t text[static len],
2426 enum fcft_subpixel subpixel)
2427 {
2428 struct font_priv *font = (struct font_priv *)_font;
2429 mtx_lock(&font->lock);
2430
2431 LOG_DBG("rasterizing a %zu character text run", len);
2432
2433 struct partial_run {
2434 size_t start;
2435 size_t len;
2436 struct instance *inst;
2437 };
2438
2439 tll(struct partial_run) pruns = tll_init();
2440
2441 struct text_run run = {
2442 .size = len,
2443 .public = malloc(sizeof(*run.public)),
2444 };
2445
2446 if (run.public == NULL)
2447 goto err;
2448
2449 run.public->glyphs = malloc(len * sizeof(run.public->glyphs[0]));
2450 run.public->cluster = malloc(len * sizeof(run.public->cluster[0]));
2451 run.public->count = 0;
2452
2453 if (run.public->glyphs == NULL || run.public->cluster == NULL)
2454 goto err;
2455
2456
2457 tll_push_back(pruns, ((struct partial_run){.start = 0}));
2458
2459 /* Split run into graphemes */
2460 utf8proc_int32_t state;
2461 for (size_t i = 1; i < len; i++) {
2462 if (utf8proc_grapheme_break_stateful(text[i - 1], text[i], &state)) {
2463 state = 0;
2464
2465 struct partial_run *prun = &tll_back(pruns);
2466
2467 assert(i > prun->start);
2468 prun->len = i - prun->start;
2469
2470 if (!font_for_grapheme(
2471 font, prun->len, &text[prun->start], &prun->inst, true))
2472 {
2473 goto err;
2474 }
2475
2476 tll_push_back(pruns, ((struct partial_run){.start = i}));
2477 }
2478 }
2479
2480 /* “Close” that last run */
2481 {
2482 struct partial_run *prun = &tll_back(pruns);
2483
2484 assert(prun->len == 0);
2485 prun->len = len - prun->start;
2486
2487 if (!font_for_grapheme(
2488 font, prun->len, &text[prun->start], &prun->inst, true))
2489 {
2490 goto err;
2491 }
2492 }
2493
2494 #if defined(_DEBUG) && LOG_ENABLE_DBG
2495 LOG_DBG("%zu partial runs (before merge):", tll_length(pruns));
2496 tll_foreach(pruns, it) {
2497 const struct partial_run *prun = &it->item;
2498 LOG_DBG(" %.*ls (start=%zu, %zu chars), inst=%p",
2499 (int)prun->len, &text[prun->start], prun->start,
2500 prun->len, prun->inst);
2501 }
2502 #endif
2503
2504 /*
2505 * Merge consecutive graphemes if:
2506 * - they belong to the same script (“language”)
2507 * - they have the same font instance
2508 */
2509 {
2510 hb_buffer_t *hb_buf = hb_buffer_create();
2511 if (hb_buf == NULL)
2512 goto err;
2513
2514 struct partial_run *prev = NULL;
2515 hb_script_t prev_script = HB_SCRIPT_INVALID;
2516
2517 tll_foreach(pruns, it) {
2518 struct partial_run *prun = &it->item;
2519
2520 /* Get script (“language”) of grapheme */
2521 hb_buffer_add_utf32(
2522 hb_buf, (const uint32_t *)text, len, prun->start, prun->len);
2523 hb_buffer_guess_segment_properties(hb_buf);
2524
2525 hb_script_t script = hb_buffer_get_script(hb_buf);
2526 hb_buffer_clear_contents(hb_buf);
2527
2528 if (prev == NULL) {
2529 prev = prun;
2530 prev_script = script;
2531 continue;
2532 }
2533
2534 if (prev->inst == prun->inst && prev_script == script) {
2535 prev->len += prun->len;
2536 tll_remove(pruns, it);
2537 } else {
2538 prev = prun;
2539 prev_script = script;
2540 }
2541 }
2542
2543 hb_buffer_destroy(hb_buf);
2544 }
2545
2546 #if defined(_DEBUG) && LOG_ENABLE_DBG
2547 LOG_DBG("%zu partial runs (after merge):", tll_length(pruns));
2548 tll_foreach(pruns, it) {
2549 const struct partial_run *prun = &it->item;
2550 LOG_DBG(" %.*ls (start=%zu, %zu chars), inst=%p",
2551 (int)prun->len, &text[prun->start], prun->start,
2552 prun->len, prun->inst);
2553 }
2554 #endif
2555
2556 /* Shape each partial run */
2557 tll_foreach(pruns, it) {
2558 const struct partial_run *prun = &it->item;
2559
2560 bool ret = rasterize_partial_run(
2561 &run, prun->inst, (const uint32_t *)text, len,
2562 prun->start, prun->len, subpixel);
2563
2564 hb_buffer_clear_contents(prun->inst->hb_buf);
2565 if (!ret)
2566 goto err;
2567 }
2568
2569 /* Re-alloc glyphs/cluster arrays */
2570 {
2571 const struct fcft_glyph **final_glyphs = realloc(
2572 run.public->glyphs, run.public->count * sizeof(final_glyphs[0]));
2573 int *final_cluster = realloc(
2574 run.public->cluster, run.public->count * sizeof(final_cluster[0]));
2575
2576 if ((final_glyphs == NULL || final_cluster == NULL) &&
2577 run.public->count > 0)
2578 {
2579 free(final_glyphs);
2580 free(final_cluster);
2581 goto err;
2582 }
2583
2584 run.public->glyphs = final_glyphs;
2585 run.public->cluster = final_cluster;
2586 }
2587
2588 LOG_DBG("glyph count: %zu", run.public->count);
2589
2590 tll_free(pruns);
2591 mtx_unlock(&font->lock);
2592 return run.public;
2593
2594 err:
2595
2596 if (run.public != NULL) {
2597 for (size_t i = 0; i < run.public->count; i++) {
2598 assert(run.public->glyphs[i] != NULL);
2599 glyph_destroy(run.public->glyphs[i]);
2600 }
2601
2602 free(run.public->glyphs);
2603 free(run.public->cluster);
2604 free(run.public);
2605 }
2606
2607 tll_free(pruns);
2608 mtx_unlock(&font->lock);
2609 return NULL;
2610 }
2611
2612 #else /* !FCFT_HAVE_HARFBUZZ || !FCFT_HAVE_UTF8PROC */
2613
2614 FCFT_EXPORT struct fcft_text_run *
fcft_text_run_rasterize(struct fcft_font * font,size_t len,const wchar_t text[static len],enum fcft_subpixel subpixel)2615 fcft_text_run_rasterize(
2616 struct fcft_font *font, size_t len, const wchar_t text[static len],
2617 enum fcft_subpixel subpixel)
2618 {
2619 return NULL;
2620 }
2621
2622 #endif /* !FCFT_HAVE_HARFBUZZ */
2623
2624 FCFT_EXPORT void
fcft_text_run_destroy(struct fcft_text_run * run)2625 fcft_text_run_destroy(struct fcft_text_run *run)
2626 {
2627 if (run == NULL)
2628 return;
2629
2630 for (size_t i = 0; i < run->count; i++) {
2631 assert(run->glyphs[i] != NULL);
2632 glyph_destroy(run->glyphs[i]);
2633 }
2634
2635 free(run->glyphs);
2636 free(run->cluster);
2637 free(run);
2638 }
2639
2640 FCFT_EXPORT void
fcft_destroy(struct fcft_font * _font)2641 fcft_destroy(struct fcft_font *_font)
2642 {
2643 if (_font == NULL)
2644 return;
2645
2646 struct font_priv *font = (struct font_priv *)_font;
2647
2648 bool in_cache = false;
2649 mtx_lock(&font_cache_lock);
2650 tll_foreach(font_cache, it) {
2651 if (it->item.font == font) {
2652
2653 in_cache = true;
2654
2655 mtx_lock(&font->lock);
2656 if (--font->ref_counter > 0) {
2657 mtx_unlock(&font->lock);
2658 mtx_unlock(&font_cache_lock);
2659 return;
2660 }
2661 mtx_unlock(&font->lock);
2662
2663 cnd_destroy(&it->item.cond);
2664 tll_remove(font_cache, it);
2665 break;
2666 }
2667 };
2668 mtx_unlock(&font_cache_lock);
2669
2670 if (!in_cache) {
2671 mtx_lock(&font->lock);
2672 if (--font->ref_counter > 0) {
2673 mtx_unlock(&font->lock);
2674 return;
2675 }
2676 mtx_unlock(&font->lock);
2677 }
2678
2679 tll_foreach(font->fallbacks, it)
2680 fallback_destroy(&it->item);
2681
2682 tll_free(font->fallbacks);
2683 mtx_destroy(&font->lock);
2684
2685 for (size_t i = 0;
2686 i < font->glyph_cache.size && font->glyph_cache.table != NULL;
2687 i++)
2688 {
2689 struct glyph_priv *entry = font->glyph_cache.table[i];
2690
2691 if (entry == NULL)
2692 continue;
2693
2694 glyph_destroy_private(entry);
2695 }
2696 free(font->glyph_cache.table);
2697 pthread_rwlock_destroy(&font->glyph_cache_lock);
2698
2699 #if defined(FCFT_HAVE_HARFBUZZ)
2700 for (size_t i = 0;
2701 i < font->grapheme_cache.size && font->grapheme_cache.table != NULL;
2702 i++)
2703 {
2704 struct grapheme_priv *entry = font->grapheme_cache.table[i];
2705
2706 if (entry == NULL)
2707 continue;
2708
2709 for (size_t j = 0; j < entry->public.count; j++) {
2710 assert(entry->public.glyphs[j] != NULL);
2711 glyph_destroy(entry->public.glyphs[j]);
2712 }
2713
2714 free(entry->public.glyphs);
2715 free(entry->cluster);
2716 free(entry);
2717 }
2718 free(font->grapheme_cache.table);
2719 pthread_rwlock_destroy(&font->grapheme_cache_lock);
2720 #endif
2721
2722 free(font);
2723 }
2724
2725 FCFT_EXPORT bool
fcft_kerning(struct fcft_font * _font,wchar_t left,wchar_t right,long * restrict x,long * restrict y)2726 fcft_kerning(struct fcft_font *_font, wchar_t left, wchar_t right,
2727 long *restrict x, long *restrict y)
2728 {
2729 struct font_priv *font = (struct font_priv *)_font;
2730
2731 if (x != NULL)
2732 *x = 0;
2733 if (y != NULL)
2734 *y = 0;
2735
2736 mtx_lock(&font->lock);
2737
2738 assert(tll_length(font->fallbacks) > 0);
2739 const struct instance *primary = tll_front(font->fallbacks).font;
2740
2741 if (!FT_HAS_KERNING(primary->face))
2742 goto err;
2743
2744 FT_UInt left_idx = FT_Get_Char_Index(primary->face, left);
2745 if (left_idx == 0)
2746 goto err;
2747
2748 FT_UInt right_idx = FT_Get_Char_Index(primary->face, right);
2749 if (right_idx == 0)
2750 goto err;
2751
2752 FT_Vector kerning;
2753 FT_Error err = FT_Get_Kerning(
2754 primary->face, left_idx, right_idx, FT_KERNING_DEFAULT, &kerning);
2755
2756 if (err != 0) {
2757 LOG_WARN("%s: failed to get kerning for %lc -> %lc: %s",
2758 primary->path, (wint_t)left, (wint_t)right,
2759 ft_error_string(err));
2760 goto err;
2761 }
2762
2763 if (x != NULL)
2764 *x = kerning.x / 64. * primary->pixel_size_fixup;
2765 if (y != NULL)
2766 *y = kerning.y / 64. * primary->pixel_size_fixup;
2767
2768 LOG_DBG("%s: kerning: %lc -> %lc: x=%ld 26.6, y=%ld 26.6",
2769 primary->path, (wint_t)left, (wint_t)right,
2770 kerning.x, kerning.y);
2771
2772 mtx_unlock(&font->lock);
2773 return true;
2774
2775 err:
2776 mtx_unlock(&font->lock);
2777 return false;
2778 }
2779
2780 FCFT_EXPORT wchar_t
fcft_precompose(const struct fcft_font * _font,wchar_t base,wchar_t comb,bool * base_is_from_primary,bool * comb_is_from_primary,bool * composed_is_from_primary)2781 fcft_precompose(const struct fcft_font *_font, wchar_t base, wchar_t comb,
2782 bool *base_is_from_primary,
2783 bool *comb_is_from_primary,
2784 bool *composed_is_from_primary)
2785 {
2786 _Static_assert(2 * sizeof(wchar_t) <= sizeof(uint64_t),
2787 "two wchars does not fit in an uint64_t");
2788
2789 const struct font_priv *font = (const struct font_priv *)_font;
2790
2791 assert(tll_length(font->fallbacks) > 0);
2792 const struct fallback *primary = &tll_front(font->fallbacks);
2793
2794 if (font != NULL) {
2795 if (base_is_from_primary != NULL)
2796 *base_is_from_primary = FcCharSetHasChar(primary->charset, base);
2797 if (comb_is_from_primary != NULL)
2798 *comb_is_from_primary = FcCharSetHasChar(primary->charset, comb);
2799 }
2800
2801 const uint64_t match = (uint64_t)base << 32 | comb;
2802
2803 ssize_t start = 0;
2804 ssize_t end = (sizeof(precompose_table) / sizeof(precompose_table[0])) - 1;
2805
2806 while (start <= end) {
2807 size_t middle = (start + end) / 2;
2808
2809 const uint64_t maybe =
2810 (uint64_t)precompose_table[middle].base << 32 | precompose_table[middle].comb;
2811
2812 if (maybe < match)
2813 start = middle + 1;
2814 else if (maybe > match)
2815 end = middle - 1;
2816 else {
2817 wchar_t composed = precompose_table[middle].replacement;
2818 if (font != NULL && composed_is_from_primary != NULL) {
2819 *composed_is_from_primary = FcCharSetHasChar(
2820 primary->charset, composed);
2821 }
2822 return composed;
2823 }
2824 }
2825
2826 if (composed_is_from_primary != NULL)
2827 *composed_is_from_primary = false;
2828 return (wchar_t)-1;
2829 }
2830
2831 FCFT_EXPORT void
fcft_set_emoji_presentation(struct fcft_font * _font,enum fcft_emoji_presentation presentation)2832 fcft_set_emoji_presentation(struct fcft_font *_font,
2833 enum fcft_emoji_presentation presentation)
2834 {
2835 struct font_priv *font = (struct font_priv *)_font;
2836 font->emoji_presentation = presentation;
2837 }
2838