]> granicus.if.org Git - vim/commitdiff
patch 8.2.3524: GUI: ligatures are not used v8.2.3524
authorDusan Popovic <dpx@binaryapparatus.com>
Sat, 16 Oct 2021 19:52:05 +0000 (20:52 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 16 Oct 2021 19:52:05 +0000 (20:52 +0100)
Problem:    GUI: ligatures are not used.
Solution:   Add the 'guiligatures' option. (Dusan Popovic, closes #8933)

12 files changed:
runtime/doc/options.txt
src/errors.h
src/gui.c
src/gui.h
src/gui_gtk_x11.c
src/option.h
src/optiondefs.h
src/optionstr.c
src/proto/gui.pro
src/proto/gui_gtk_x11.pro
src/testdir/test_gui.vim
src/version.c

index 735e1c1f936ed2cd2ca58551963da415b2c87798..aed0ad41c135d391b0ff868773c9d977f3b489bf 100644 (file)
@@ -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|),
index a2a1394e84db9b0316d71d8a34b9f7a06146b1d6..12d00b724ad92145e5ef8043ef33aeff47da5932 100644 (file)
@@ -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"));
index 687e9daba1c7265e2f6b38ec21899d2297121c0e..1edf659dd1f9f27566fbea898af5779e8e719921 100644 (file)
--- 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)
 {
index b7b526d0cf6489619872834dbbdba060b6e7e701..9806c8330184c9af80bb2ad39b3137b736f0bb4d 100644 (file)
--- 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) \
index 1a3eadad7271c6d2554a90bbc09439e1e02d0735..c55d9792bbef51ecedc5b3d73d6ccbafb4b3b70a 100644 (file)
@@ -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;
@@ -5652,6 +5656,124 @@ gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags)
        len = convlen;
     }
 
+    /*
+     * 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);
index 75c83d56d96dfcf6f8e608c176e0d7ac25e7c6c1..89cec946dbb26b03d2f3ac93c078255640d83ec6 100644 (file)
@@ -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
index a7a3d0c9008291a64d26590fa2868d557d26f002..042f05551475c682a074ffe6f7d46ab8650b32b4 100644 (file)
@@ -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,
index 06a633b0fcce1560709f8f0282c7c87acc23de52..bced92d6dd0a80c1470d06fa807f7b5998c2d689 100644 (file)
@@ -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'
index d76242026429c03da5e14eab0b1e225187632d63..209e7f12e4f1516043c3bf3bb65847817ba7b093 100644 (file)
@@ -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);
index 1d0a78b092a0b8823ca90c118d76b6626a82eb28..3fa2ac96d6a7cd8959726341578380f055269a18 100644 (file)
@@ -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);
index 6b849c747e971bff9d73f8b37b1026542986c457..240fda355ea5c2daf2a3ab07eac25814b16a780b 100644 (file)
@@ -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 = ''
 
index 8048f8e995dad3806df4547f20010451ce9adfe2..16ba2cfb6d2c89aa935ba3cb831baf3be581c6a2 100644 (file)
@@ -757,6 +757,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3524,
 /**/
     3523,
 /**/