From a789230cf9f87c68fcced647d0a1e8edc516f1c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 16 Oct 2019 21:52:12 +0200 Subject: [PATCH 1/4] font: font_from_name() returns an allocated font struct --- font.c | 10 +++++++--- font.h | 2 +- main.c | 20 ++++++++++---------- render.c | 4 ++-- terminal.h | 2 +- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/font.c b/font.c index 049a3be8..b68e341c 100644 --- a/font.c +++ b/font.c @@ -291,8 +291,8 @@ from_name(const char *base_name, const font_list_t *fallbacks, const char *attri return true; } -bool -font_from_name(font_list_t names, const char *attributes, struct font *font) +struct font * +font_from_name(font_list_t names, const char *attributes) { if (tll_length(names) == 0) return false; @@ -308,10 +308,14 @@ font_from_name(font_list_t names, const char *attributes, struct font *font) tll_push_back(fallbacks, it->item); } + struct font *font = malloc(sizeof(*font)); bool ret = from_name(tll_front(names), &fallbacks, attributes, font, false); + if (!ret) + free(font); + tll_free(fallbacks); - return ret; + return ret ? font : NULL; } static size_t diff --git a/font.h b/font.h index 594ae6ec..1d5ad4dc 100644 --- a/font.h +++ b/font.h @@ -58,6 +58,6 @@ struct font { mtx_t lock; }; -bool font_from_name(font_list_t names, const char *attributes, struct font *result); +struct font *font_from_name(font_list_t names, const char *attributes); const struct glyph *font_glyph_for_wc(struct font *font, wchar_t wc); void font_destroy(struct font *font); diff --git a/main.c b/main.c index 0f93e188..e81d2395 100644 --- a/main.c +++ b/main.c @@ -623,28 +623,28 @@ main(int argc, char *const *argv) tll_foreach(conf.fonts, it) tll_push_back(font_names, it->item); - if (!font_from_name(font_names, "", &term.fonts[0])) { + if ((term.fonts[0] = font_from_name(font_names, "")) == NULL) { tll_free(font_names); goto out; } - font_from_name(font_names, "style=bold", &term.fonts[1]); - font_from_name(font_names, "style=italic", &term.fonts[2]); - font_from_name(font_names, "style=bold italic", &term.fonts[3]); + term.fonts[1] = font_from_name(font_names, "style=bold"); + term.fonts[2] = font_from_name(font_names, "style=italic"); + term.fonts[3] = font_from_name(font_names, "style=bold italic"); tll_free(font_names); { - FT_Face ft_face = term.fonts[0].face; + FT_Face ft_face = term.fonts[0]->face; int max_x_advance = ft_face->size->metrics.max_advance / 64; int height = ft_face->size->metrics.height / 64; int descent = ft_face->size->metrics.descender / 64; int ascent = ft_face->size->metrics.ascender / 64; - term.fextents.height = height * term.fonts[0].pixel_size_fixup; - term.fextents.descent = -descent * term.fonts[0].pixel_size_fixup; - term.fextents.ascent = ascent * term.fonts[0].pixel_size_fixup; - term.fextents.max_x_advance = max_x_advance * term.fonts[0].pixel_size_fixup; + term.fextents.height = height * term.fonts[0]->pixel_size_fixup; + term.fextents.descent = -descent * term.fonts[0]->pixel_size_fixup; + term.fextents.ascent = ascent * term.fonts[0]->pixel_size_fixup; + term.fextents.max_x_advance = max_x_advance * term.fonts[0]->pixel_size_fixup; LOG_DBG("metrics: height: %d, descent: %d, ascent: %d, x-advance: %d", height, descent, ascent, max_x_advance); @@ -1185,7 +1185,7 @@ out: tll_free_and_free(term.window_title_stack, free); for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++) - font_destroy(&term.fonts[i]); + font_destroy(term.fonts[i]); free(term.search.buf); diff --git a/render.c b/render.c index bfd27faa..ff05d501 100644 --- a/render.c +++ b/render.c @@ -24,7 +24,7 @@ struct font * attrs_to_font(struct terminal *term, const struct attributes *attrs) { int idx = attrs->italic << 1 | attrs->bold; - return &term->fonts[idx]; + return term->fonts[idx]; } static inline struct rgb @@ -750,7 +750,7 @@ render_search_box(struct terminal *term) PIXMAN_OP_SRC, buf->pix, &color, 1, &(pixman_rectangle16_t){0, 0, width, height}); - struct font *font = &term->fonts[0]; + struct font *font = term->fonts[0]; int x = margin; int y = margin; pixman_color_t fg = color_hex_to_pixman(term->colors.table[0]); diff --git a/terminal.h b/terminal.h index 72a41fcf..6f6e1d53 100644 --- a/terminal.h +++ b/terminal.h @@ -366,7 +366,7 @@ struct terminal { struct grid alt; struct grid *grid; - struct font fonts[4]; + struct font *fonts[4]; struct { int height; int descent; From 928e86b423f5ca5905a4b9525d25ac0e469564c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 16 Oct 2019 22:18:57 +0200 Subject: [PATCH 2/4] font: cache loaded fontconfig fallback fonts --- font.c | 70 ++++++++++++++++++++++++++++++++++++++++++---------------- font.h | 13 ++++++----- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/font.c b/font.c index b68e341c..135b4dee 100644 --- a/font.c +++ b/font.c @@ -212,11 +212,18 @@ from_font_set(FcPattern *pattern, FcFontSet *fonts, int start_idx, const font_li font->is_fallback = is_fallback; font->pixel_size_fixup = scalable ? pixel_fixup : 1.; font->bgr = fc_rgba == FC_RGBA_BGR || fc_rgba == FC_RGBA_VBGR; - font->fc_idx = font_idx; - if (!is_fallback) { - font->fc_pattern = pattern; - font->fc_fonts = fonts; + if (is_fallback) { + font->fc_idx = 0; + font->fc_pattern = NULL; + font->fc_fonts = NULL; + font->fc_loaded_fonts = NULL; + font->cache = NULL; + } else { + font->fc_idx = font_idx; + font->fc_pattern = !is_fallback ? pattern : NULL; + font->fc_fonts = !is_fallback ? fonts : NULL; + font->fc_loaded_fonts = calloc(fonts->nfont, sizeof(font->fc_loaded_fonts[0])); font->cache = calloc(cache_size, sizeof(font->cache[0])); } @@ -341,19 +348,20 @@ glyph_for_wchar(const struct font *font, wchar_t wc, struct glyph *glyph) FT_UInt idx = FT_Get_Char_Index(font->face, wc); if (idx == 0) { /* No glyph in this font, try fallback fonts */ - struct font fallback; /* Try user configured fallback fonts */ tll_foreach(font->fallbacks, it) { - if (from_name(it->item, NULL, "", &fallback, true)) { - if (glyph_for_wchar(&fallback, wc, glyph)) { + struct font *fallback = malloc(sizeof(*fallback)); + + if (from_name(it->item, NULL, "", fallback, true)) { + if (glyph_for_wchar(fallback, wc, glyph)) { LOG_DBG("%C: used fallback %s (fixup = %f)", - wc, it->item, fallback.pixel_size_fixup); - font_destroy(&fallback); + wc, it->item, fallback->pixel_size_fixup); + font_destroy(fallback); return true; } - font_destroy(&fallback); + font_destroy(fallback); } } @@ -364,17 +372,30 @@ glyph_for_wchar(const struct font *font, wchar_t wc, struct glyph *glyph) assert(font->fc_pattern != NULL); assert(font->fc_fonts != NULL); + assert(font->fc_loaded_fonts != NULL); assert(font->fc_idx != -1); for (int i = font->fc_idx + 1; i < font->fc_fonts->nfont; i++) { - if (from_font_set(font->fc_pattern, font->fc_fonts, i, NULL, "", &fallback, true)) { - if (glyph_for_wchar(&fallback, wc, glyph)) { - LOG_DBG("%C: used fontconfig fallback", wc); - font_destroy(&fallback); - return true; + if (font->fc_loaded_fonts[i] == NULL) { + /* Load font */ + struct font *fallback = malloc(sizeof(*fallback)); + if (!from_font_set(font->fc_pattern, font->fc_fonts, i, NULL, + "", fallback, true)) + { + LOG_WARN("failed to load fontconfig fallback font"); + free(fallback); + continue; } - font_destroy(&fallback); + LOG_DBG("loaded new fontconfig fallback font"); + font->fc_loaded_fonts[i] = fallback; + } + + assert(font->fc_loaded_fonts[i] != NULL); + + if (glyph_for_wchar(font->fc_loaded_fonts[i], wc, glyph)) { + LOG_DBG("%C: used fontconfig fallback", wc); + return true; } } @@ -584,6 +605,9 @@ font_glyph_for_wc(struct font *font, wchar_t wc) void font_destroy(struct font *font) { + if (font == NULL) + return; + tll_free_and_free(font->fallbacks, free); if (font->face != NULL) { @@ -594,15 +618,22 @@ font_destroy(struct font *font) mtx_destroy(&font->lock); + if (font->fc_fonts != NULL) { + assert(font->fc_loaded_fonts != NULL); + + for (size_t i = 0; i < font->fc_fonts->nfont; i++) + font_destroy(font->fc_loaded_fonts[i]); + + free(font->fc_loaded_fonts); + } + if (font->fc_pattern != NULL) FcPatternDestroy(font->fc_pattern); if (font->fc_fonts != NULL) FcFontSetDestroy(font->fc_fonts); - if (font->cache == NULL) - return; - for (size_t i = 0; i < cache_size; i++) { + for (size_t i = 0; i < cache_size && font->cache != NULL; i++) { if (font->cache[i] == NULL) continue; @@ -619,4 +650,5 @@ font_destroy(struct font *font) free(font->cache[i]); } free(font->cache); + free(font); } diff --git a/font.h b/font.h index 1d5ad4dc..424bbb7a 100644 --- a/font.h +++ b/font.h @@ -30,14 +30,12 @@ struct glyph { typedef tll(struct glyph) hash_entry_t; struct font { - FcPattern *fc_pattern; - FcFontSet *fc_fonts; - int fc_idx; - + mtx_t lock; FT_Face face; int load_flags; int render_flags; FT_LcdFilter lcd_filter; + double pixel_size_fixup; /* Scale factor - should only be used with ARGB32 glyphs */ bool bgr; /* True for FC_RGBA_BGR and FC_RGBA_VBGR */ @@ -54,8 +52,13 @@ struct font { bool is_fallback; tll(char *) fallbacks; + /* Fields below are only valid for non-fallback fonts */ + FcPattern *fc_pattern; + FcFontSet *fc_fonts; + int fc_idx; + struct font **fc_loaded_fonts; /* fc_fonts->nfont array */ + hash_entry_t **cache; - mtx_t lock; }; struct font *font_from_name(font_list_t names, const char *attributes); From bf5ad13df09d5c237decf73cd8675c65e56fc2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 16 Oct 2019 22:22:26 +0200 Subject: [PATCH 3/4] font: from_name() returns an allocated font struct --- font.c | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/font.c b/font.c index 135b4dee..17de9859 100644 --- a/font.c +++ b/font.c @@ -247,8 +247,8 @@ from_font_set(FcPattern *pattern, FcFontSet *fonts, int start_idx, const font_li return true; } -static bool -from_name(const char *base_name, const font_list_t *fallbacks, const char *attributes, struct font *font, bool is_fallback) +static struct font * +from_name(const char *base_name, const font_list_t *fallbacks, const char *attributes, bool is_fallback) { size_t attr_len = attributes == NULL ? 0 : strlen(attributes); bool have_attrs = attr_len > 0; @@ -265,13 +265,13 @@ from_name(const char *base_name, const font_list_t *fallbacks, const char *attri FcPattern *pattern = FcNameParse((const unsigned char *)name); if (pattern == NULL) { LOG_ERR("%s: failed to lookup font", name); - return false; + return NULL; } if (!FcConfigSubstitute(NULL, pattern, FcMatchPattern)) { LOG_ERR("%s: failed to do config substitution", name); FcPatternDestroy(pattern); - return false; + return NULL; } FcDefaultSubstitute(pattern); @@ -281,13 +281,16 @@ from_name(const char *base_name, const font_list_t *fallbacks, const char *attri if (result != FcResultMatch) { LOG_ERR("%s: failed to match font", name); FcPatternDestroy(pattern); - return false; + return NULL; } + struct font *font = malloc(sizeof(*font)); + if (!from_font_set(pattern, fonts, 0, fallbacks, attributes, font, is_fallback)) { + free(font); FcFontSetDestroy(fonts); FcPatternDestroy(pattern); - return false; + return NULL; } if (is_fallback) { @@ -295,7 +298,7 @@ from_name(const char *base_name, const font_list_t *fallbacks, const char *attri FcPatternDestroy(pattern); } - return true; + return font; } struct font * @@ -315,14 +318,10 @@ font_from_name(font_list_t names, const char *attributes) tll_push_back(fallbacks, it->item); } - struct font *font = malloc(sizeof(*font)); - bool ret = from_name(tll_front(names), &fallbacks, attributes, font, false); - - if (!ret) - free(font); + struct font *font = from_name(tll_front(names), &fallbacks, attributes, false); tll_free(fallbacks); - return ret ? font : NULL; + return font; } static size_t @@ -351,17 +350,15 @@ glyph_for_wchar(const struct font *font, wchar_t wc, struct glyph *glyph) /* Try user configured fallback fonts */ tll_foreach(font->fallbacks, it) { - struct font *fallback = malloc(sizeof(*fallback)); - - if (from_name(it->item, NULL, "", fallback, true)) { - if (glyph_for_wchar(fallback, wc, glyph)) { - LOG_DBG("%C: used fallback %s (fixup = %f)", - wc, it->item, fallback->pixel_size_fixup); - font_destroy(fallback); - return true; - } + struct font *fallback = from_name(it->item, NULL, "", true); + if (fallback == NULL) + continue; + if (glyph_for_wchar(fallback, wc, glyph)) { + LOG_DBG("%C: used fallback %s (fixup = %f)", + wc, it->item, fallback->pixel_size_fixup); font_destroy(fallback); + return true; } } From 04edd960186dd316858d3b4d68298a01465043e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 16 Oct 2019 22:34:23 +0200 Subject: [PATCH 4/4] font: cache top-level fonts This greatly improves the performance when loading user-configured fallback fonts. Previously, we had to re-load these fallback fonts over and over again for each (new) glyph. --- font.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- font.h | 2 ++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/font.c b/font.c index 17de9859..576a4f3f 100644 --- a/font.c +++ b/font.c @@ -20,6 +20,12 @@ static FT_Library ft_lib; static mtx_t ft_lock; +struct font_cache_entry { + uint64_t hash; + struct font *font; +}; +static tll(struct font_cache_entry) font_cache = tll_init(); + static const size_t cache_size = 512; static void __attribute__((constructor)) @@ -33,6 +39,8 @@ init(void) static void __attribute__((destructor)) fini(void) { + assert(tll_length(font_cache) == 0); + mtx_destroy(&ft_lock); FT_Done_FreeType(ft_lib); FcFini(); @@ -212,6 +220,7 @@ from_font_set(FcPattern *pattern, FcFontSet *fonts, int start_idx, const font_li font->is_fallback = is_fallback; font->pixel_size_fixup = scalable ? pixel_fixup : 1.; font->bgr = fc_rgba == FC_RGBA_BGR || fc_rgba == FC_RGBA_VBGR; + font->ref_counter = 1; if (is_fallback) { font->fc_idx = 0; @@ -247,9 +256,49 @@ from_font_set(FcPattern *pattern, FcFontSet *fonts, int start_idx, const font_li return true; } -static struct font * -from_name(const char *base_name, const font_list_t *fallbacks, const char *attributes, bool is_fallback) +static uint64_t +hash_font(const char *base_name, const font_list_t *fallbacks, + const char *attributes, bool is_fallback) { +#define rot(h, n) (((h) << (n)) | ((h) >> (64 - (n)))) + + /* TODO: better string hash */ + uint64_t hash = 0; + + for (size_t i = 0; i < strlen(base_name); i++) + hash = rot(hash, 7) ^ base_name[i]; + + if (fallbacks != NULL) { + tll_foreach(*fallbacks, it) { + for (size_t i = 0; i < strlen(it->item); i++) + hash = rot(hash, 17) ^ it->item[i]; + } + } + + if (attributes != NULL) { + for (size_t i = 0; i < strlen(attributes); i++) + hash = rot(hash, 11) ^ attributes[i]; + } + + if (is_fallback) + hash = rot(hash, 27); + + return hash; +#undef rot +} + +static struct font * +from_name(const char *base_name, const font_list_t *fallbacks, + const char *attributes, bool is_fallback) +{ + uint64_t hash = hash_font(base_name, fallbacks, attributes, is_fallback); + tll_foreach(font_cache, it) { + if (it->item.hash == hash) { + it->item.font->ref_counter++; + return it->item.font; + } + } + size_t attr_len = attributes == NULL ? 0 : strlen(attributes); bool have_attrs = attr_len > 0; @@ -298,6 +347,7 @@ from_name(const char *base_name, const font_list_t *fallbacks, const char *attri FcPatternDestroy(pattern); } + tll_push_back(font_cache, ((struct font_cache_entry){.hash = hash, .font = font})); return font; } @@ -605,6 +655,9 @@ font_destroy(struct font *font) if (font == NULL) return; + if (--font->ref_counter > 0) + return; + tll_free_and_free(font->fallbacks, free); if (font->face != NULL) { @@ -647,5 +700,13 @@ font_destroy(struct font *font) free(font->cache[i]); } free(font->cache); + + tll_foreach(font_cache, it) { + if (it->item.font == font) { + tll_remove(font_cache, it); + break; + } + } + free(font); } diff --git a/font.h b/font.h index 424bbb7a..9e912e04 100644 --- a/font.h +++ b/font.h @@ -52,6 +52,8 @@ struct font { bool is_fallback; tll(char *) fallbacks; + size_t ref_counter; + /* Fields below are only valid for non-fallback fonts */ FcPattern *fc_pattern; FcFontSet *fc_fonts;