Skip to content

Commit 1496167

Browse files
authored
Merge pull request #680 from ichizok/fix/unicode-composing
Fix: A display of unicode composing characters by CoreText renderer
2 parents 3af91bd + ee668a4 commit 1496167

4 files changed

Lines changed: 88 additions & 169 deletions

File tree

src/MacVim/MMCoreTextView.m

Lines changed: 39 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#define DRAW_ITALIC 0x10 /* draw italic text */
4444
#define DRAW_CURSOR 0x20
4545
#define DRAW_WIDE 0x80 /* draw wide text */
46+
#define DRAW_COMP 0x100 /* drawing composing char */
4647

4748
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
4849
#define kCTFontOrientationDefault kCTFontDefaultOrientation
@@ -1181,7 +1182,7 @@ - (void)batchDrawData:(NSData *)data
11811182
// 2 - full ligatures including rare
11821183
// 1 - basic ligatures
11831184
// 0 - no ligatures
1184-
[NSNumber numberWithInteger:(useLigatures ? 1 : 0)],
1185+
[NSNumber numberWithBool:useLigatures],
11851186
kCTLigatureAttributeName,
11861187
nil
11871188
];
@@ -1192,7 +1193,7 @@ - (void)batchDrawData:(NSData *)data
11921193

11931194
static UniCharCount
11941195
fetchGlyphsAndAdvances(const CTLineRef line, CGGlyph *glyphs, CGSize *advances,
1195-
UniCharCount length)
1196+
CGPoint *positions, UniCharCount length)
11961197
{
11971198
NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line);
11981199

@@ -1211,6 +1212,8 @@ - (void)batchDrawData:(NSData *)data
12111212
CTRunGetGlyphs(run, range, &glyphs[offset]);
12121213
if (advances != NULL)
12131214
CTRunGetAdvances(run, range, &advances[offset]);
1215+
if (positions != NULL)
1216+
CTRunGetPositions(run, range, &positions[offset]);
12141217

12151218
offset += count;
12161219
if (offset >= length)
@@ -1237,97 +1240,41 @@ - (void)batchDrawData:(NSData *)data
12371240
}
12381241

12391242
static UniCharCount
1240-
ligatureGlyphsForChars(const unichar *chars, CGGlyph *glyphs,
1241-
CGPoint *positions, UniCharCount length, CTFontRef font)
1243+
composeGlyphsForChars(const unichar *chars, CGGlyph *glyphs,
1244+
CGPoint *positions, UniCharCount length, CTFontRef font,
1245+
BOOL isComposing, BOOL useLigatures)
12421246
{
1243-
// CoreText has no simple wait of retrieving a ligature for a set of
1244-
// UniChars. The way proposed on the CoreText ML is to convert the text to
1245-
// an attributed string, create a CTLine from it and retrieve the Glyphs
1246-
// from the CTRuns in it.
1247-
CGGlyph refGlyphs[length];
1248-
CGPoint refPositions[length];
1249-
1250-
memcpy(refGlyphs, glyphs, sizeof(CGGlyph) * length);
1251-
memcpy(refPositions, positions, sizeof(CGSize) * length);
1252-
12531247
memset(glyphs, 0, sizeof(CGGlyph) * length);
12541248

12551249
NSString *plainText = [NSString stringWithCharacters:chars length:length];
1256-
CFAttributedStringRef ligatureText = attributedStringForString(plainText,
1257-
font, YES);
1258-
1259-
CTLineRef ligature = CTLineCreateWithAttributedString(ligatureText);
1260-
1261-
CGSize ligatureRanges[length], regularRanges[length];
1262-
1263-
// get the (ligature)glyphs and advances for the new text
1264-
UniCharCount offset = fetchGlyphsAndAdvances(ligature, glyphs,
1265-
ligatureRanges, length);
1266-
// fetch the advances for the base text
1267-
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, refGlyphs,
1268-
regularRanges, length);
1269-
1270-
CFRelease(ligatureText);
1271-
CFRelease(ligature);
1272-
1273-
// tricky part: compare both advance ranges and chomp positions which are
1274-
// covered by a single ligature while keeping glyphs not in the ligature
1275-
// font.
1276-
#define fequal(a, b) (fabs((a) - (b)) < FLT_EPSILON)
1277-
#define fless(a, b)((a) - (b) < FLT_EPSILON) && (fabs((a) - (b)) > FLT_EPSILON)
1278-
1279-
CFIndex skip = 0;
1280-
CFIndex i;
1281-
for (i = 0; i < offset && skip + i < length; ++i) {
1282-
memcpy(&positions[i], &refPositions[skip + i], sizeof(CGSize));
1283-
1284-
if (fequal(ligatureRanges[i].width, regularRanges[skip + i].width)) {
1285-
// [mostly] same width
1286-
continue;
1287-
} else if (fless(ligatureRanges[i].width,
1288-
regularRanges[skip + i].width)) {
1289-
// original is wider than our result - use the original glyph
1290-
// FIXME: this is currently the only way to detect emoji (except
1291-
// for 'glyph[i] == 5')
1292-
glyphs[i] = refGlyphs[skip + i];
1293-
continue;
1294-
}
1250+
CFAttributedStringRef composedText = attributedStringForString(plainText,
1251+
font,
1252+
useLigatures);
12951253

1296-
// no, that's a ligature
1297-
// count how many positions this glyph would take up in the base text
1298-
CFIndex j = 0;
1299-
float width = ceil(regularRanges[skip + i].width);
1254+
CTLineRef line = CTLineCreateWithAttributedString(composedText);
13001255

1301-
while ((int)width < (int)ligatureRanges[i].width
1302-
&& skip + i + j < length) {
1303-
width += ceil(regularRanges[++j + skip + i].width);
1304-
}
1305-
skip += j;
1306-
}
1256+
// get the (composing)glyphs and advances for the new text
1257+
UniCharCount offset = fetchGlyphsAndAdvances(line, glyphs, NULL,
1258+
isComposing ? positions : NULL,
1259+
length);
13071260

