1
0
forked from aniani/vim

patch 8.2.3524: GUI: ligatures are not used

Problem:    GUI: ligatures are not used.
Solution:   Add the 'guiligatures' option. (Dusan Popovic, closes #8933)
This commit is contained in:
Dusan Popovic
2021-10-16 20:52:05 +01:00
committed by Bram Moolenaar
parent c89c91cafd
commit 4eeedc09fe
12 changed files with 239 additions and 14 deletions

View 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. Set it to a negative value to allow windows taller than the
screen. 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'*
'guioptions' 'go' string (default "egmrLtT" (MS-Windows, 'guioptions' 'go' string (default "egmrLtT" (MS-Windows,
"t" is removed in |defaults.vim|), "t" is removed in |defaults.vim|),

View File

@@ -670,3 +670,5 @@ EXTERN char e_separator_not_supported_str[]
INIT(= N_("E1241: Separator not supported: %s")); INIT(= N_("E1241: Separator not supported: %s"));
EXTERN char e_no_white_space_allowed_before_separator_str[] EXTERN char e_no_white_space_allowed_before_separator_str[]
INIT(= N_("E1242: No white space allowed before separator: %s")); 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"));

View File

@@ -460,6 +460,10 @@ gui_init_check(void)
gui.scrollbar_width = gui.scrollbar_height = SB_DEFAULT_WIDTH; gui.scrollbar_width = gui.scrollbar_height = SB_DEFAULT_WIDTH;
gui.prev_wrap = -1; gui.prev_wrap = -1;
# ifdef FEAT_GUI_GTK
CLEAR_FIELD(gui.ligatures_map);
#endif
#if defined(ALWAYS_USE_GUI) || defined(VIMDLL) #if defined(ALWAYS_USE_GUI) || defined(VIMDLL)
result = OK; result = OK;
#else #else
@@ -1065,6 +1069,36 @@ gui_get_wide_font(void)
return OK; 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 static void
gui_set_cursor(int row, int col) gui_set_cursor(int row, int col)
{ {

View File

@@ -409,6 +409,9 @@ typedef struct Gui
char_u *browse_fname; // file name from filedlg char_u *browse_fname; // file name from filedlg
guint32 event_time; guint32 event_time;
char_u ligatures_map[256]; // ascii map for characters 0-255, value is
// 1 if in 'guiligatures'
#endif // FEAT_GUI_GTK #endif // FEAT_GUI_GTK
#if defined(FEAT_GUI_TABLINE) \ #if defined(FEAT_GUI_TABLINE) \

View File

@@ -5595,18 +5595,22 @@ draw_under(int flags, int row, int col, int cells)
int int
gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags) gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags)
{ {
GdkRectangle area; // area for clip mask char_u *conv_buf = NULL; // result of UTF-8 conversion
PangoGlyphString *glyphs; // glyphs of current item char_u *new_conv_buf;
int column_offset = 0; // column offset in cells int convlen;
int i; char_u *sp, *bp;
char_u *conv_buf = NULL; // result of UTF-8 conversion int plen;
char_u *new_conv_buf; int len_sum; // return value needs to add up since we are
int convlen; // printing substrings
char_u *sp, *bp; int byte_sum; // byte position in string
int plen; char_u *cs; // current *s pointer
#if GTK_CHECK_VERSION(3,0,0) int needs_pango; // look ahead, 0=ascii 1=unicode/ligatures
cairo_t *cr; int should_need_pango;
#endif 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) if (gui.text_context == NULL || gtk_widget_get_window(gui.drawarea) == NULL)
return len; return len;
@@ -5652,6 +5656,124 @@ gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags)
len = convlen; 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 * Restrict all drawing to the current screen line in order to prevent
* fuzzy font lookups from messing up the screen. * 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) if (!(flags & DRAW_ITALIC)
&& !((flags & DRAW_BOLD) && gui.font_can_bold) && !((flags & DRAW_BOLD) && gui.font_can_bold)
&& gui.ascii_glyphs != NULL) && gui.ascii_glyphs != NULL
&& !force_pango)
{ {
char_u *p; char_u *p;
@@ -5883,7 +6006,6 @@ skipitall:
#endif #endif
pango_glyph_string_free(glyphs); pango_glyph_string_free(glyphs);
vim_free(conv_buf);
#if GTK_CHECK_VERSION(3,0,0) #if GTK_CHECK_VERSION(3,0,0)
cairo_destroy(cr); cairo_destroy(cr);

View File

@@ -622,6 +622,9 @@ EXTERN char_u *p_guifontset; // 'guifontset'
EXTERN char_u *p_guifontwide; // 'guifontwide' EXTERN char_u *p_guifontwide; // 'guifontwide'
EXTERN int p_guipty; // 'guipty' EXTERN int p_guipty; // 'guipty'
#endif #endif
#ifdef FEAT_GUI_GTK
EXTERN char_u *p_guiligatures; // 'guiligatures'
# endif
#if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11) #if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11)
EXTERN long p_ghr; // 'guiheadroom' EXTERN long p_ghr; // 'guiheadroom'
#endif #endif

View File

@@ -1208,6 +1208,19 @@ static struct vimoption options[] =
{(char_u *)NULL, (char_u *)0L} {(char_u *)NULL, (char_u *)0L}
#endif #endif
SCTX_INIT}, 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, {"guiheadroom", "ghr", P_NUM|P_VI_DEF,
#if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11) #if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11)
(char_u *)&p_ghr, PV_NONE, (char_u *)&p_ghr, PV_NONE,

View File

@@ -1560,6 +1560,13 @@ ambw_end:
redraw_gui_only = TRUE; redraw_gui_only = TRUE;
} }
#endif #endif
# if defined(FEAT_GUI_GTK)
else if (varp == &p_guiligatures)
{
gui_set_ligatures();
redraw_gui_only = TRUE;
}
# endif
#ifdef CURSOR_SHAPE #ifdef CURSOR_SHAPE
// 'guicursor' // 'guicursor'

View File

@@ -7,6 +7,7 @@ void gui_exit(int rc);
void gui_shell_closed(void); void gui_shell_closed(void);
int gui_init_font(char_u *font_list, int fontset); int gui_init_font(char_u *font_list, int fontset);
int gui_get_wide_font(void); int gui_get_wide_font(void);
void gui_set_ligatures(void);
void gui_update_cursor(int force, int clear_selection); void gui_update_cursor(int force, int clear_selection);
void gui_position_menu(void); void gui_position_menu(void);
int gui_get_base_width(void); int gui_get_base_width(void);

View 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_bg_color(guicolor_T color);
void gui_mch_set_sp_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(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_mch_haskey(char_u *name);
int gui_get_x11_windis(Window *win, Display **dis); int gui_get_x11_windis(Window *win, Display **dis);
Display *gui_mch_get_display(void); Display *gui_mch_get_display(void);

View File

@@ -567,6 +567,31 @@ func Test_set_guifontwide()
endif endif
endfunc 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() func Test_set_guiheadroom()
let skipped = '' let skipped = ''

View File

@@ -757,6 +757,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
3524,
/**/ /**/
3523, 3523,
/**/ /**/