patch 8.2.3524: GUI: ligatures are not used
Problem: GUI: ligatures are not used.
Solution: Add the 'guiligatures' option. (Dusan Popovic, closes #8933)
diff --git a/src/errors.h b/src/errors.h
index a2a1394..12d00b7 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -670,3 +670,5 @@
INIT(= N_("E1241: Separator not supported: %s"));
EXTERN char e_no_white_space_allowed_before_separator_str[]
INIT(= N_("E1242: No white space allowed before separator: %s"));
+EXTERN char e_ascii_code_not_in_range[]
+ INIT(= N_("E1243: ASCII code not in 32-127 range"));
diff --git a/src/gui.c b/src/gui.c
index 687e9da..1edf659 100644
--- a/src/gui.c
+++ b/src/gui.c
@@ -460,6 +460,10 @@
gui.scrollbar_width = gui.scrollbar_height = SB_DEFAULT_WIDTH;
gui.prev_wrap = -1;
+# ifdef FEAT_GUI_GTK
+ CLEAR_FIELD(gui.ligatures_map);
+#endif
+
#if defined(ALWAYS_USE_GUI) || defined(VIMDLL)
result = OK;
#else
@@ -1065,6 +1069,36 @@
return OK;
}
+#if defined(FEAT_GUI_GTK) || defined(PROTO)
+/*
+ * Set list of ascii characters that combined can create ligature.
+ * Store them in char map for quick access from gui_gtk2_draw_string.
+ */
+ void
+gui_set_ligatures(void)
+{
+ char_u *p;
+
+ if (*p_guiligatures != NUL)
+ {
+ // check for invalid characters
+ for (p = p_guiligatures; *p != NUL; ++p)
+ if (*p < 32 || *p > 127)
+ {
+ emsg(_(e_ascii_code_not_in_range));
+ return;
+ }
+
+ // store valid setting into ligatures_map
+ CLEAR_FIELD(gui.ligatures_map);
+ for (p = p_guiligatures; *p != NUL; ++p)
+ gui.ligatures_map[*p] = 1;
+ }
+ else
+ CLEAR_FIELD(gui.ligatures_map);
+}
+#endif
+
static void
gui_set_cursor(int row, int col)
{
diff --git a/src/gui.h b/src/gui.h
index b7b526d..9806c83 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -409,6 +409,9 @@
char_u *browse_fname; // file name from filedlg
guint32 event_time;
+
+ char_u ligatures_map[256]; // ascii map for characters 0-255, value is
+ // 1 if in 'guiligatures'
#endif // FEAT_GUI_GTK
#if defined(FEAT_GUI_TABLINE) \
diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c
index 1a3eada..c55d979 100644
--- a/src/gui_gtk_x11.c
+++ b/src/gui_gtk_x11.c
@@ -5595,18 +5595,22 @@
int
gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags)
{
- GdkRectangle area; // area for clip mask
- PangoGlyphString *glyphs; // glyphs of current item
- int column_offset = 0; // column offset in cells
- int i;
- char_u *conv_buf = NULL; // result of UTF-8 conversion
- char_u *new_conv_buf;
- int convlen;
- char_u *sp, *bp;
- int plen;
-#if GTK_CHECK_VERSION(3,0,0)
- cairo_t *cr;
-#endif
+ char_u *conv_buf = NULL; // result of UTF-8 conversion
+ char_u *new_conv_buf;
+ int convlen;
+ char_u *sp, *bp;
+ int plen;
+ int len_sum; // return value needs to add up since we are
+ // printing substrings
+ int byte_sum; // byte position in string
+ char_u *cs; // current *s pointer
+ int needs_pango; // look ahead, 0=ascii 1=unicode/ligatures
+ int should_need_pango;
+ int slen;
+ int is_ligature;
+ int next_is_ligature;
+ int is_utf8;
+ char_u backup_ch;
if (gui.text_context == NULL || gtk_widget_get_window(gui.drawarea) == NULL)
return len;
@@ -5653,6 +5657,124 @@
}
/*
+ * Ligature support and complex utf-8 char optimization:
+ * String received to output to screen can print using pre-cached glyphs
+ * (fast) or Pango (slow). Ligatures and multibype utf-8 must use Pango.
+ * Since we receive mixed content string, split it into logical segments
+ * that are guaranteed to go trough glyphs as much as possible. Since
+ * single ligature char prints as ascii, print it that way.
+ */
+ len_sum = 0; // return value needs to add up since we are printing
+ // substrings
+ byte_sum = 0;
+ cs = s;
+ // look ahead, 0=ascii 1=unicode/ligatures
+ needs_pango = ((*cs & 0x80) || gui.ligatures_map[*cs]);
+
+ // split string into ascii and non-ascii (ligatures + utf-8) substrings,
+ // print glyphs or use Pango
+ while (cs < s + len)
+ {
+ slen = 0;
+ while (slen < (len - byte_sum))
+ {
+ is_ligature = gui.ligatures_map[*(cs + slen)];
+ // look ahead, single ligature char between ascii is ascii
+ if (is_ligature && !needs_pango)
+ {
+ if ((slen + 1) < (len - byte_sum))
+ {
+ next_is_ligature = gui.ligatures_map[*(cs + slen + 1)];
+ if (!next_is_ligature)
+ is_ligature = 0;
+ }
+ else
+ {
+ is_ligature = 0;
+ }
+ }
+ is_utf8 = *(cs + slen) & 0x80;
+ should_need_pango = (is_ligature || is_utf8);
+ if (needs_pango != should_need_pango) // mode switch
+ break;
+ if (needs_pango)
+ {
+ if (is_ligature)
+ {
+ slen++; // ligature char by char
+ }
+ else
+ {
+ if ((*(cs + slen) & 0xC0) == 0x80)
+ {
+ // a continuation, find next 0xC0 != 0x80 but don't
+ // include it
+ while ((slen < (len - byte_sum))
+ && ((*(cs + slen) & 0xC0) == 0x80))
+ {
+ slen++;
+ }
+ }
+ else if ((*(cs + slen) & 0xE0) == 0xC0)
+ {
+ // + one byte utf8
+ slen++;
+ }
+ else if ((*(cs + slen) & 0xF0) == 0xE0)
+ {
+ // + two bytes utf8
+ slen += 2;
+ }
+ else if ((*(cs + slen) & 0xF8) == 0xF0)
+ {
+ // + three bytes utf8
+ slen += 3;
+ }
+ else
+ {
+ // this should not happen, try moving forward, Pango
+ // will catch it
+ slen++;
+ }
+ }
+ }
+ else
+ {
+ slen++; // ascii
+ }
+ }
+ // temporarily zero terminate substring, print, restore char, wrap
+ backup_ch = *(cs + slen);
+ *(cs + slen) = 0;
+ len_sum += gui_gtk2_draw_string_ext(row, col + len_sum,
+ cs, slen, flags, needs_pango);
+ *(cs + slen) = backup_ch;
+ cs += slen;
+ byte_sum += slen;
+ needs_pango = should_need_pango;
+ }
+ vim_free(conv_buf);
+ return len_sum;
+}
+
+ int
+gui_gtk2_draw_string_ext(
+ int row,
+ int col,
+ char_u *s,
+ int len,
+ int flags,
+ int force_pango)
+{
+ GdkRectangle area; // area for clip mask
+ PangoGlyphString *glyphs; // glyphs of current item
+ int column_offset = 0; // column offset in cells
+ int i;
+#if GTK_CHECK_VERSION(3,0,0)
+ cairo_t *cr;
+#endif
+
+ /*
* Restrict all drawing to the current screen line in order to prevent
* fuzzy font lookups from messing up the screen.
*/
@@ -5679,7 +5801,8 @@
*/
if (!(flags & DRAW_ITALIC)
&& !((flags & DRAW_BOLD) && gui.font_can_bold)
- && gui.ascii_glyphs != NULL)
+ && gui.ascii_glyphs != NULL
+ && !force_pango)
{
char_u *p;
@@ -5883,7 +6006,6 @@
#endif
pango_glyph_string_free(glyphs);
- vim_free(conv_buf);
#if GTK_CHECK_VERSION(3,0,0)
cairo_destroy(cr);
diff --git a/src/option.h b/src/option.h
index 75c83d5..89cec94 100644
--- a/src/option.h
+++ b/src/option.h
@@ -622,6 +622,9 @@
EXTERN char_u *p_guifontwide; // 'guifontwide'
EXTERN int p_guipty; // 'guipty'
#endif
+#ifdef FEAT_GUI_GTK
+EXTERN char_u *p_guiligatures; // 'guiligatures'
+# endif
#if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11)
EXTERN long p_ghr; // 'guiheadroom'
#endif
diff --git a/src/optiondefs.h b/src/optiondefs.h
index a7a3d0c..042f055 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -1208,6 +1208,19 @@
{(char_u *)NULL, (char_u *)0L}
#endif
SCTX_INIT},
+
+
+ {"guiligatures", "gli", P_STRING|P_VI_DEF|P_RCLR|P_ONECOMMA|P_NODUP,
+#if defined(FEAT_GUI_GTK)
+ (char_u *)&p_guiligatures, PV_NONE,
+ {(char_u *)"", (char_u *)0L}
+#else
+ (char_u *)NULL, PV_NONE,
+ {(char_u *)NULL, (char_u *)0L}
+#endif
+ SCTX_INIT},
+
+
{"guiheadroom", "ghr", P_NUM|P_VI_DEF,
#if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11)
(char_u *)&p_ghr, PV_NONE,
diff --git a/src/optionstr.c b/src/optionstr.c
index 06a633b..bced92d 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -1560,6 +1560,13 @@
redraw_gui_only = TRUE;
}
#endif
+# if defined(FEAT_GUI_GTK)
+ else if (varp == &p_guiligatures)
+ {
+ gui_set_ligatures();
+ redraw_gui_only = TRUE;
+ }
+# endif
#ifdef CURSOR_SHAPE
// 'guicursor'
diff --git a/src/proto/gui.pro b/src/proto/gui.pro
index d762420..209e7f1 100644
--- a/src/proto/gui.pro
+++ b/src/proto/gui.pro
@@ -7,6 +7,7 @@
void gui_shell_closed(void);
int gui_init_font(char_u *font_list, int fontset);
int gui_get_wide_font(void);
+void gui_set_ligatures(void);
void gui_update_cursor(int force, int clear_selection);
void gui_position_menu(void);
int gui_get_base_width(void);
diff --git a/src/proto/gui_gtk_x11.pro b/src/proto/gui_gtk_x11.pro
index 1d0a78b..3fa2ac9 100644
--- a/src/proto/gui_gtk_x11.pro
+++ b/src/proto/gui_gtk_x11.pro
@@ -43,6 +43,7 @@
void gui_mch_set_bg_color(guicolor_T color);
void gui_mch_set_sp_color(guicolor_T color);
int gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags);
+int gui_gtk2_draw_string_ext(int row, int col, char_u *s, int len, int flags, int force_pango);
int gui_mch_haskey(char_u *name);
int gui_get_x11_windis(Window *win, Display **dis);
Display *gui_mch_get_display(void);
diff --git a/src/testdir/test_gui.vim b/src/testdir/test_gui.vim
index 6b849c7..240fda3 100644
--- a/src/testdir/test_gui.vim
+++ b/src/testdir/test_gui.vim
@@ -567,6 +567,31 @@
endif
endfunc
+func Test_set_guiligatures()
+ let skipped = ''
+
+ if !g:x11_based_gui
+ let skipped = g:not_supported . 'guiligatures'
+ else
+ if has('gui_gtk') || has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+ " Try correct value
+ set guiligatures=<>=ab
+ call assert_equal("<>=ab", &guiligatures)
+ " Try to throw error
+ try
+ set guiligatures=<>=šab
+ call assert_report("'set guiligatures=<>=šab should have failed")
+ catch
+ call assert_exception('E1243:')
+ endtry
+ endif
+ endif
+
+ if !empty(skipped)
+ throw skipped
+ endif
+endfunc
+
func Test_set_guiheadroom()
let skipped = ''
diff --git a/src/version.c b/src/version.c
index 8048f8e..16ba2cf 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 3524,
+/**/
3523,
/**/
3522,