1308-
#undef fless
1309-
#undef fequal
1261+
CFRelease(composedText);
1262+
CFRelease(line);
13101263

1311-
// as ligatures combine characters it is required to adjust the
1264+
// as ligatures composing characters it is required to adjust the
13121265
// original length value
13131266
return offset;
13141267
}
13151268

13161269
static void
13171270
recurseDraw(const unichar *chars, CGGlyph *glyphs, CGPoint *positions,
13181271
UniCharCount length, CGContextRef context, CTFontRef fontRef,
1319-
NSMutableArray *fontCache, BOOL useLigatures)
1272+
NSMutableArray *fontCache, BOOL isComposing, BOOL useLigatures)
13201273
{
13211274
if (CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, length)) {
13221275
// All chars were mapped to glyphs, so draw all at once and return.
1323-
if (useLigatures) {
1324-
length = ligatureGlyphsForChars(chars, glyphs, positions, length,
1325-
fontRef);
1326-
} else {
1327-
// only fixup surrogate pairs if we're not using ligatures
1328-
length = gatherGlyphs(glyphs, length);
1329-
}
1330-
1276+
length = composeGlyphsForChars(chars, glyphs, positions, length,
1277+
fontRef, isComposing, useLigatures);
13311278
CTFontDrawGlyphs(fontRef, glyphs, positions, length, context);
13321279
return;
13331280
}
@@ -1388,7 +1335,7 @@ - (void)batchDrawData:(NSData *)data
13881335
return;
13891336

13901337
recurseDraw(chars, glyphs, positions, attemptedCount, context,
1391-
fallback, fontCache, useLigatures);
1338+
fallback, fontCache, isComposing, useLigatures);
13921339

13931340
// If only a portion of the invalid range was rendered above,
13941341
// the remaining range needs to be attempted by subsequent
@@ -1422,8 +1369,10 @@ - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
14221369
float x = col*cellSize.width + insetSize.width;
14231370
float y = frame.size.height - insetSize.height - (1+row)*cellSize.height;
14241371
float w = cellSize.width;
1372+
BOOL wide = flags & DRAW_WIDE ? YES : NO;
1373+
BOOL composing = flags & DRAW_COMP ? YES : NO;
14251374

