From 858a0d9906a66f2eb9c2ffaf6d71e1153dad33f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 31 Jul 2019 18:03:35 +0200 Subject: [PATCH] font: initial support for double-width *and* color emoji glyphs Fonts are now loaded with FT_LOAD_COLOR and we recognize and support the FT_PIXEL_MODE_BGRA pixel mode. This is mapped to a CAIRO_FORMAT_ARGB32 surface, that is blitted as-is (instead of used as a mask like we do for gray and mono glyphs). Furthermore, since many emojis are double-width, we add initial support for double-width glyphs. These are assumed to always be utf8. When PRINT:ing an utf8 character, we check its width, and add empty "spacer" cells after the cell with the multi-column glyph. When rendering, we render the columns in each row backwards. This ensures the spacer cells get cleared *before* we render the glyph (so that we don't end up erasing part of the glyph). Finally, emoji fonts are usually bitmap fonts with *large* glyphs. These aren't automatically scaled down. I.e. even if we request a glyph of 13 pixels, we might end up getting a 100px glyph. To handle this, fontconfig must be configured to scale bitmap fonts. When it is, we can look at the 'scalable' and 'pixelsizefixup' properties, and use these to scale the rendered glyph. --- font.c | 85 +++++++++++++++++++++++++++++++++----------------------- font.h | 13 ++++----- main.c | 8 +++--- render.c | 30 ++++++++++++++++---- vt.c | 34 +++++++++++++++++------ 5 files changed, 110 insertions(+), 60 deletions(-) diff --git a/font.c b/font.c index 6842a439..9c4a9b50 100644 --- a/font.c +++ b/font.c @@ -106,6 +106,14 @@ from_name(const char *base_name, const font_list_t *fallbacks, const char *attri return false; } + FcBool scalable; + if (FcPatternGetBool(final_pattern, FC_SCALABLE, 0, &scalable) != FcResultMatch) + scalable = FcTrue; + + double pixel_fixup; + if (FcPatternGetDouble(final_pattern, "pixelsizefixupfactor", 0, &pixel_fixup) != FcResultMatch) + pixel_fixup = 1.; + LOG_DBG("loading: %s", face_file); mtx_lock(&ft_lock); @@ -199,9 +207,10 @@ from_name(const char *base_name, const font_list_t *fallbacks, const char *attri mtx_init(&font->lock, mtx_plain); font->face = ft_face; - font->load_flags = load_flags; + font->load_flags = load_flags | FT_LOAD_COLOR; font->render_flags = render_flags; font->is_fallback = is_fallback; + font->pixel_size_fixup = scalable ? pixel_fixup : 1.; if (fallbacks != NULL) { tll_foreach(*fallbacks, it) { @@ -222,7 +231,6 @@ from_name(const char *base_name, const font_list_t *fallbacks, const char *attri if (is_fallback) return true; - //font_populate_glyph_cache(font); font->cache = calloc(cache_size, sizeof(font->cache[0])); return true; } @@ -272,15 +280,16 @@ glyph_for_wchar(struct font *font, wchar_t wc, struct glyph *glyph) FT_UInt idx = FT_Get_Char_Index(font->face, wc); if (idx == 0) { - /* LOG_DBG("no glyph found for %02x %02x %02x %02x", */ - /* (unsigned char)utf8[0], (unsigned char)utf8[1], */ - /* (unsigned char)utf8[2], (unsigned char)utf8[3]); */ + LOG_DBG("no glyph found for %02x %02x %02x %02x", + (unsigned char)utf8[0], (unsigned char)utf8[1], + (unsigned char)utf8[2], (unsigned char)utf8[3]); /* Try fallback fonts */ tll_foreach(font->fallbacks, it) { struct font fallback; if (from_name(it->item, NULL, "", &fallback, true)) { if (glyph_for_wchar(&fallback, wc, glyph)) { + LOG_DBG("used fallback %s (fixup = %f)", it->item, fallback.pixel_size_fixup); font_destroy(&fallback); return true; } @@ -306,11 +315,15 @@ glyph_for_wchar(struct font *font, wchar_t wc, struct glyph *glyph) assert(font->face->glyph->format == FT_GLYPH_FORMAT_BITMAP); FT_Bitmap *bitmap = &font->face->glyph->bitmap; - assert(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY || - bitmap->pixel_mode == FT_PIXEL_MODE_MONO); + assert(bitmap->pixel_mode == FT_PIXEL_MODE_MONO || + bitmap->pixel_mode == FT_PIXEL_MODE_GRAY || + bitmap->pixel_mode == FT_PIXEL_MODE_BGRA); - cairo_format_t cr_format = bitmap->pixel_mode == FT_PIXEL_MODE_GRAY - ? CAIRO_FORMAT_A8 : CAIRO_FORMAT_A1; + /* Map FT pixel format to cairo surface format */ + cairo_format_t cr_format = + bitmap->pixel_mode == FT_PIXEL_MODE_MONO ? CAIRO_FORMAT_A1 : + bitmap->pixel_mode == FT_PIXEL_MODE_GRAY ? CAIRO_FORMAT_A8 : + bitmap->pixel_mode == FT_PIXEL_MODE_BGRA ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_INVALID; int stride = cairo_format_stride_for_width(cr_format, bitmap->width); assert(stride >= bitmap->pitch); @@ -318,6 +331,7 @@ glyph_for_wchar(struct font *font, wchar_t wc, struct glyph *glyph) uint8_t *data = malloc(bitmap->rows * stride); assert(bitmap->pitch >= 0); + /* Convert FT bitmap to cairo surface (well, the backing image) */ switch (bitmap->pixel_mode) { case FT_PIXEL_MODE_MONO: for (size_t r = 0; r < bitmap->rows; r++) { @@ -339,6 +353,11 @@ glyph_for_wchar(struct font *font, wchar_t wc, struct glyph *glyph) } break; + case FT_PIXEL_MODE_BGRA: + assert(stride == bitmap->pitch); + memcpy(data, bitmap->buffer, bitmap->rows * bitmap->pitch); + break; + default: LOG_ERR("unimplemented FreeType bitmap pixel mode: %d", bitmap->pixel_mode); @@ -357,10 +376,11 @@ glyph_for_wchar(struct font *font, wchar_t wc, struct glyph *glyph) *glyph = (struct glyph){ .wc = wc, - .data = data, + .width = wcwidth(wc), .surf = surf, .left = font->face->glyph->bitmap_left, .top = font->face->glyph->bitmap_top, + .pixel_size_fixup = font->pixel_size_fixup, }; return true; @@ -428,30 +448,25 @@ font_destroy(struct font *font) mtx_unlock(&ft_lock); } - if (font->cache != NULL) { - for (size_t i = 0; i < cache_size; i++) { - if (font->cache[i] == NULL) - continue; - - tll_foreach(*font->cache[i], it) { - cairo_surface_destroy(it->item.surf); - free(it->item.data); - } - - tll_free(*font->cache[i]); - free(font->cache[i]); - } - free(font->cache); - } - -#if 0 - for (size_t i = 0; i < 256; i++) { - if (font->cache[i].surf != NULL) - cairo_surface_destroy(font->cache[i].surf); - if (font->cache[i].data != NULL) - free(font->cache[i].data); - } -#endif - mtx_destroy(&font->lock); + + if (font->cache == NULL) + return; + + for (size_t i = 0; i < cache_size; i++) { + if (font->cache[i] == NULL) + continue; + + tll_foreach(*font->cache[i], it) { + cairo_surface_flush(it->item.surf); + void *image = cairo_image_surface_get_data(it->item.surf); + + cairo_surface_destroy(it->item.surf); + free(image); + } + + tll_free(*font->cache[i]); + free(font->cache[i]); + } + free(font->cache); } diff --git a/font.h b/font.h index 0f2c9833..9746eaac 100644 --- a/font.h +++ b/font.h @@ -15,18 +15,13 @@ typedef tll(const char *) font_list_t; struct glyph { wchar_t wc; + int width; - void *data; cairo_surface_t *surf; int left; int top; -#if 0 - int format; - int width; - int height; - int stride; -#endif + double pixel_size_fixup; }; typedef tll(struct glyph) hash_entry_t; @@ -36,10 +31,13 @@ struct font { int load_flags; int render_flags; FT_LcdFilter lcd_filter; + double pixel_size_fixup; /* Scale factor - should only be used with ARGB32 glyphs */ + struct { double position; double thickness; } underline; + struct { double position; double thickness; @@ -48,7 +46,6 @@ struct font { bool is_fallback; tll(char *) fallbacks; - //struct glyph cache[256]; hash_entry_t **cache; mtx_t lock; }; diff --git a/main.c b/main.c index d8b94c12..55639c65 100644 --- a/main.c +++ b/main.c @@ -493,10 +493,10 @@ main(int argc, char *const *argv) int descent = ft_face->size->metrics.descender / 64; int ascent = ft_face->size->metrics.ascender / 64; - term.fextents.height = height; - term.fextents.descent = -descent; - term.fextents.ascent = ascent; - term.fextents.max_x_advance = max_x_advance; + 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); diff --git a/render.c b/render.c index 0a04a59b..bd96149b 100644 --- a/render.c +++ b/render.c @@ -139,6 +139,7 @@ static void render_cell(struct terminal *term, cairo_t *cr, struct cell *cell, int col, int row, bool has_cursor) { + if (cell->attrs.clean) return; @@ -215,12 +216,31 @@ render_cell(struct terminal *term, cairo_t *cr, struct font *font = attrs_to_font(term, &cell->attrs); const struct glyph *glyph = font_glyph_for_utf8(font, cell->c); - if (glyph != NULL) { - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + if (glyph == NULL) + return; + + cairo_save(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + double fixup = glyph->pixel_size_fixup; + cairo_translate( + cr, + x + glyph->left / fixup, + y + term->fextents.ascent - glyph->top * fixup); + cairo_scale(cr, fixup, fixup); + + if (cairo_image_surface_get_format(glyph->surf) == CAIRO_FORMAT_ARGB32) { + /* Glyph surface is a pre-rendered image (typically a color emoji...) */ + cairo_set_source_surface(cr, glyph->surf, 0, 0); + cairo_paint(cr); + } else { + /* Glyph surface is an alpha mask */ + //assert(glyph->pixel_size_fixup == 1.); cairo_set_source_rgb(cr, fg.r, fg.g, fg.b); - cairo_mask_surface( - cr, glyph->surf, x + glyph->left, y + term->fextents.ascent - glyph->top); + cairo_mask_surface(cr, glyph->surf, 0, 0); + //cr, glyph->surf, x + glyph->left, y + term->fextents.ascent - glyph->top); } + cairo_restore(cr); } static void @@ -284,7 +304,7 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, static void render_row(struct terminal *term, cairo_t *cr, struct row *row, int row_no) { - for (int col = 0; col < term->cols; col++) + for (int col = term->cols - 1; col >= 0; col--) render_cell(term, cr, &row->cells[col], col, row_no, false); } diff --git a/vt.c b/vt.c index 7fbe47b3..1d62c30a 100644 --- a/vt.c +++ b/vt.c @@ -691,6 +691,7 @@ pre_print(struct terminal *term) } } +#include static inline void post_print(struct terminal *term) { @@ -721,12 +722,9 @@ action_print_utf8(struct terminal *term) struct row *row = term->grid->cur_row; struct cell *cell = &row->cells[term->cursor.col]; -#if 0 - term_damage_update(term, term->cursor.linear, 1); -#else + row->dirty = true; cell->attrs.clean = 0; -#endif print_insert(term); @@ -736,6 +734,29 @@ action_print_utf8(struct terminal *term) term->vt.utf8.idx = 0; cell->attrs = term->vt.attrs; + + /* Hack: zero- and double-width characters */ + mbstate_t ps = {0}; + wchar_t wc; + if (mbrtowc(&wc, cell->c, 4, &ps) >= 0) { + int width = wcwidth(wc); + if (width <= 0) { + /* Skip post_print() below - i.e. don't advance cursor */ + return; + } + + /* Advance cursor the 'additional' columns (last step is done + * by post_print()) */ + for (int i = 1; i < width && term->cursor.col < term->cols - 1; i++) { + term_cursor_right(term, 1); + + assert(term->cursor.col < term->cols); + struct cell *cell = &row->cells[term->cursor.col]; + cell->c[0] = '\0'; + cell->attrs.clean = 0; + } + } + post_print(term); } @@ -746,12 +767,9 @@ action_print(struct terminal *term, uint8_t c) struct row *row = term->grid->cur_row; struct cell *cell = &row->cells[term->cursor.col]; -#if 0 - term_damage_update(term, term->cursor.linear, 1); -#else + row->dirty = true; cell->attrs.clean = 0; -#endif print_insert(term);