particle/string: use fcft_text_run_rasterize() when available

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.
This commit is contained in:
Daniel Eklöf 2021-05-24 17:38:43 +02:00
parent 18a0920ed9
commit a5bbf0b769
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
2 changed files with 75 additions and 14 deletions

View file

@ -65,7 +65,7 @@ backend_wayland = wayland_client.found() and wayland_cursor.found()
# "My" dependencies, fallback to subproject # "My" dependencies, fallback to subproject
tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist') 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( add_project_arguments(
['-D_GNU_SOURCE'] + ['-D_GNU_SOURCE'] +

View file

@ -13,6 +13,12 @@
struct private { struct private {
char *text; char *text;
size_t max_len; size_t max_len;
struct {
uint64_t hash;
struct fcft_text_run *run;
int width;
} cached;
}; };
struct eprivate { struct eprivate {
@ -20,17 +26,31 @@ struct eprivate {
char *text; char *text;
const struct fcft_glyph **glyphs; const struct fcft_glyph **glyphs;
const struct fcft_glyph **allocated_glyphs;
long *kern_x; long *kern_x;
int num_glyphs; 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 static void
exposable_destroy(struct exposable *exposable) exposable_destroy(struct exposable *exposable)
{ {
struct eprivate *e = exposable->private; struct eprivate *e = exposable->private;
free(e->text); free(e->text);
free(e->glyphs); free(e->allocated_glyphs);
free(e->kern_x); free(e->kern_x);
free(e); free(e);
exposable_default_destroy(exposable); exposable_default_destroy(exposable);
@ -40,19 +60,55 @@ static int
begin_expose(struct exposable *exposable) begin_expose(struct exposable *exposable)
{ {
struct eprivate *e = exposable->private; struct eprivate *e = exposable->private;
struct private *p = exposable->particle->private;
struct fcft_font *font = exposable->particle->font; struct fcft_font *font = exposable->particle->font;
e->glyphs = NULL; e->glyphs = e->allocated_glyphs = NULL;
e->num_glyphs = 0; 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); size_t chars = mbstowcs(NULL, e->text, 0);
if (chars != (size_t)-1) { if (chars != (size_t)-1) {
wchar_t wtext[chars + 1]; wchar_t wtext[chars + 1];
mbstowcs(wtext, e->text, 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])); e->kern_x = calloc(chars, sizeof(e->kern_x[0]));
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;
fcft_text_run_destroy(p->cached.run);
p->cached.hash = hash;
p->cached.run = run;
p->cached.width = w;
e->num_glyphs = run->count;
e->glyphs = run->glyphs;
}
}
if (e->glyphs == NULL) {
e->allocated_glyphs = malloc(chars * sizeof(e->glyphs[0]));
/* Convert text to glyph masks/images. */ /* Convert text to glyph masks/images. */
for (size_t i = 0; i < chars; i++) { for (size_t i = 0; i < chars; i++) {
const struct fcft_glyph *glyph = fcft_glyph_rasterize( const struct fcft_glyph *glyph = fcft_glyph_rasterize(
@ -61,13 +117,16 @@ begin_expose(struct exposable *exposable)
if (glyph == NULL) if (glyph == NULL)
continue; continue;
e->glyphs[e->num_glyphs++] = glyph; e->allocated_glyphs[e->num_glyphs++] = glyph;
if (i == 0) if (i == 0)
continue; continue;
fcft_kerning(font, wtext[i - 1], wtext[i], &e->kern_x[i], NULL); fcft_kerning(font, wtext[i - 1], wtext[i], &e->kern_x[i], NULL);
} }
e->glyphs = e->allocated_glyphs;
}
} }
exposable->width = exposable->particle->left_margin + exposable->width = exposable->particle->left_margin +
@ -188,6 +247,7 @@ static void
particle_destroy(struct particle *particle) particle_destroy(struct particle *particle)
{ {
struct private *p = particle->private; struct private *p = particle->private;
fcft_text_run_destroy(p->cached.run);
free(p->text); free(p->text);
free(p); free(p);
particle_default_destroy(particle); 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)); struct private *p = calloc(1, sizeof(*p));
p->text = strdup(text); p->text = strdup(text);
p->max_len = max_len; p->max_len = max_len;
p->cached.hash = -1;
common->private = p; common->private = p;
common->destroy = &particle_destroy; common->destroy = &particle_destroy;