From a5bbf0b7693db2fcd3c7e3b07dcb3e4276f9abfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 24 May 2021 17:38:43 +0200 Subject: [PATCH] particle/string: use fcft_text_run_rasterize() when available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This enables support for text shaping, and is required to render e.g. πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘§ correctly. Since text-shaping is a fairly expensive operation, and since many times the text is unchanged, we cache the last *rendered* string. That is, we hash the instantiated string, and cache it along with the text-run from fcft in the *particle* object (i.e. not the exposable). This means two things: * we only need to call fcft_text_run_rasterize() once per string * if the string is the same as last time, we don’t have to call it at all. --- meson.build | 2 +- particles/string.c | 87 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/meson.build b/meson.build index 74418af..a1e4fa1 100644 --- a/meson.build +++ b/meson.build @@ -65,7 +65,7 @@ backend_wayland = wayland_client.found() and wayland_cursor.found() # "My" dependencies, fallback to subproject tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist') -fcft = dependency('fcft', version: ['>=2.3.90', '<3.0.0'], fallback: 'fcft') +fcft = dependency('fcft', version: ['>=2.4.0', '<3.0.0'], fallback: 'fcft') add_project_arguments( ['-D_GNU_SOURCE'] + diff --git a/particles/string.c b/particles/string.c index 5e98132..abb581e 100644 --- a/particles/string.c +++ b/particles/string.c @@ -13,6 +13,12 @@ struct private { char *text; size_t max_len; + + struct { + uint64_t hash; + struct fcft_text_run *run; + int width; + } cached; }; struct eprivate { @@ -20,17 +26,31 @@ struct eprivate { char *text; const struct fcft_glyph **glyphs; + const struct fcft_glyph **allocated_glyphs; long *kern_x; int num_glyphs; }; +static uint64_t +sdbm_hash(const char *s) +{ + uint64_t hash = 0; + + for (; *s != '\0'; s++) { + int c = *s; + hash = c + (hash << 6) + (hash << 16) - hash; + } + + return hash; +} + static void exposable_destroy(struct exposable *exposable) { struct eprivate *e = exposable->private; free(e->text); - free(e->glyphs); + free(e->allocated_glyphs); free(e->kern_x); free(e); exposable_default_destroy(exposable); @@ -40,33 +60,72 @@ static int begin_expose(struct exposable *exposable) { struct eprivate *e = exposable->private; + struct private *p = exposable->particle->private; struct fcft_font *font = exposable->particle->font; - e->glyphs = NULL; + e->glyphs = e->allocated_glyphs = NULL; e->num_glyphs = 0; + e->kern_x = NULL; + + uint64_t hash = sdbm_hash(e->text); + + if (p->cached.hash == hash) { + e->glyphs = p->cached.run->glyphs; + e->num_glyphs = p->cached.run->count; + e->kern_x = calloc(p->cached.run->count, sizeof(e->kern_x[0])); + + exposable->width = + exposable->particle->left_margin + + p->cached.width + + exposable->particle->right_margin; + + return exposable->width; + } size_t chars = mbstowcs(NULL, e->text, 0); if (chars != (size_t)-1) { wchar_t wtext[chars + 1]; mbstowcs(wtext, e->text, chars + 1); - e->glyphs = malloc(chars * sizeof(e->glyphs[0])); e->kern_x = calloc(chars, sizeof(e->kern_x[0])); - /* Convert text to glyph masks/images. */ - for (size_t i = 0; i < chars; i++) { - const struct fcft_glyph *glyph = fcft_glyph_rasterize( - font, wtext[i], FCFT_SUBPIXEL_NONE); + if (fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING) { + struct fcft_text_run *run = fcft_text_run_rasterize(font, chars, wtext, FCFT_SUBPIXEL_NONE); + if (run != NULL) { + int w = 0; + for (size_t i = 0; i < run->count; i++) + w += run->glyphs[i]->advance.x; - if (glyph == NULL) - continue; + fcft_text_run_destroy(p->cached.run); + p->cached.hash = hash; + p->cached.run = run; + p->cached.width = w; - e->glyphs[e->num_glyphs++] = glyph; + e->num_glyphs = run->count; + e->glyphs = run->glyphs; + } + } - if (i == 0) - continue; + if (e->glyphs == NULL) { + e->allocated_glyphs = malloc(chars * sizeof(e->glyphs[0])); - fcft_kerning(font, wtext[i - 1], wtext[i], &e->kern_x[i], NULL); + /* Convert text to glyph masks/images. */ + for (size_t i = 0; i < chars; i++) { + const struct fcft_glyph *glyph = fcft_glyph_rasterize( + font, wtext[i], FCFT_SUBPIXEL_NONE); + + if (glyph == NULL) + continue; + + e->allocated_glyphs[e->num_glyphs++] = glyph; + + if (i == 0) + continue; + + fcft_kerning(font, wtext[i - 1], wtext[i], &e->kern_x[i], NULL); + } + + e->glyphs = e->allocated_glyphs; } } @@ -188,6 +247,7 @@ static void particle_destroy(struct particle *particle) { struct private *p = particle->private; + fcft_text_run_destroy(p->cached.run); free(p->text); free(p); particle_default_destroy(particle); @@ -199,6 +259,7 @@ string_new(struct particle *common, const char *text, size_t max_len) struct private *p = calloc(1, sizeof(*p)); p->text = strdup(text); p->max_len = max_len; + p->cached.hash = -1; common->private = p; common->destroy = &particle_destroy;