To: vim_dev@googlegroups.com Subject: Patch 8.2.3524 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.3524 Problem: GUI: ligatures are not used. Solution: Add the 'guiligatures' option. (Dusan Popovic, closes #8933) Files: runtime/doc/options.txt, src/gui.c, src/gui.h, src/gui_gtk_x11.c, src/option.h, src/optiondefs.h, src/optionstr.c, src/errors.h, src/proto/gui.pro, src/proto/gui_gtk_x11.pro, src/testdir/test_gui.vim *** ../vim-8.2.3523/runtime/doc/options.txt 2021-10-16 17:51:08.047842118 +0100 --- runtime/doc/options.txt 2021-10-16 20:45:34.367702659 +0100 *************** *** 3779,3784 **** --- 3790,3807 ---- 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|), *** ../vim-8.2.3523/src/gui.c 2021-07-20 20:07:32.964058857 +0100 --- src/gui.c 2021-10-16 20:34:32.858636291 +0100 *************** *** 460,465 **** --- 460,469 ---- 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,1070 **** --- 1069,1104 ---- 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) { *** ../vim-8.2.3523/src/gui.h 2020-12-30 12:14:41.950441890 +0000 --- src/gui.h 2021-10-16 20:33:58.110140304 +0100 *************** *** 409,414 **** --- 409,417 ---- 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) \ *** ../vim-8.2.3523/src/gui_gtk_x11.c 2021-06-02 12:28:11.427120469 +0100 --- src/gui_gtk_x11.c 2021-10-16 20:51:16.056225178 +0100 *************** *** 5595,5612 **** 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 if (gui.text_context == NULL || gtk_widget_get_window(gui.drawarea) == NULL) return len; --- 5595,5616 ---- int gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags) { ! 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,5658 **** --- 5657,5780 ---- } /* + * 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,5685 **** */ if (!(flags & DRAW_ITALIC) && !((flags & DRAW_BOLD) && gui.font_can_bold) ! && gui.ascii_glyphs != NULL) { char_u *p; --- 5801,5808 ---- */ if (!(flags & DRAW_ITALIC) && !((flags & DRAW_BOLD) && gui.font_can_bold) ! && gui.ascii_glyphs != NULL ! && !force_pango) { char_u *p; *************** *** 5883,5889 **** #endif pango_glyph_string_free(glyphs); - vim_free(conv_buf); #if GTK_CHECK_VERSION(3,0,0) cairo_destroy(cr); --- 6006,6011 ---- *** ../vim-8.2.3523/src/option.h 2021-10-16 15:41:25.378336694 +0100 --- src/option.h 2021-10-16 20:40:54.896084995 +0100 *************** *** 622,627 **** --- 622,630 ---- 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 *** ../vim-8.2.3523/src/optiondefs.h 2021-10-16 15:41:25.378336694 +0100 --- src/optiondefs.h 2021-10-16 20:25:49.587154629 +0100 *************** *** 1208,1213 **** --- 1208,1226 ---- {(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, *** ../vim-8.2.3523/src/optionstr.c 2021-10-16 15:41:25.378336694 +0100 --- src/optionstr.c 2021-10-16 20:25:49.587154629 +0100 *************** *** 1560,1565 **** --- 1560,1572 ---- 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' *** ../vim-8.2.3523/src/errors.h 2021-10-13 15:04:28.859631740 +0100 --- src/errors.h 2021-10-16 20:31:57.952424519 +0100 *************** *** 670,672 **** --- 670,674 ---- 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")); *** ../vim-8.2.3523/src/proto/gui.pro 2020-08-01 12:10:04.582539553 +0100 --- src/proto/gui.pro 2021-10-16 20:42:09.709104774 +0100 *************** *** 7,12 **** --- 7,13 ---- 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); *** ../vim-8.2.3523/src/proto/gui_gtk_x11.pro 2021-03-18 21:28:53.563835682 +0000 --- src/proto/gui_gtk_x11.pro 2021-10-16 20:25:49.587154629 +0100 *************** *** 43,48 **** --- 43,49 ---- 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); *** ../vim-8.2.3523/src/testdir/test_gui.vim 2021-10-16 13:00:10.940165406 +0100 --- src/testdir/test_gui.vim 2021-10-16 20:45:23.643563983 +0100 *************** *** 567,572 **** --- 567,597 ---- 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 = '' *** ../vim-8.2.3523/src/version.c 2021-10-16 19:07:40.459867578 +0100 --- src/version.c 2021-10-16 20:25:37.918987372 +0100 *************** *** 759,760 **** --- 759,762 ---- { /* Add new patch number below this line */ + /**/ + 3524, /**/ -- Living in Hollywood is like living in a bowl of granola. What ain't fruits and nuts is flakes. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///