1426-
if (flags & DRAW_WIDE) {
1375+
if (wide) {
14271376
// NOTE: It is assumed that either all characters in 'chars' are wide
14281377
// or all are normal width.
14291378
w *= 2;
@@ -1489,7 +1438,7 @@ - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
14891438
if (length > maxlen) {
14901439
if (glyphs) free(glyphs);
14911440
if (positions) free(positions);
1492-
glyphs = (CGGlyph*)malloc(length*sizeof(CGGlyph));
1441+
glyphs = (CGGlyph*)calloc(length, sizeof(CGGlyph));
14931442
positions = (CGPoint*)calloc(length, sizeof(CGPoint));
14941443
maxlen = length;
14951444
}
@@ -1500,15 +1449,17 @@ - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
15001449
CGContextSetFontSize(context, [font pointSize]);
15011450

15021451
// Calculate position of each glyph relative to (x,y).
1503-
NSUInteger i;
1504-
float xrel = 0;
1505-
for (i = 0; i < length; ++i) {
1506-
positions[i].x = xrel;
1507-
xrel += w;
1452+
if (!composing) {
1453+
float xrel = 0;
1454+
for (unsigned i = 0; i < length; ++i) {
1455+
positions[i].x = xrel;
1456+
positions[i].y = .0;
1457+
xrel += w;
1458+
}
15081459
}
15091460

1510-
CTFontRef fontRef = (CTFontRef)(flags & DRAW_WIDE ? [fontWide retain]
1511-
: [font retain]);
1461+
CTFontRef fontRef = (CTFontRef)(wide ? [fontWide retain]
1462+
: [font retain]);
15121463
unsigned traits = 0;
15131464
if (flags & DRAW_ITALIC)
15141465
traits |= kCTFontItalicTrait;
@@ -1517,15 +1468,16 @@ - (void)drawString:(const UniChar *)chars length:(UniCharCount)length
15171468

15181469
if (traits) {
15191470
CTFontRef fr = CTFontCreateCopyWithSymbolicTraits(fontRef, 0.0, NULL,
1520-
traits, traits);
1471+
traits, traits);
15211472
if (fr) {
15221473
CFRelease(fontRef);
15231474
fontRef = fr;
15241475
}
15251476
}
15261477

15271478
CGContextSetTextPosition(context, x, y+fontDescent);
1528-
recurseDraw(chars, glyphs, positions, length, context, fontRef, fontCache, ligatures);
1479+
recurseDraw(chars, glyphs, positions, length, context, fontRef, fontCache,
1480+
composing, ligatures);
15291481

15301482
CFRelease(fontRef);
15311483
if (thinStrokes)

src/MacVim/gui_macvim.m

Lines changed: 42 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -477,87 +477,79 @@
477477
}
478478

479479

480-
void
481-
gui_mch_draw_string(int row, int col, char_u *s, int len, int cells, int flags)
482-
{
483-
#ifdef FEAT_MBYTE
484-
char_u *conv_str = NULL;
485-
if (output_conv.vc_type != CONV_NONE) {
486-
conv_str = string_convert(&output_conv, s, &len);
487-
if (conv_str)
488-
s = conv_str;
489-
}
490-
#endif
491-
492-
[[MMBackend sharedInstance] drawString:s
493-
length:len
494-
row:row
495-
column:col
496-
cells:cells
497-
flags:flags];
498-
#ifdef FEAT_MBYTE
499-
if (conv_str)
500-
vim_free(conv_str);
501-
#endif
502-
}
503-
504-
505480
int
506481
gui_macvim_draw_string(int row, int col, char_u *s, int len, int flags)
507482
{
508-
int c, cn, cl, i;
483+
MMBackend *backend = [MMBackend sharedInstance];
484+
#ifdef FEAT_MBYTE
485+
int c, cw, cl, ccl;
509486
int start = 0;
510487
int endcol = col;
511488
int startcol = col;
512489
BOOL wide = NO;
513-
MMBackend *backend = [MMBackend sharedInstance];
514-
#ifdef FEAT_MBYTE
515490
char_u *conv_str = NULL;
516491

517492
if (output_conv.vc_type != CONV_NONE) {
518493
conv_str = string_convert(&output_conv, s, &len);
519494
if (conv_str)
520495
s = conv_str;
521496
}
522-
#endif
523497

524498
// Loop over each character and output text when it changes from normal to
525499
// wide and vice versa.
526-
for (i = 0; i < len; i += cl) {
500+
for (int i = 0; i < len; i += cl) {
527501
c = utf_ptr2char(s + i);
528-
cn = utf_char2cells(c);
502+
cw = utf_char2cells(c);
529503
cl = utf_ptr2len(s + i);
530-
if (0 == cl)
504+
ccl = utfc_ptr2len(s + i);
505+
if (cl == 0)
531506
len = i; // len must be wrong (shouldn't happen)
532507

533-
if (!utf_iscomposing(c)) {
534-
if ((cn > 1 && !wide) || (cn <= 1 && wide)) {
535-
// Changed from normal to wide or vice versa.
536-
[backend drawString:(s+start) length:i-start
537-
row:row column:startcol
538-
cells:endcol-startcol
539-
flags:(wide ? flags|DRAW_WIDE : flags)];
508+
if (i > start && (cl < ccl || (cw > 1 && !wide) || (cw <= 1 && wide))) {
509+
// Changed from normal to wide or vice versa.
510+
[backend drawString:(s+start) length:i-start
511+
row:row column:startcol
512+
cells:endcol-startcol
513+
flags:flags|(wide ? DRAW_WIDE : 0)];
540514

541-
start = i;
542-
startcol = endcol;
543-
}
515+
start = i;
516+
startcol = endcol;
517+
}
518+
519+
wide = cw > 1;
520+
endcol += cw;
544521

545-
wide = cn > 1;
546-
endcol += cn;
522+
if (cl < ccl) {
523+
// Changed from normal to wide or vice versa.
524+
[backend drawString:(s+start) length:ccl
525+
row:row column:startcol
526+
cells:endcol-startcol
527+
flags:flags|DRAW_COMP|(wide ? DRAW_WIDE : 0)];
528+
529+
start = i + ccl;
530+
startcol = endcol;
531+
cl = ccl;
547532
}
548533
}
549534

550-
// Output remaining characters.
551-
[backend drawString:(s+start) length:len-start
552-
row:row column:startcol cells:endcol-startcol
553-
flags:(wide ? flags|DRAW_WIDE : flags)];
535+
if (len > start) {
536+
// Output remaining characters.
537+
[backend drawString:(s+start) length:len-start
538+
row:row column:startcol
539+
cells:endcol-startcol
540+
flags:flags|(wide ? DRAW_WIDE : 0)];
541+
}
554542

555-
#ifdef FEAT_MBYTE
556543
if (conv_str)
557544
vim_free(conv_str);
558-
#endif
559545

560546
return endcol - col;
547+
#else
548+
[backend drawString:s length:len
549+
row:row column:col
550+
cells:len flags:flags];
551+
return len;
552+
#endif
561553
}
562554

563555

0 commit comments

Comments
 (0)