diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2ed336..f12a8b53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ * `locked-title=no|yes` to `foot.ini` (https://codeberg.org/dnkl/foot/issues/386). +* `tweak.overflowing-glyphs` option, which can be enabled to fix rendering + issues with glyphs of any width that appear cut-off + (https://codeberg.org/dnkl/foot/issues/592). ### Changed @@ -45,6 +48,12 @@ ### Deprecated ### Removed + +* The `tweak.allow-overflowing-double-width-glyphs` and + `tweak.pua-double-width` options (which have been superseded by + `tweak.overflowing-glyphs`). + + ### Fixed * Glyph offset not being taken into account when applying @@ -69,6 +78,8 @@ ### Security ### Contributors +* [clktmr](https://codeberg.org/clktmr) + ## 1.8.1 diff --git a/config.c b/config.c index 92582f2c..a9acb58e 100644 --- a/config.c +++ b/config.c @@ -2213,16 +2213,10 @@ parse_section_tweak( return false; } - else if (strcmp(key, "allow-overflowing-double-width-glyphs") == 0) { - conf->tweak.allow_overflowing_double_width_glyphs = str_to_bool(value); - if (!conf->tweak.allow_overflowing_double_width_glyphs) - LOG_WARN("tweak: disabled overflowing double-width glyphs"); - } - - else if (strcmp(key, "pua-double-width") == 0) { - conf->tweak.pua_double_width = str_to_bool(value); - if (conf->tweak.pua_double_width) - LOG_WARN("tweak: PUA double width glyphs enabled"); + else if (strcmp(key, "overflowing-glyphs") == 0) { + conf->tweak.overflowing_glyphs = str_to_bool(value); + if (!conf->tweak.overflowing_glyphs) + LOG_WARN("tweak: disabled overflowing glyphs"); } else if (strcmp(key, "damage-whole-window") == 0) { @@ -2833,7 +2827,7 @@ config_load(struct config *conf, const char *conf_path, .tweak = { .fcft_filter = FCFT_SCALING_FILTER_LANCZOS3, - .allow_overflowing_double_width_glyphs = true, + .overflowing_glyphs = true, .grapheme_shaping = false, .grapheme_width_method = GRAPHEME_WIDTH_DOUBLE, .delayed_render_lower_ns = 500000, /* 0.5ms */ @@ -2844,7 +2838,6 @@ config_load(struct config *conf, const char *conf_path, .damage_whole_window = false, .box_drawing_base_thickness = 0.04, .box_drawing_solid_shades = true, - .pua_double_width = false, }, .notifications = tll_init(), diff --git a/config.h b/config.h index 190ea427..729eba3e 100644 --- a/config.h +++ b/config.h @@ -245,7 +245,7 @@ struct config { struct { enum fcft_scaling_filter fcft_filter; - bool allow_overflowing_double_width_glyphs; + bool overflowing_glyphs; bool grapheme_shaping; enum {GRAPHEME_WIDTH_WCSWIDTH, GRAPHEME_WIDTH_DOUBLE} grapheme_width_method; bool render_timer_osd; @@ -256,7 +256,6 @@ struct config { off_t max_shm_pool_size; float box_drawing_base_thickness; bool box_drawing_solid_shades; - bool pua_double_width; } tweak; user_notifications_t notifications; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 6ceaf3b9..8b2f1dc8 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -847,34 +847,24 @@ any of these options. Default: _lanczos3_. -*allow-overflowing-double-width-glyphs* - Boolean. When enabled, double width glyphs with a character width - of 1 are allowed to overflow into the neighbouring cell. +*overflowing-glyphs* + Boolean. When enabled, glyphs wider than their cell(s) are allowed + to render into one additional neighbouring cell. - One use case for this is fonts "icon" characters in the Unicode - private usage area, e.g. Nerd Fonts, or Powerline Fonts. Without - this option, such glyphs will appear "cut off". + One use case for this are fonts with wide italic characters that + "bend" into the next cell. Without this option, such glyphs will + appear "cut off". - Another use case are legacy emoji characters like *WHITE FROWNING - FACE*. + Another use case are fonts with "icon" characters in the Unicode + private usage area, e.g. Nerd Fonts, or Powerline Fonts and legacy + emoji characters like *WHITE FROWNING FACE*. - Note: this feature uses _heuristics_ to determine *which* glyphs - should be allowed to overflow. - - See also: *pua-double-width* + Note: might impact performance depending on the font used. + Especially small font sizes can cause many overflowing glyphs + because of subpixel rendering. Default: _yes_. -*pua-double-width* - Boolean. When enabled, Unicode code points from the private usage - area (PUA) are always considered to be double width, regardless of - the actual glyph width. - - Ignored if *allow-overflowing-double-width-glyphs* has been - disabled. - - Default: _no_. - *render-timer* Enables a frame rendering timer, that prints the time it takes to render each frame, in microseconds, either on-screen, to stderr, diff --git a/render.c b/render.c index 8e66bf00..41a20226 100644 --- a/render.c +++ b/render.c @@ -437,6 +437,20 @@ draw_cursor(const struct terminal *term, const struct cell *cell, } } +static inline void +render_cell_prepass(struct terminal *term, struct row *row, int col) +{ + for (; col < term->cols - 1; col++) { + if (row->cells[col].attrs.confined || + (row->cells[col].attrs.clean == row->cells[col + 1].attrs.clean)) { + break; + } + + row->cells[col].attrs.clean = 0; + row->cells[col + 1].attrs.clean = 0; + } +} + static int render_cell(struct terminal *term, pixman_image_t *pix, struct row *row, int col, int row_no, bool has_cursor) @@ -446,6 +460,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, return 0; cell->attrs.clean = 1; + cell->attrs.confined = true; int width = term->cell_width; int height = term->cell_height; @@ -597,51 +612,32 @@ render_cell(struct terminal *term, pixman_image_t *pix, cell_cols = max(1, min(cell_cols, cols_left)); /* - * Hack! - * - * Deal with double-width glyphs for which wcwidth() returns - * 1. Typically Unicode private usage area characters, - * e.g. powerline, or nerd hack fonts. - * - * Users can enable a tweak option that lets this glyphs - * overflow/bleed into the neighbouring cell. - * - * We only apply this workaround if: - * - the user has explicitly enabled this feature - * - the *character* width is 1 - * - the *glyph* width is at least 1.5 cells - * - the *glyph* width is less than 3 cells - * - *this* column isn’t the last column - * - *this* cells is followed by an empty cell, or a space + * Determine cells that will bleed into their right neighbor and remember + * them for cleanup in the next frame. */ - if (term->conf->tweak.allow_overflowing_double_width_glyphs && - glyph_count > 0 && - cell_cols == 1 && - col < term->cols - 1 && - ((glyphs[0]->x + glyphs[0]->width >= term->cell_width * 15 / 10 && - glyphs[0]->x + glyphs[0]->width < 3 * term->cell_width) || - (term->conf->tweak.pua_double_width && - ((base >= 0x00e000 && base <= 0x00f8ff) || - (base >= 0x0f0000 && base <= 0x0ffffd) || - (base >= 0x100000 && base <= 0x10fffd)))) && - (row->cells[col + 1].wc == 0 || row->cells[col + 1].wc == L' ')) + int render_width = cell_cols * width; + if (term->conf->tweak.overflowing_glyphs && + glyph_count > 0) { - cell_cols = 2; + int glyph_width = 0, advance = 0; + for (size_t i = 0; i < glyph_count; i++) { + glyph_width = max(glyph_width, + advance + glyphs[i]->x + glyphs[i]->width); + advance += glyphs[i]->advance.x; + } - /* - * Ensure the cell we’re overflowing into gets re-rendered, to - * ensure it is erased if *this* cell is erased. Note that we - * do *not* mark the row as dirty - we don’t need to re-render - * the cell if nothing else on the row has changed. - */ - row->cells[col].attrs.clean = 0; - row->cells[col + 1].attrs.clean = 0; + if (glyph_width > render_width) { + render_width = min(glyph_width, render_width + width); + + for (int i = 0; i < cell_cols; i++) + row->cells[col + i].attrs.confined = false; + } } pixman_region32_t clip; pixman_region32_init_rect( &clip, x, y, - cell_cols * term->cell_width, term->cell_height); + render_width, term->cell_height); pixman_image_set_clip_region32(pix, &clip); /* Background */ @@ -771,6 +767,10 @@ static void render_row(struct terminal *term, pixman_image_t *pix, struct row *row, int row_no, int cursor_col) { + if (term->conf->tweak.overflowing_glyphs) + for (int col = term->cols - 1; col >= 0; col--) + render_cell_prepass(term, row, col); + for (int col = term->cols - 1; col >= 0; col--) render_cell(term, pix, row, col, row_no, cursor_col == col); } @@ -1192,8 +1192,10 @@ render_sixel(struct terminal *term, pixman_image_t *pix, (last_col_needs_erase && last_col)) { render_cell(term, pix, row, col, term_row_no, cursor_col == col); - } else + } else { cell->attrs.clean = 1; + cell->attrs.confined = 1; + } } } } diff --git a/terminal.h b/terminal.h index 12a637a0..5ffa6154 100644 --- a/terminal.h +++ b/terminal.h @@ -42,11 +42,12 @@ struct attributes { uint32_t fg:24; bool clean:1; + bool confined:1; bool have_fg:1; bool have_bg:1; uint32_t selected:2; bool url:1; - uint32_t reserved:2; + uint32_t reserved:1; uint32_t bg:24; }; static_assert(sizeof(struct attributes) == 8, "VT attribute struct too large");