diff --git a/CHANGELOG.md b/CHANGELOG.md index 78016bae..0b79b0ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ terminfo entries. This should make 24-bit RGB colors work in tmux and neovim, without the need for config hacks or detection heuristics (https://codeberg.org/dnkl/foot/issues/615). +* `[tweak].grapheme-width-method=wcswidth|double-width` option to + `foot.ini`. + ### Changed ### Deprecated diff --git a/config.c b/config.c index 372e9f9c..e02ca3de 100644 --- a/config.c +++ b/config.c @@ -2255,6 +2255,15 @@ parse_section_tweak( LOG_WARN("tweak: grapheme shaping"); } + else if (strcmp(key, "grapheme-width-method") == 0) { + if (strcmp(value, "double-width") == 0) + conf->tweak.grapheme_width_method = GRAPHEME_WIDTH_DOUBLE; + else if (strcmp(value, "wcswidth") == 0) + conf->tweak.grapheme_width_method = GRAPHEME_WIDTH_WCSWIDTH; + + LOG_WARN("%s:%d [tweak]: grapheme-width-method=%s", path, lineno, value); + } + else if (strcmp(key, "render-timer") == 0) { if (strcmp(value, "none") == 0) { conf->tweak.render_timer_osd = false; @@ -2823,6 +2832,7 @@ config_load(struct config *conf, const char *conf_path, .fcft_filter = FCFT_SCALING_FILTER_LANCZOS3, .allow_overflowing_double_width_glyphs = true, .grapheme_shaping = false, + .grapheme_width_method = GRAPHEME_WIDTH_DOUBLE, .delayed_render_lower_ns = 500000, /* 0.5ms */ .delayed_render_upper_ns = 16666666 / 2, /* half a frame period (60Hz) */ .max_shm_pool_size = 512 * 1024 * 1024, diff --git a/config.h b/config.h index ee427267..2300a0ad 100644 --- a/config.h +++ b/config.h @@ -246,6 +246,7 @@ struct config { enum fcft_scaling_filter fcft_filter; bool allow_overflowing_double_width_glyphs; bool grapheme_shaping; + enum {GRAPHEME_WIDTH_WCSWIDTH, GRAPHEME_WIDTH_DOUBLE} grapheme_width_method; bool render_timer_osd; bool render_timer_log; bool damage_whole_window; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 2e408b3a..684497ca 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -997,8 +997,26 @@ any of these options. but is necessary to not break cursor synchronization with the application running in foot. + See also: *grapheme-width-method*. + Default: _no_ +*grapheme-width-method* + Selects which method to use when calculating the width + (i.e. number of columns) of a grapheme cluster. One of + *double-width* and *wcswidth*. + + *wcswidth* simply adds together the individual width of all + codepoints making up the cluster. + + *double-width* does the same, but limits the maximum number of + columns to 2. This is more correct, but is likely to break + applications since applications typically use *wcswidth*(3) + internally to calculate the width. This results in cursor + de-synchronization issues. + + Default: _double-width_ + *max-shm-pool-size-mb* This option controls the amount of virtual address space used by the pixmap memory to which the terminal screen content is diff --git a/vt.c b/vt.c index 900ffd9d..b9a4f4b0 100644 --- a/vt.c +++ b/vt.c @@ -764,19 +764,26 @@ action_utf8_print(struct terminal *term, wchar_t wc) (wanted_count - 2) * sizeof(new_cc->chars[0])); } - int grapheme_width = composed != NULL ? composed->width : base_width; + const int grapheme_width = + composed != NULL ? composed->width : base_width; - if (wc == 0xfe0f && grapheme_width < 2) - grapheme_width = 2; - else - grapheme_width += width; - new_cc->width = grapheme_width; + switch (term->conf->tweak.grapheme_width_method) { + case GRAPHEME_WIDTH_DOUBLE: + if (unlikely(wc == 0xfe0f)) + width = 2; + new_cc->width = min(grapheme_width + width, 2); + break; + + case GRAPHEME_WIDTH_WCSWIDTH: + new_cc->width = grapheme_width + width; + break; + } term->composed_count++; composed_insert(&term->composed, new_cc); wc = CELL_COMB_CHARS_LO + key; - width = grapheme_width; + width = new_cc->width; xassert(wc >= CELL_COMB_CHARS_LO); xassert(wc <= CELL_COMB_CHARS_HI);