summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDusan Popovic <dpx@binaryapparatus.com>2021-10-16 20:52:05 +0100
committerBram Moolenaar <Bram@vim.org>2021-10-16 20:52:05 +0100
commit4eeedc09fed0cbbb3ba48317e0a01e20cd0b4f80 (patch)
treecac81ddbe09eac705e72e2c3390749cc60a16ae0
parentc89c91cafd91fbf17f431d800bbf4cafcffffe7a (diff)
downloadvim-git-4eeedc09fed0cbbb3ba48317e0a01e20cd0b4f80.tar.gz
patch 8.2.3524: GUI: ligatures are not usedv8.2.3524
Problem: GUI: ligatures are not used. Solution: Add the 'guiligatures' option. (Dusan Popovic, closes #8933)
-rw-r--r--runtime/doc/options.txt12
-rw-r--r--src/errors.h2
-rw-r--r--src/gui.c34
-rw-r--r--src/gui.h3
-rw-r--r--src/gui_gtk_x11.c150
-rw-r--r--src/option.h3
-rw-r--r--src/optiondefs.h13
-rw-r--r--src/optionstr.c7
-rw-r--r--src/proto/gui.pro1
-rw-r--r--src/proto/gui_gtk_x11.pro1
-rw-r--r--src/testdir/test_gui.vim25
-rw-r--r--src/version.c2
12 files changed, 239 insertions, 14 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 735e1c1f9..aed0ad41c 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3790,6 +3790,18 @@ A jump table for the options with a short description can be found at |Q_op|.
screen. Set it to a negative value to allow windows taller than the
screen.
+ *'guiligatures'* *'gli'* *E1243*
+'guiligatures' 'gli' string (default "")
+ global
+ {only for GTK GUI}
+ List of ASCII characters that, when combined together, can create more
+ complex shapes. Each character must be a printable ASCII character
+ with a value in the 32-127 range.
+ Example: >
+ :set guiligatures=!\"#$%&()*+-./:<=>?@[]^_{\|~
+< Changing this option updates screen output immediately. Set it to an
+ empty string to disable ligatures.
+
*'guioptions'* *'go'*
'guioptions' 'go' string (default "egmrLtT" (MS-Windows,
"t" is removed in |defaults.vim|),
diff --git a/src/errors.h b/src/errors.h
index a2a1394e8..12d00b724 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -670,3 +670,5 @@ EXTERN char e_separator_not_supported_str[]
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 687e9daba..1edf659dd 100644
--- a/src/gui.c
+++ b/src/gui.c
@@ -460,6 +460,10 @@ gui_init_check(void)
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 @@ gui_get_wide_font(void)
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 b7b526d0c..9806c8330 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -409,6 +409,9 @@ typedef struct Gui
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 1a3eadad7..c55d9792b 100644
--- a/src/gui_gtk_x11.c
+++ b/src/gui_gtk_x11.c
@@ -5595,18 +5595,22 @@ draw_under(int flags, int row, int col, int cells)
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 @@ gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags)
}
/*
+ * 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 @@ gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags)
*/
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 @@ skipitall:
#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 75c83d56d..89cec946d 100644
--- a/src/option.h
+++ b/src/option.h
@@ -622,6 +622,9 @@ EXTERN char_u *p_guifontset; // 'guifontset'
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 a7a3d0c90..042f05551 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -1208,6 +1208,19 @@ static struct vimoption options[] =
{(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 06a633b0f..bced92d6d 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -1560,6 +1560,13 @@ ambw_end:
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 d76242026..209e7f12e 100644
--- a/src/proto/gui.pro
+++ b/src/proto/gui.pro
@@ -7,6 +7,7 @@ void gui_exit(int rc);
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 1d0a78b09..3fa2ac96d 100644
--- a/src/proto/gui_gtk_x11.pro
+++ b/src/proto/gui_gtk_x11.pro
@@ -43,6 +43,7 @@ void gui_mch_set_fg_color(guicolor_T color);
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 6b849c747..240fda355 100644
--- a/src/testdir/test_gui.vim
+++ b/src/testdir/test_gui.vim
@@ -567,6 +567,31 @@ func Test_set_guifontwide()
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 8048f8e99..16ba2cfb6 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 3524,
+/**/
3523,
/**/
3522,