diff options
author | Phil Jones <philj56@gmail.com> | 2022-06-08 23:14:38 +0100 |
---|---|---|
committer | Phil Jones <philj56@gmail.com> | 2022-06-09 16:51:44 +0100 |
commit | 05861ed6737f6b7b139895c4eeb26791edc333b4 (patch) | |
tree | 2f6af9e3038ec900f3968a1f2ee687f7a7238414 /src/entry_backend | |
parent | 51bbf779ba2c9d5954e2c9470a8eae7c1ddd38a5 (diff) |
Add Harfbuzz backend.
Another source of slow startup is initialising Pango. If the user
supplies a ttf file, we can skip any Pango stuff and use Harfbuzz
directly with Cairo to do our font rendering, providing a large speedup.
Diffstat (limited to 'src/entry_backend')
-rw-r--r-- | src/entry_backend/harfbuzz.c | 148 | ||||
-rw-r--r-- | src/entry_backend/harfbuzz.h | 26 | ||||
-rw-r--r-- | src/entry_backend/pango.c | 92 | ||||
-rw-r--r-- | src/entry_backend/pango.h | 19 |
4 files changed, 285 insertions, 0 deletions
diff --git a/src/entry_backend/harfbuzz.c b/src/entry_backend/harfbuzz.c new file mode 100644 index 0000000..d1f54e9 --- /dev/null +++ b/src/entry_backend/harfbuzz.c @@ -0,0 +1,148 @@ +#include <assert.h> +#include <cairo/cairo.h> +#include <glib.h> +#include <harfbuzz/hb-ft.h> +#include <harfbuzz/hb-glib.h> +#include <math.h> +#include <pango/pangocairo.h> +#include <pango/pango.h> +#include <wchar.h> +#include "../entry.h" +#include "../log.h" +#include "../nelem.h" +#include "../xmalloc.h" + +static void setup_hb_buffer(hb_buffer_t *buffer) +{ + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + hb_buffer_set_script(buffer, HB_SCRIPT_LATIN); + hb_buffer_set_language(buffer, hb_language_from_string("en", -1)); +} + + +static hb_buffer_t *create_hb_buffer(void) +{ + hb_buffer_t *buffer = hb_buffer_create(); + hb_buffer_set_unicode_funcs(buffer, hb_glib_get_unicode_funcs()); + setup_hb_buffer(buffer); + + return buffer; +} + +static uint32_t render_hb_buffer(cairo_t *cr, hb_buffer_t *buffer, uint32_t scale) +{ + cairo_save(cr); + + cairo_font_extents_t font_extents; + cairo_font_extents(cr, &font_extents); + cairo_matrix_t font_matrix; + cairo_get_font_matrix(cr, &font_matrix); + double baseline = (font_matrix.xx - font_extents.height) / 2 + font_extents.ascent; + cairo_translate(cr, 0, baseline); + + unsigned int glyph_count; + hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count); + hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); + cairo_glyph_t *cairo_glyphs = xmalloc(sizeof(cairo_glyph_t) * glyph_count); + + unsigned int width = 0; + for (unsigned int i=0; i < glyph_count; ++i) { + width += ceil(glyph_pos[i].x_advance / 64.0 / scale); + } + + int x = 0; + int y = 0; + for (unsigned int i=0; i < glyph_count; ++i) { + cairo_glyphs[i].index = glyph_info[i].codepoint; + cairo_glyphs[i].x = x + ceil(glyph_pos[i].x_offset / 64.0 / scale); + cairo_glyphs[i].y = y - ceil(glyph_pos[i].y_offset / 64.0 / scale); + x += ceil(glyph_pos[i].x_advance / 64.0 / scale); + y -= ceil(glyph_pos[i].y_advance / 64.0 / scale); + } + + cairo_show_glyphs(cr, cairo_glyphs, glyph_count); + + free(cairo_glyphs); + + cairo_restore(cr); + + return width; +} + +void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale) +{ + cairo_t *cr = entry->cairo.cr; + + /* Setup FreeType. */ + log_debug("Creating FreeType library.\n"); + assert(!FT_Init_FreeType(&entry->backend.ft_library)); + + log_debug("Loading FreeType font.\n"); + assert(!FT_New_Face(entry->backend.ft_library, "font.ttf", 0, &entry->backend.ft_face)); + assert(!FT_Set_Char_Size(entry->backend.ft_face, entry->font_size * 64, entry->font_size * 64, 0, 0)); + + log_debug("Creating Cairo font.\n"); + entry->backend.cairo_face = cairo_ft_font_face_create_for_ft_face(entry->backend.ft_face, 0); + + struct color color = entry->foreground_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + cairo_set_font_face(cr, entry->backend.cairo_face); + cairo_set_font_size(cr, entry->font_size * (96.0 / 72.0)); + + cairo_font_extents_t font_extents; + cairo_font_extents(cr, &font_extents); + + log_debug("Creating Harfbuzz font.\n"); + entry->backend.hb_font = hb_ft_font_create_referenced(entry->backend.ft_face); + + log_debug("Creating Harfbuzz buffer.\n"); + entry->backend.hb_buffer = create_hb_buffer(); + + /* Draw the prompt now, as this only needs to be done once */ + log_debug("Drawing prompt.\n"); + hb_buffer_add_utf8(entry->backend.hb_buffer, "run: ", -1, 0, -1); + hb_shape(entry->backend.hb_font, entry->backend.hb_buffer, NULL, 0); + + /* Move and clip so we don't draw over the prompt */ + uint32_t prompt_width = render_hb_buffer(cr, entry->backend.hb_buffer, scale); + cairo_translate(cr, prompt_width, 0); + width -= prompt_width; + cairo_rectangle(cr, 0, 0, width, height); + cairo_clip(cr); +} + +void entry_backend_destroy(struct entry *entry) +{ + hb_buffer_destroy(entry->backend.hb_buffer); + hb_font_destroy(entry->backend.hb_font); + FT_Done_Face(entry->backend.ft_face); + FT_Done_FreeType(entry->backend.ft_library); +} + +void entry_backend_update(struct entry *entry) +{ + cairo_t *cr = entry->cairo.cr; + + hb_buffer_clear_contents(entry->backend.hb_buffer); + setup_hb_buffer(entry->backend.hb_buffer); + hb_buffer_add_utf8(entry->backend.hb_buffer, entry->input_mb, -1, 0, -1); + hb_shape(entry->backend.hb_font, entry->backend.hb_buffer, NULL, 0); + render_hb_buffer(cr, entry->backend.hb_buffer, entry->image.scale); + + cairo_font_extents_t font_extents; + cairo_font_extents(cr, &font_extents); + + for (size_t i = 0; i < 5; i++) { + cairo_translate(cr, 0, font_extents.height); + + hb_buffer_t *buffer = entry->backend.hb_buffer; + + hb_buffer_clear_contents(buffer); + setup_hb_buffer(buffer); + if (i < entry->results.count) { + hb_buffer_add_utf8(buffer, entry->results.buf[i], -1, 0, -1); + hb_shape(entry->backend.hb_font, buffer, NULL, 0); + render_hb_buffer(cr, buffer, entry->image.scale); + } + } +} diff --git a/src/entry_backend/harfbuzz.h b/src/entry_backend/harfbuzz.h new file mode 100644 index 0000000..0b07538 --- /dev/null +++ b/src/entry_backend/harfbuzz.h @@ -0,0 +1,26 @@ +#ifndef ENTRY_BACKEND_HARFBUZZ_H +#define ENTRY_BACKEND_HARFBUZZ_H + +#include <cairo/cairo-ft.h> +#include <ft2build.h> +#include FT_FREETYPE_H +#include <harfbuzz/hb.h> +#include <pango/pangocairo.h> + +struct entry; + +struct entry_backend { + FT_Library ft_library; + FT_Face ft_face; + + cairo_font_face_t *cairo_face; + + hb_font_t *hb_font; + hb_buffer_t *hb_buffer; +}; + +void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale); +void entry_backend_destroy(struct entry *entry); +void entry_backend_update(struct entry *entry); + +#endif /* ENTRY_BACKEND_HARFBUZZ_H */ diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c new file mode 100644 index 0000000..569a706 --- /dev/null +++ b/src/entry_backend/pango.c @@ -0,0 +1,92 @@ +#include <cairo/cairo.h> +#include <glib.h> +#include <pango/pangocairo.h> +#include <pango/pango.h> +#include <wchar.h> +#include "../entry.h" +#include "../log.h" +#include "../nelem.h" + +void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale) +{ + cairo_t *cr = entry->cairo.cr; + + /* Setup Pango. */ + log_debug("Creating Pango context.\n"); + PangoContext *context = pango_cairo_create_context(cr); + + log_debug("Creating Pango font description.\n"); + PangoFontDescription *font_description = + pango_font_description_from_string(entry->font_name); + pango_font_description_set_size( + font_description, + entry->font_size * PANGO_SCALE); + pango_context_set_font_description(context, font_description); + pango_font_description_free(font_description); + + entry->backend.prompt_layout = pango_layout_new(context); + log_debug("Setting Pango text.\n"); + pango_layout_set_text(entry->backend.prompt_layout, "run: ", -1); + int prompt_width; + int prompt_height; + log_debug("Get Pango pixel size.\n"); + pango_layout_get_pixel_size(entry->backend.prompt_layout, &prompt_width, &prompt_height); + log_debug("First Pango draw.\n"); + pango_cairo_update_layout(cr, entry->backend.prompt_layout); + + /* Draw the prompt now, as this only needs to be done once */ + struct color color = entry->foreground_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + pango_cairo_show_layout(cr, entry->backend.prompt_layout); + + /* Move and clip so we don't draw over the prompt */ + cairo_translate(cr, prompt_width, 0); + width -= prompt_width; + cairo_rectangle(cr, 0, 0, width, height); + cairo_clip(cr); + + log_debug("Creating Pango layout.\n"); + entry->backend.entry_layout = pango_layout_new(context); + pango_layout_set_text(entry->backend.entry_layout, "", -1); + + for (size_t i = 0; i < 5; i++) { + PangoLayout *layout = pango_layout_new(context); + pango_layout_set_text(layout, "", -1); + entry->backend.result_layouts[i] = layout; + } + + entry->backend.context = context; +} + +void entry_backend_destroy(struct entry *entry) +{ + for (size_t i = 0; i < 5; i++) { + g_object_unref(entry->backend.result_layouts[i]); + } + g_object_unref(entry->backend.entry_layout); + g_object_unref(entry->backend.prompt_layout); + g_object_unref(entry->backend.context); +} + +void entry_backend_update(struct entry *entry) +{ + cairo_t *cr = entry->cairo.cr; + + pango_layout_set_text(entry->backend.entry_layout, entry->input_mb, -1); + pango_cairo_update_layout(cr, entry->backend.entry_layout); + pango_cairo_show_layout(cr, entry->backend.entry_layout); + + for (size_t i = 0; i < 5; i++) { + cairo_translate(cr, 0, 50); + PangoLayout *layout = entry->backend.result_layouts[i]; + const char *str; + if (i < entry->results.count) { + str = entry->results.buf[i]; + } else { + str = ""; + } + pango_layout_set_text(layout, str, -1); + pango_cairo_update_layout(cr, layout); + pango_cairo_show_layout(cr, layout); + } +} diff --git a/src/entry_backend/pango.h b/src/entry_backend/pango.h new file mode 100644 index 0000000..08572ae --- /dev/null +++ b/src/entry_backend/pango.h @@ -0,0 +1,19 @@ +#ifndef ENTRY_BACKEND_PANGO_H +#define ENTRY_BACKEND_PANGO_H + +#include <pango/pangocairo.h> + +struct entry; + +struct entry_backend { + PangoContext *context; + PangoLayout *prompt_layout; + PangoLayout *entry_layout; + PangoLayout *result_layouts[5]; +}; + +void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale); +void entry_backend_destroy(struct entry *entry); +void entry_backend_update(struct entry *entry); + +#endif /* ENTRY_BACKEND_PANGO_H */ |