Fix a display of unicode composing characters

Reconstruct and use 'gui_macvim_draw_string' for CoreText renderer
This commit is contained in:
ichizok
2017-11-13 11:36:39 +09:00
parent 763f5e3806
commit ee668a459a
4 changed files with 88 additions and 169 deletions
+39 -87
View File
@@ -43,6 +43,7 @@
#define DRAW_ITALIC 0x10 /* draw italic text */
#define DRAW_CURSOR 0x20
#define DRAW_WIDE 0x80 /* draw wide text */
#define DRAW_COMP 0x100 /* drawing composing char */
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
#define kCTFontOrientationDefault kCTFontDefaultOrientation
@@ -1181,7 +1182,7 @@ attributedStringForString(NSString *string, const CTFontRef font,
// 2 - full ligatures including rare
// 1 - basic ligatures
// 0 - no ligatures
[NSNumber numberWithInteger:(useLigatures ? 1 : 0)],
[NSNumber numberWithBool:useLigatures],
kCTLigatureAttributeName,
nil
];
@@ -1192,7 +1193,7 @@ attributedStringForString(NSString *string, const CTFontRef font,
static UniCharCount
fetchGlyphsAndAdvances(const CTLineRef line, CGGlyph *glyphs, CGSize *advances,
UniCharCount length)
CGPoint *positions, UniCharCount length)
{
NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line);
@@ -1211,6 +1212,8 @@ fetchGlyphsAndAdvances(const CTLineRef line, CGGlyph *glyphs, CGSize *advances,
CTRunGetGlyphs(run, range, &glyphs[offset]);
if (advances != NULL)
CTRunGetAdvances(run, range, &advances[offset]);
if (positions != NULL)
CTRunGetPositions(run, range, &positions[offset]);
offset += count;
if (offset >= length)
@@ -1237,78 +1240,28 @@ gatherGlyphs(CGGlyph glyphs[], UniCharCount count)
}
static UniCharCount
ligatureGlyphsForChars(const unichar *chars, CGGlyph *glyphs,
CGPoint *positions, UniCharCount length, CTFontRef font)
composeGlyphsForChars(const unichar *chars, CGGlyph *glyphs,
CGPoint *positions, UniCharCount length, CTFontRef font,
BOOL isComposing, BOOL useLigatures)
{
// CoreText has no simple wait of retrieving a ligature for a set of
// UniChars. The way proposed on the CoreText ML is to convert the text to
// an attributed string, create a CTLine from it and retrieve the Glyphs
// from the CTRuns in it.
CGGlyph refGlyphs[length];
CGPoint refPositions[length];
memcpy(refGlyphs, glyphs, sizeof(CGGlyph) * length);
memcpy(refPositions, positions, sizeof(CGSize) * length);
memset(glyphs, 0, sizeof(CGGlyph) * length);
NSString *plainText = [NSString stringWithCharacters:chars length:length];
CFAttributedStringRef ligatureText = attributedStringForString(plainText,
font, YES);
CFAttributedStringRef composedText = attributedStringForString(plainText,
font,
useLigatures);
CTLineRef ligature = CTLineCreateWithAttributedString(ligatureText);
CTLineRef line = CTLineCreateWithAttributedString(composedText);
CGSize ligatureRanges[length], regularRanges[length];
// get the (composing)glyphs and advances for the new text
UniCharCount offset = fetchGlyphsAndAdvances(line, glyphs, NULL,
isComposing ? positions : NULL,
length);
// get the (ligature)glyphs and advances for the new text
UniCharCount offset = fetchGlyphsAndAdvances(ligature, glyphs,
ligatureRanges, length);
// fetch the advances for the base text
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, refGlyphs,
regularRanges, length);
CFRelease(composedText);
CFRelease(line);
CFRelease(ligatureText);
CFRelease(ligature);
// tricky part: compare both advance ranges and chomp positions which are
// covered by a single ligature while keeping glyphs not in the ligature
// font.
#define fequal(a, b) (fabs((a) - (b)) < FLT_EPSILON)
#define fless(a, b)((a) - (b) < FLT_EPSILON) && (fabs((a) - (b)) > FLT_EPSILON)
CFIndex skip = 0;
CFIndex i;
for (i = 0; i < offset && skip + i < length; ++i) {
memcpy(&positions[i], &refPositions[skip + i], sizeof(CGSize));
if (fequal(ligatureRanges[i].width, regularRanges[skip + i].width)) {
// [mostly] same width
continue;
} else if (fless(ligatureRanges[i].width,
regularRanges[skip + i].width)) {
// original is wider than our result - use the original glyph
// FIXME: this is currently the only way to detect emoji (except
// for 'glyph[i] == 5')
glyphs[i] = refGlyphs[skip + i];
continue;
}
// no, that's a ligature
// count how many positions this glyph would take up in the base text
CFIndex j = 0;
float width = ceil(regularRanges[skip + i].width);
while ((int)width < (int)ligatureRanges[i].width
&& skip + i + j < length) {
width += ceil(regularRanges[++j + skip + i].width);
}
skip += j;
}
#undef fless
#undef fequal
// as ligatures combine characters it is required to adjust the
// as ligatures composing characters it is required to adjust the
// original length value
return offset;
}
@@ -1316,18 +1269,12 @@ ligatureGlyphsForChars(const unichar *chars, CGGlyph *glyphs,
static void
recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
UniCharCount length, CGContextRef context, CTFontRef fontRef,
NSMutableArray *fontCache, BOOL useLigatures)
NSMutableArray *fontCache, BOOL isComposing, BOOL useLigatures)
{
if (CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, length)) {
// All chars were mapped to glyphs, so draw all at once and return.
if (useLigatures) {
length = ligatureGlyphsForChars(chars, glyphs, positions, length,
fontRef);
} else {
// only fixup surrogate pairs if we're not using ligatures
length = gatherGlyphs(glyphs, length);
}
length = composeGlyphsForChars(chars, glyphs, positions, length,
fontRef, isComposing, useLigatures);
CTFontDrawGlyphs(fontRef, glyphs, positions, length, context);
return;
}
@@ -1388,7 +1335,7 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
return;
recurseDraw(chars, glyphs, positions, attemptedCount, context,
fallback, fontCache, useLigatures);
fallback, fontCache, isComposing, useLigatures);
// If only a portion of the invalid range was rendered above,
// the remaining range needs to be attempted by subsequent
@@ -1422,8 +1369,10 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
float x = col*cellSize.width + insetSize.width;
float y = frame.size.height - insetSize.height - (1+row)*cellSize.height;
float w = cellSize.width;
BOOL wide = flags & DRAW_WIDE ? YES : NO;
BOOL composing = flags & DRAW_COMP ? YES : NO;
if (flags & DRAW_WIDE) {
if (wide) {
// NOTE: It is assumed that either all characters in 'chars' are wide
// or all are normal width.
w *= 2;
@@ -1489,7 +1438,7 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
if (length > maxlen) {
if (glyphs) free(glyphs);
if (positions) free(positions);
glyphs = (CGGlyph*)malloc(length*sizeof(CGGlyph));
glyphs = (CGGlyph*)calloc(length, sizeof(CGGlyph));
positions = (CGPoint*)calloc(length, sizeof(CGPoint));
maxlen = length;
}
@@ -1500,15 +1449,17 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
CGContextSetFontSize(context, [font pointSize]);
// Calculate position of each glyph relative to (x,y).
NSUInteger i;
float xrel = 0;
for (i = 0; i < length; ++i) {
positions[i].x = xrel;
xrel += w;
if (!composing) {
float xrel = 0;
for (unsigned i = 0; i < length; ++i) {
positions[i].x = xrel;
positions[i].y = .0;
xrel += w;
}
}
CTFontRef fontRef = (CTFontRef)(flags & DRAW_WIDE ? [fontWide retain]
: [font retain]);
CTFontRef fontRef = (CTFontRef)(wide ? [fontWide retain]
: [font retain]);
unsigned traits = 0;
if (flags & DRAW_ITALIC)
traits |= kCTFontItalicTrait;
@@ -1517,7 +1468,7 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
if (traits) {
CTFontRef fr = CTFontCreateCopyWithSymbolicTraits(fontRef, 0.0, NULL,
traits, traits);
traits, traits);
if (fr) {
CFRelease(fontRef);
fontRef = fr;
@@ -1525,7 +1476,8 @@ recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
}
CGContextSetTextPosition(context, x, y+fontDescent);
recurseDraw(chars, glyphs, positions, length, context, fontRef, fontCache, ligatures);
recurseDraw(chars, glyphs, positions, length, context, fontRef, fontCache,
composing, ligatures);
CFRelease(fontRef);
if (thinStrokes)
+42 -50
View File
@@ -477,41 +477,16 @@ gui_mch_delete_lines(int row, int num_lines)
}
void
gui_mch_draw_string(int row, int col, char_u *s, int len, int cells, int flags)
{
#ifdef FEAT_MBYTE
char_u *conv_str = NULL;
if (output_conv.vc_type != CONV_NONE) {
conv_str = string_convert(&output_conv, s, &len);
if (conv_str)
s = conv_str;
}
#endif
[[MMBackend sharedInstance] drawString:s
length:len
row:row
column:col
cells:cells
flags:flags];
#ifdef FEAT_MBYTE
if (conv_str)
vim_free(conv_str);
#endif
}
int
gui_macvim_draw_string(int row, int col, char_u *s, int len, int flags)
{
int c, cn, cl, i;
MMBackend *backend = [MMBackend sharedInstance];
#ifdef FEAT_MBYTE
int c, cw, cl, ccl;
int start = 0;
int endcol = col;
int startcol = col;
BOOL wide = NO;
MMBackend *backend = [MMBackend sharedInstance];
#ifdef FEAT_MBYTE
char_u *conv_str = NULL;
if (output_conv.vc_type != CONV_NONE) {
@@ -519,45 +494,62 @@ gui_macvim_draw_string(int row, int col, char_u *s, int len, int flags)
if (conv_str)
s = conv_str;
}
#endif
// Loop over each character and output text when it changes from normal to
// wide and vice versa.
for (i = 0; i < len; i += cl) {
for (int i = 0; i < len; i += cl) {
c = utf_ptr2char(s + i);
cn = utf_char2cells(c);
cw = utf_char2cells(c);
cl = utf_ptr2len(s + i);
if (0 == cl)
ccl = utfc_ptr2len(s + i);
if (cl == 0)
len = i; // len must be wrong (shouldn't happen)
if (!utf_iscomposing(c)) {
if ((cn > 1 && !wide) || (cn <= 1 && wide)) {
// Changed from normal to wide or vice versa.
[backend drawString:(s+start) length:i-start
row:row column:startcol
cells:endcol-startcol
flags:(wide ? flags|DRAW_WIDE : flags)];
if (i > start && (cl < ccl || (cw > 1 && !wide) || (cw <= 1 && wide))) {
// Changed from normal to wide or vice versa.
[backend drawString:(s+start) length:i-start
row:row column:startcol
cells:endcol-startcol
flags:flags|(wide ? DRAW_WIDE : 0)];
start = i;
startcol = endcol;
}
start = i;
startcol = endcol;
}
wide = cn > 1;
endcol += cn;
wide = cw > 1;
endcol += cw;
if (cl < ccl) {
// Changed from normal to wide or vice versa.
[backend drawString:(s+start) length:ccl
row:row column:startcol
cells:endcol-startcol
flags:flags|DRAW_COMP|(wide ? DRAW_WIDE : 0)];
start = i + ccl;
startcol = endcol;
cl = ccl;
}
}
// Output remaining characters.
[backend drawString:(s+start) length:len-start
row:row column:startcol cells:endcol-startcol
flags:(wide ? flags|DRAW_WIDE : flags)];
if (len > start) {
// Output remaining characters.
[backend drawString:(s+start) length:len-start
row:row column:startcol
cells:endcol-startcol
flags:flags|(wide ? DRAW_WIDE : 0)];
}
#ifdef FEAT_MBYTE
if (conv_str)
vim_free(conv_str);
#endif
return endcol - col;
#else
[backend drawString:s length:len
row:row column:col
cells:len flags:flags];
return len;
#endif
}
+7 -30
View File
@@ -2469,16 +2469,11 @@ gui_outstr_nowrap(
#ifdef FEAT_GUI_GTK
/* The value returned is the length in display cells */
len = gui_gtk2_draw_string(gui.row, col, s, len, draw_flags);
#elif defined(FEAT_GUI_MACVIM)
/* The value returned is the length in display cells */
len = gui_macvim_draw_string(gui.row, col, s, len, draw_flags);
#else
# ifdef FEAT_MBYTE
# ifdef FEAT_GUI_MACVIM
if (use_gui_macvim_draw_string)
{
/* The value returned is the length in display cells */
len = gui_macvim_draw_string(gui.row, col, s, len, draw_flags);
}
else
# endif
if (enc_utf8)
{
int start; /* index of bytes to be drawn */
@@ -2512,9 +2507,6 @@ gui_outstr_nowrap(
cells += cn;
if (!comping || sep_comp)
{
# ifdef FEAT_GUI_MACVIM
curr_wide = (cn > 1);
# else
if (cn > 1
# ifdef FEAT_XFONTSET
&& fontset == NOFONTSET
@@ -2523,7 +2515,6 @@ gui_outstr_nowrap(
curr_wide = TRUE;
else
curr_wide = FALSE;
# endif
}
cl = utf_ptr2len(s + i);
if (cl == 0) /* hit end of string */
@@ -2555,13 +2546,7 @@ gui_outstr_nowrap(
if (prev_wide)
gui_mch_set_font(wide_font);
gui_mch_draw_string(gui.row, scol, s + start, thislen,
# ifdef FEAT_GUI_MACVIM
cells,
draw_flags | (prev_wide ? DRAW_WIDE : 0)
# else
draw_flag
# endif
);
draw_flags);
if (prev_wide)
gui_mch_set_font(font);
start += thislen;
@@ -2591,17 +2576,13 @@ gui_outstr_nowrap(
/* Draw a composing char on top of the previous char. */
if (comping && sep_comp)
{
# if !defined(FEAT_GUI_MACVIM) && \
(defined(__APPLE_CC__) && TARGET_API_MAC_CARBON)
# if defined(__APPLE_CC__) && TARGET_API_MAC_CARBON
/* Carbon ATSUI autodraws composing char over previous char */
gui_mch_draw_string(gui.row, scol, s + i, cl,
draw_flags | DRAW_TRANSP);
# else
gui_mch_draw_string(gui.row, scol - cn, s + i, cl,
# ifdef FEAT_GUI_MACVIM
0,
# endif
draw_flags | DRAW_TRANSP | DRAW_COMP);
draw_flags | DRAW_TRANSP);
# endif
start = i + cl;
}
@@ -2613,11 +2594,7 @@ gui_outstr_nowrap(
else
# endif
{
gui_mch_draw_string(gui.row, col, s, len,
# ifdef FEAT_GUI_MACVIM
len,
# endif
draw_flags);
gui_mch_draw_string(gui.row, col, s, len, draw_flags);
# ifdef FEAT_MBYTE
if (enc_dbcs == DBCS_JPNU)
{
-2
View File
@@ -30,8 +30,6 @@ gui_mch_clear_all(void);
gui_mch_clear_block(int row1, int col1, int row2, int col2);
void
gui_mch_delete_lines(int row, int num_lines);
void
gui_mch_draw_string(int row, int col, char_u *s, int len, int cells, int flags);
int
gui_macvim_draw_string(int row, int col, char_u *s, int len, int flags);
void