diff options
author | Phil Jones <philj56@gmail.com> | 2022-08-05 17:25:27 +0100 |
---|---|---|
committer | Phil Jones <philj56@gmail.com> | 2022-08-05 19:21:11 +0100 |
commit | c9f1daea03a41c0cad56453e1dcc50f6133f19c6 (patch) | |
tree | a609e8a6145f2027d78876a2a775d48c06aafabc | |
parent | 15f831bbb6f27d5a11879dd07f383400e83c97e3 (diff) |
Correct selection background for slanted fonts.
Previously the selection background didn't really wrap text correctly,
especially if the font was very slanted. This fixes that, so the width
is tightly wrapped around any font.
-rw-r--r-- | src/entry_backend/harfbuzz.c | 135 | ||||
-rw-r--r-- | src/entry_backend/pango.c | 77 |
2 files changed, 137 insertions, 75 deletions
diff --git a/src/entry_backend/harfbuzz.c b/src/entry_backend/harfbuzz.c index 946bca3..9868578 100644 --- a/src/entry_backend/harfbuzz.c +++ b/src/entry_backend/harfbuzz.c @@ -62,7 +62,7 @@ static void setup_hb_buffer(hb_buffer_t *buffer) * Render a hb_buffer with Cairo, and return the width of the rendered area in * Cairo units. */ -static uint32_t render_hb_buffer(cairo_t *cr, hb_buffer_t *buffer) +static cairo_text_extents_t render_hb_buffer(cairo_t *cr, hb_buffer_t *buffer) { cairo_save(cr); @@ -95,15 +95,34 @@ static uint32_t render_hb_buffer(cairo_t *cr, hb_buffer_t *buffer) cairo_show_glyphs(cr, cairo_glyphs, glyph_count); + cairo_text_extents_t extents; + cairo_glyph_extents(cr, cairo_glyphs, glyph_count, &extents); + + /* Account for the shifted baseline in our returned text extents. */ + extents.y_bearing += font_extents.ascent; + free(cairo_glyphs); cairo_restore(cr); - double width = 0; - for (unsigned int i=0; i < glyph_count; i++) { - width += glyph_pos[i].x_advance / 64.0; + return extents; +} + +static bool size_overflows(struct entry *entry, uint32_t width, uint32_t height) +{ + cairo_t *cr = entry->cairo[entry->index].cr; + cairo_matrix_t mat; + cairo_get_matrix(cr, &mat); + if (entry->horizontal) { + if (mat.x0 + width > entry->clip_x + entry->clip_width) { + return true; + } + } else { + if (mat.y0 + height > entry->clip_y + entry->clip_height) { + return true; + } } - return ceil(width); + return false; } void entry_backend_harfbuzz_init( @@ -188,28 +207,11 @@ void entry_backend_harfbuzz_destroy(struct entry *entry) FT_Done_FreeType(entry->harfbuzz.ft_library); } -static bool size_overflows(struct entry *entry, uint32_t width, uint32_t height) -{ - cairo_t *cr = entry->cairo[entry->index].cr; - cairo_matrix_t mat; - cairo_get_matrix(cr, &mat); - if (entry->horizontal) { - if (mat.x0 + width > entry->clip_x + entry->clip_width) { - return true; - } - } else { - if (mat.y0 + height > entry->clip_y + entry->clip_height) { - return true; - } - } - return false; -} - void entry_backend_harfbuzz_update(struct entry *entry) { cairo_t *cr = entry->cairo[entry->index].cr; hb_buffer_t *buffer = entry->harfbuzz.hb_buffer; - uint32_t width; + cairo_text_extents_t extents; cairo_save(cr); struct color color = entry->foreground_color; @@ -220,17 +222,17 @@ void entry_backend_harfbuzz_update(struct entry *entry) setup_hb_buffer(buffer); hb_buffer_add_utf8(entry->harfbuzz.hb_buffer, entry->prompt_text, -1, 0, -1); hb_shape(entry->harfbuzz.hb_font, entry->harfbuzz.hb_buffer, NULL, 0); - width = render_hb_buffer(cr, buffer); + extents = render_hb_buffer(cr, buffer); - cairo_translate(cr, width, 0); + cairo_translate(cr, extents.x_advance, 0); /* Render the entry text */ hb_buffer_clear_contents(buffer); setup_hb_buffer(buffer); hb_buffer_add_utf8(buffer, entry->input_mb, -1, 0, -1); hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); - width = render_hb_buffer(cr, buffer); - width = MAX(width, entry->input_width); + extents = render_hb_buffer(cr, buffer); + extents.x_advance = MAX(extents.x_advance, entry->input_width); cairo_font_extents_t font_extents; cairo_font_extents(cr, &font_extents); @@ -245,7 +247,7 @@ void entry_backend_harfbuzz_update(struct entry *entry) size_t i; for (i = 0; i < num_results; i++) { if (entry->horizontal) { - cairo_translate(cr, width + entry->result_spacing, 0); + cairo_translate(cr, extents.x_advance + entry->result_spacing, 0); } else { cairo_translate(cr, 0, font_extents.height + entry->result_spacing); } @@ -280,7 +282,7 @@ void entry_backend_harfbuzz_update(struct entry *entry) * We're not auto-detecting how many results we * can fit, so just render the text. */ - width = render_hb_buffer(cr, buffer); + extents = render_hb_buffer(cr, buffer); } else if (!entry->horizontal) { /* * The height of the text doesn't change, so @@ -290,7 +292,7 @@ void entry_backend_harfbuzz_update(struct entry *entry) entry->num_results_drawn = i; break; } else { - width = render_hb_buffer(cr, buffer); + extents = render_hb_buffer(cr, buffer); } } else { /* @@ -302,9 +304,9 @@ void entry_backend_harfbuzz_update(struct entry *entry) * to the main canvas only if it will fit. */ cairo_push_group(cr); - width = render_hb_buffer(cr, buffer); + extents = render_hb_buffer(cr, buffer); cairo_pattern_t *group = cairo_pop_group(cr); - if (size_overflows(entry, width, 0)) { + if (size_overflows(entry, extents.x_advance, 0)) { entry->num_results_drawn = i; cairo_pattern_destroy(group); break; @@ -339,17 +341,21 @@ void entry_backend_harfbuzz_update(struct entry *entry) * do so. */ size_t prematch_len; + size_t postmatch_len; char *prematch = xstrdup(result); char *match = NULL; char *postmatch = NULL; - uint32_t subwidth; + cairo_text_extents_t subextents; if (entry->input_mb_length > 0 && entry->selection_highlight_color.a != 0) { char *match_pos = strcasestr(prematch, entry->input_mb); if (match_pos != NULL) { match = xstrdup(result); - postmatch = xstrdup(result); prematch_len = (match_pos - prematch); prematch[prematch_len] = '\0'; + postmatch_len = strlen(result) - prematch_len - entry->input_mb_length; + if (postmatch_len > 0) { + postmatch = xstrdup(result); + } match[entry->input_mb_length + prematch_len] = '\0'; } } @@ -362,39 +368,69 @@ void entry_backend_harfbuzz_update(struct entry *entry) setup_hb_buffer(buffer); hb_buffer_add_utf8(buffer, prematch, -1, 0, -1); hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); - subwidth = render_hb_buffer(cr, buffer); - width = subwidth; + subextents = render_hb_buffer(cr, buffer); + extents = subextents; + + free(prematch); + prematch = NULL; if (match != NULL) { - cairo_translate(cr, subwidth, 0); + cairo_translate(cr, subextents.x_advance, 0); color = entry->selection_highlight_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); hb_buffer_clear_contents(buffer); setup_hb_buffer(buffer); hb_buffer_add_utf8(buffer, &match[prematch_len], -1, 0, -1); hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); - subwidth = render_hb_buffer(cr, buffer); - width += subwidth; + subextents = render_hb_buffer(cr, buffer); + + if (prematch_len == 0) { + extents = subextents; + } else { + /* + * This calculation is a little + * complex, but it's basically: + * + * (distance from leftmost pixel of + * prematch to logical end of prematch) + * + * + + * + * (distance from logical start of match + * to rightmost pixel of match). + */ + extents.width = extents.x_advance + - extents.x_bearing + + subextents.x_bearing + + subextents.width; + extents.x_advance += subextents.x_advance; + } - cairo_translate(cr, subwidth, 0); + free(match); + match = NULL; + } + + if (postmatch != NULL) { + cairo_translate(cr, subextents.x_advance, 0); color = entry->selection_foreground_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); hb_buffer_clear_contents(buffer); setup_hb_buffer(buffer); hb_buffer_add_utf8(buffer, &postmatch[entry->input_mb_length + prematch_len], -1, 0, -1); hb_shape(entry->harfbuzz.hb_font, buffer, NULL, 0); - subwidth = render_hb_buffer(cr, buffer); - width += subwidth; + subextents = render_hb_buffer(cr, buffer); + + extents.width = extents.x_advance + - extents.x_bearing + + subextents.x_bearing + + subextents.width; + extents.x_advance += subextents.x_advance; + - free(match); free(postmatch); - match = NULL; postmatch = NULL; } - free(prematch); - prematch = NULL; - cairo_pop_group_to_source(cr); cairo_save(cr); color = entry->selection_background_color; @@ -403,9 +439,8 @@ void entry_backend_harfbuzz_update(struct entry *entry) if (pad < 0) { pad = entry->clip_width; } - cairo_translate(cr, -pad, 0); - cairo_rectangle(cr, 0, 0, width + pad * 2, font_extents.height); - cairo_translate(cr, pad, 0); + cairo_translate(cr, floor(-pad + extents.x_bearing), 0); + cairo_rectangle(cr, 0, 0, ceil(extents.width + pad * 2), ceil(font_extents.height)); cairo_fill(cr); cairo_restore(cr); cairo_paint(cr); diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c index 575bba2..3173d6f 100644 --- a/src/entry_backend/pango.c +++ b/src/entry_backend/pango.c @@ -57,6 +57,12 @@ static bool size_overflows(struct entry *entry, uint32_t width, uint32_t height) return false; } +/* + * This is pretty much a direct translation of the corresponding function in + * the harfbuzz backend. As that's the one that I care about most, there are + * more explanatory comments than there are here, so go look at that if you + * want to understand how tofi's text rendering works. + */ void entry_backend_pango_update(struct entry *entry) { cairo_t *cr = entry->cairo[entry->index].cr; @@ -71,18 +77,18 @@ void entry_backend_pango_update(struct entry *entry) pango_cairo_update_layout(cr, layout); pango_cairo_show_layout(cr, layout); - int width; - int height; - pango_layout_get_pixel_size(entry->pango.layout, &width, NULL); - cairo_translate(cr, width, 0); + PangoRectangle ink_rect; + PangoRectangle logical_rect; + pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); + cairo_translate(cr, logical_rect.width + logical_rect.x, 0); /* Render the entry text */ pango_layout_set_text(layout, entry->input_mb, -1); pango_cairo_update_layout(cr, layout); pango_cairo_show_layout(cr, layout); - pango_layout_get_size(layout, &width, &height); - width = MAX(width, (int)entry->input_width * PANGO_SCALE); + pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); + logical_rect.width = MAX(logical_rect.width, (int)entry->input_width); uint32_t num_results; if (entry->num_results == 0) { @@ -94,9 +100,9 @@ void entry_backend_pango_update(struct entry *entry) size_t i; for (i = 0; i < num_results; i++) { if (entry->horizontal) { - cairo_translate(cr, (int)(width / PANGO_SCALE) + entry->result_spacing, 0); + cairo_translate(cr, logical_rect.x + logical_rect.width + entry->result_spacing, 0); } else { - cairo_translate(cr, 0, (int)(height / PANGO_SCALE) + entry->result_spacing); + cairo_translate(cr, 0, logical_rect.height + entry->result_spacing); } if (entry->num_results == 0) { if (size_overflows(entry, 0, 0)) { @@ -126,21 +132,21 @@ void entry_backend_pango_update(struct entry *entry) if (entry->num_results > 0) { pango_cairo_show_layout(cr, layout); - pango_layout_get_size(layout, &width, &height); + pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); } else if (!entry->horizontal) { - if (size_overflows(entry, 0, height / PANGO_SCALE)) { + if (size_overflows(entry, 0, logical_rect.height)) { entry->num_results_drawn = i; break; } else { pango_cairo_show_layout(cr, layout); - pango_layout_get_size(layout, &width, &height); + pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); } } else { cairo_push_group(cr); pango_cairo_show_layout(cr, layout); - pango_layout_get_size(layout, &width, &height); + pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); cairo_pattern_t *group = cairo_pop_group(cr); - if (size_overflows(entry, width / PANGO_SCALE, 0)) { + if (size_overflows(entry, logical_rect.width, 0)) { entry->num_results_drawn = i; cairo_pattern_destroy(group); break; @@ -154,12 +160,18 @@ void entry_backend_pango_update(struct entry *entry) } } else { ssize_t prematch_len = -1; + ssize_t postmatch_len = -1; size_t match_len = entry->input_mb_length; - int32_t subwidth; + PangoRectangle ink_subrect; + PangoRectangle logical_subrect; if (entry->input_mb_length > 0 && entry->selection_highlight_color.a != 0) { char *match_pos = strcasestr(str, entry->input_mb); if (match_pos != NULL) { prematch_len = (match_pos - str); + postmatch_len = strlen(str) - prematch_len - match_len; + if (postmatch_len <= 0) { + postmatch_len = -1; + } } } @@ -170,27 +182,43 @@ void entry_backend_pango_update(struct entry *entry) pango_layout_set_text(layout, str, prematch_len); pango_cairo_update_layout(cr, layout); pango_cairo_show_layout(cr, layout); - pango_layout_get_size(layout, &subwidth, &height); - width = subwidth; + pango_layout_get_pixel_extents(entry->pango.layout, &ink_subrect, &logical_subrect); + ink_rect = ink_subrect; + logical_rect = logical_subrect; if (prematch_len != -1) { - cairo_translate(cr, (int)(subwidth / PANGO_SCALE), 0); + cairo_translate(cr, logical_subrect.x + logical_subrect.width, 0); color = entry->selection_highlight_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); pango_layout_set_text(layout, &str[prematch_len], match_len); pango_cairo_update_layout(cr, layout); pango_cairo_show_layout(cr, layout); - pango_layout_get_size(layout, &subwidth, &height); - width += subwidth; + pango_layout_get_pixel_extents(entry->pango.layout, &ink_subrect, &logical_subrect); + if (prematch_len == 0) { + ink_rect = ink_subrect; + logical_rect = logical_subrect; + } else { + ink_rect.width = logical_rect.width + - ink_rect.x + + ink_subrect.x + + ink_subrect.width; + logical_rect.width += logical_subrect.x + logical_subrect.width; + } + } - cairo_translate(cr, (int)(subwidth / PANGO_SCALE), 0); + if (postmatch_len != -1) { + cairo_translate(cr, logical_subrect.x + logical_subrect.width, 0); color = entry->selection_foreground_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); pango_layout_set_text(layout, &str[prematch_len + match_len], -1); pango_cairo_update_layout(cr, layout); pango_cairo_show_layout(cr, layout); - pango_layout_get_size(layout, &subwidth, &height); - width += subwidth; + pango_layout_get_pixel_extents(entry->pango.layout, &ink_subrect, &logical_subrect); + ink_rect.width = logical_rect.width + - ink_rect.x + + ink_subrect.x + + ink_subrect.width; + logical_rect.width += logical_subrect.x + logical_subrect.width; } @@ -202,9 +230,8 @@ void entry_backend_pango_update(struct entry *entry) if (pad < 0) { pad = entry->clip_width; } - cairo_translate(cr, -pad, 0); - cairo_rectangle(cr, 0, 0, (int)(width / PANGO_SCALE) + pad * 2, (int)(height / PANGO_SCALE)); - cairo_translate(cr, pad, 0); + cairo_translate(cr, floor(-pad + ink_rect.x), 0); + cairo_rectangle(cr, 0, 0, ceil(ink_rect.width + pad * 2), ceil(logical_rect.height)); cairo_fill(cr); cairo_restore(cr); cairo_paint(cr); |