diff options
-rw-r--r-- | meson.build | 18 | ||||
-rw-r--r-- | src/entry.c | 79 | ||||
-rw-r--r-- | src/entry.h | 15 | ||||
-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 | ||||
-rw-r--r-- | src/image.h | 1 | ||||
-rw-r--r-- | src/main.c | 13 |
9 files changed, 327 insertions, 84 deletions
diff --git a/meson.build b/meson.build index 06ce596..4b4b100 100644 --- a/meson.build +++ b/meson.build @@ -48,7 +48,7 @@ add_project_arguments( language: 'c' ) -sources = files( +common_sources = files( 'src/main.c', 'src/color.c', 'src/compgen.c', @@ -62,8 +62,14 @@ sources = files( 'src/xmalloc.c', ) +pango_sources = files('src/entry_backend/pango.c') +harfbuzz_sources = files('src/entry_backend/harfbuzz.c') + cc = meson.get_compiler('c') +libm = cc.find_library('m', required: false) +freetype = dependency('freetype2') glib = dependency('glib-2.0') +harfbuzz = dependency('harfbuzz') pangocairo = dependency('pangocairo') wayland_client = dependency('wayland-client') wayland_protocols = dependency('wayland-protocols', native: true) @@ -101,8 +107,16 @@ endforeach executable( 'tofi', - sources, wl_proto_src, wl_proto_headers, + common_sources, pango_sources, wl_proto_src, wl_proto_headers, dependencies: [glib, pangocairo, wayland_client, xkbcommon], + c_args: '-DUSE_PANGO', + install: true +) + +executable( + 'tofi-hb', + common_sources, harfbuzz_sources, wl_proto_src, wl_proto_headers, + dependencies: [libm, glib, freetype, harfbuzz, pangocairo, wayland_client, xkbcommon], install: true ) diff --git a/src/entry.c b/src/entry.c index 845df0a..eb58695 100644 --- a/src/entry.c +++ b/src/entry.c @@ -1,7 +1,5 @@ #include <cairo/cairo.h> #include <glib.h> -#include <pango/pangocairo.h> -#include <pango/pango.h> #include <wchar.h> #include "entry.h" #include "log.h" @@ -11,6 +9,7 @@ void entry_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t s { entry->image.width = width; entry->image.height = height; + entry->image.scale = scale; width /= scale; height /= scale; @@ -79,67 +78,19 @@ void entry_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t s height -= 2 * entry->padding; cairo_rectangle(cr, 0, 0, width, height); cairo_clip(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->pango.prompt_layout = pango_layout_new(context); - log_debug("Setting Pango text.\n"); - pango_layout_set_text(entry->pango.prompt_layout, "run: ", -1); - int prompt_width; - int prompt_height; - log_debug("Get Pango pixel size.\n"); - pango_layout_get_pixel_size(entry->pango.prompt_layout, &prompt_width, &prompt_height); - log_debug("First Pango draw.\n"); - pango_cairo_update_layout(cr, entry->pango.prompt_layout); - - /* Draw the prompt now, as this only needs to be done once */ - color = entry->foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - pango_cairo_show_layout(cr, entry->pango.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->pango.entry_layout = pango_layout_new(context); - pango_layout_set_text(entry->pango.entry_layout, "", -1); - - for (size_t i = 0; i < 5; i++) { - PangoLayout *layout = pango_layout_new(context); - pango_layout_set_text(layout, "", -1); - entry->pango.result_layouts[i] = layout; - } - - entry->pango.context = context; entry->cairo.surface = surface; entry->cairo.cr = cr; + /* Setup the backend. */ + entry_backend_init(entry, width, height, scale); + entry->image.buffer = cairo_image_surface_get_data(surface); } void entry_destroy(struct entry *entry) { - for (size_t i = 0; i < 5; i++) { - g_object_unref(entry->pango.result_layouts[i]); - } - g_object_unref(entry->pango.entry_layout); - g_object_unref(entry->pango.prompt_layout); - g_object_unref(entry->pango.context); + entry_backend_destroy(entry); cairo_destroy(entry->cairo.cr); cairo_surface_destroy(entry->cairo.surface); } @@ -160,27 +111,13 @@ void entry_update(struct entry *entry) color = entry->foreground_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - pango_layout_set_text(entry->pango.entry_layout, entry->input_mb, -1); - pango_cairo_update_layout(cr, entry->pango.entry_layout); - pango_cairo_show_layout(cr, entry->pango.entry_layout); - - for (size_t i = 0; i < 5; i++) { - cairo_translate(cr, 0, 50); - PangoLayout *layout = entry->pango.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); - } + entry_backend_update(entry); + cairo_restore(cr); } void entry_set_scale(struct entry *entry, uint32_t scale) { + entry->image.scale = scale; cairo_surface_set_device_scale(entry->cairo.surface, scale, scale); } diff --git a/src/entry.h b/src/entry.h index 6efe773..341e340 100644 --- a/src/entry.h +++ b/src/entry.h @@ -1,7 +1,13 @@ #ifndef ENTRY_H #define ENTRY_H -#include <pango/pangocairo.h> +#ifdef USE_PANGO +#include "entry_backend/pango.h" +#else +#include "entry_backend/harfbuzz.h" +#endif + +#include <cairo/cairo.h> #include "color.h" #include "history.h" #include "image.h" @@ -12,12 +18,7 @@ struct entry { struct image image; - struct { - PangoContext *context; - PangoLayout *prompt_layout; - PangoLayout *entry_layout; - PangoLayout *result_layouts[5]; - } pango; + struct entry_backend backend; struct { cairo_surface_t *surface; cairo_t *cr; 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 */ diff --git a/src/image.h b/src/image.h index 2afaa4d..a366e59 100644 --- a/src/image.h +++ b/src/image.h @@ -8,6 +8,7 @@ struct image { uint8_t *buffer; uint32_t width; uint32_t height; + uint32_t scale; bool redraw; struct { uint32_t x; @@ -443,7 +443,7 @@ static void registry_global( uint32_t version) { struct tofi *tofi = data; - log_debug("Registry %u: %s v%u.\n", name, interface, version); + //log_debug("Registry %u: %s v%u.\n", name, interface, version); if (!strcmp(interface, wl_compositor_interface.name)) { tofi->wl_compositor = wl_registry_bind( wl_registry, @@ -786,7 +786,7 @@ int main(int argc, char *argv[]) log_debug("Third roundtrip done.\n"); /* - * Initialise the Pango & Cairo structures for rendering the entry. + * Initialise the structures for rendering the entry. * Cairo needs to know the size of the surface it's creating, and * there's no way to resize it aside from tearing everything down and * starting again, so we make sure to do this after we've determined @@ -794,14 +794,14 @@ int main(int argc, char *argv[]) * scale factor after startup, but this is just a launcher, which * shouldn't be moving between outputs while running. */ - log_debug("Initialising Pango / Cairo.\n"); + log_debug("Initialising renderer.\n"); entry_init( &tofi.window.entry, tofi.window.surface.width, tofi.window.surface.height, tofi.window.scale); entry_update(&tofi.window.entry); - log_debug("Pango / Cairo initialised.\n"); + log_debug("Renderer initialised.\n"); /* * Create the various structures for each surface, and @@ -855,6 +855,7 @@ int main(int argc, char *argv[]) */ surface_destroy(&tofi.window.surface); entry_destroy(&tofi.window.entry); + zwlr_layer_surface_v1_destroy(tofi.window.zwlr_layer_surface); wl_surface_destroy(tofi.window.surface.wl_surface); if (tofi.wl_keyboard != NULL) { wl_keyboard_release(tofi.wl_keyboard); @@ -865,16 +866,20 @@ int main(int argc, char *argv[]) wl_compositor_destroy(tofi.wl_compositor); wl_seat_release(tofi.wl_seat); wl_output_release(tofi.wl_output); + wl_shm_destroy(tofi.wl_shm); + zwlr_layer_shell_v1_destroy(tofi.zwlr_layer_shell); xkb_state_unref(tofi.xkb_state); xkb_keymap_unref(tofi.xkb_keymap); xkb_context_unref(tofi.xkb_context); wl_registry_destroy(tofi.wl_registry); string_vec_destroy(&tofi.window.entry.commands); + string_vec_destroy(&tofi.window.entry.results); history_destroy(&tofi.window.entry.history); #endif /* * For release builds, skip straight to display disconnection and quit. */ + wl_display_roundtrip(tofi.wl_display); wl_display_disconnect(tofi.wl_display); log_debug("Finished, exiting.\n"); |