Skip to content

Commit 2696990

Browse files
committed
Merge pull request #122 from Shirk/ligature_fixes
Fix spacing issues on ligatures as seen on #36.
2 parents 76622a7 + 2a3e654 commit 2696990

File tree

3 files changed

+113
-52
lines changed

3 files changed

+113
-52
lines changed

src/MacVim/English.lproj/Preferences.nib/designable.nib

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
-155 Bytes
Binary file not shown.

src/MacVim/MMCoreTextView.m

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,53 @@ - (void)batchDrawData:(NSData *)data
10391039
return newFontRef;
10401040
}
10411041

1042+
static CFAttributedStringRef
1043+
attributedStringForString(NSString *string, const CTFontRef font, BOOL useLigatures)
1044+
{
1045+
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
1046+
(id)font, kCTFontAttributeName,
1047+
// 2 - full ligatures including rare
1048+
// 1 - basic ligatures
1049+
// 0 - no ligatures
1050+
[NSNumber numberWithInteger: (useLigatures) ? 1 : 0], kCTLigatureAttributeName,
1051+
nil
1052+
];
1053+
1054+
return CFAttributedStringCreate(NULL, (CFStringRef)string, (CFDictionaryRef)attrs);
1055+
}
1056+
1057+
static UniCharCount
1058+
fetchGlyphsAndAdvances(const CTLineRef line, CGGlyph *glyphs, CGSize *advances, UniCharCount length)
1059+
{
1060+
NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line);
1061+
1062+
// get a hold on the actual character widths and glyphs in line
1063+
UniCharCount offset = 0;
1064+
for (id item in glyphRuns) {
1065+
CTRunRef run = (CTRunRef)item;
1066+
CFIndex count = CTRunGetGlyphCount(run);
1067+
1068+
if(count > 0 && count - offset > length) {
1069+
count = length - offset;
1070+
}
1071+
1072+
CFRange range = CFRangeMake(0, count);
1073+
1074+
if( glyphs != NULL ) {
1075+
CTRunGetGlyphs(run, range, &glyphs[offset]);
1076+
}
1077+
if( advances != NULL ) {
1078+
CTRunGetAdvances(run, range, &advances[offset]);
1079+
}
1080+
1081+
offset += count;
1082+
if(offset >= length) {
1083+
break;
1084+
}
1085+
}
1086+
return offset;
1087+
}
1088+
10421089
static UniCharCount
10431090
gatherGlyphs(CGGlyph glyphs[], UniCharCount count)
10441091
{
@@ -1062,52 +1109,65 @@ - (void)batchDrawData:(NSData *)data
10621109
* The way proposed on the CoreText ML is to convert the text to an attributed
10631110
* string, create a CTLine from it and retrieve the Glyphs from the CTRuns in it.
10641111
*/
1065-
NSString *text = [NSString stringWithCharacters:chars
1066-
length:*length];
1112+
CGGlyph refGlyphs[*length];
1113+
CGPoint refPositions[*length];
10671114

1068-
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
1069-
(id)font, kCTFontAttributeName,
1070-
// 2 - full ligatures including rare
1071-
[NSNumber numberWithInteger: 2], kCTLigatureAttributeName,
1072-
nil
1073-
];
1115+
memcpy(refGlyphs, glyphs, sizeof(CGGlyph) * (*length));
1116+
memcpy(refPositions, positions, sizeof(CGSize) * (*length));
10741117

1075-
NSAttributedString *attrText = [[NSAttributedString alloc] initWithString:text
1076-
attributes:attrs];
1118+
memset(glyphs, 0, sizeof(CGGlyph) * (*length));
10771119

1078-
CGPoint refPos = positions[0];
1120+
NSString *plainText = [NSString stringWithCharacters:chars length:*length];
1121+
CFAttributedStringRef ligatureText = attributedStringForString(plainText, font, YES);
10791122

1080-
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attrText);
1123+
CTLineRef ligature = CTLineCreateWithAttributedString(ligatureText);
10811124

1082-
UniCharCount offset = 0;
1083-
NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line);
1125+
CGSize ligatureRanges[*length], regularRanges[*length];
10841126

1085-
for (id item in glyphRuns) {
1086-
CTRunRef run = (CTRunRef)item;
1087-
CFIndex count = CTRunGetGlyphCount(run);
1127+
// get the (ligature)glyphs and advances for the new text
1128+
UniCharCount offset = fetchGlyphsAndAdvances(ligature, glyphs, ligatureRanges, length);
1129+
// fetch the advances for the base text
1130+
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, refGlyphs, regularRanges, *length);
10881131

1089-
if(count > 0) {
1090-
if(count - offset > *length) {
1091-
count = (*length) - offset;
1092-
}
1132+
CFRelease(ligatureText);
1133+
CFRelease(ligature);
1134+
1135+
// tricky part: compare both advance ranges and chomp positions which
1136+
// are covered by a single ligature while keeping glyphs not in the ligature font.
1137+
#define fequal(a, b) (fabs( (a) - (b) ) < FLT_EPSILON)
1138+
#define fless(a, b)((a) - (b) < FLT_EPSILON) && (fabs( (a) - (b) ) > FLT_EPSILON)
1139+
1140+
CFIndex skip = 0;
1141+
for( CFIndex i = 0; i < offset && skip + i < *length; ++i ) {
1142+
memcpy(&positions[i], &refPositions[skip + i], sizeof(CGSize));
1143+
1144+
if( fequal(ligatureRanges[i].width, regularRanges[skip + i].width) ) {
1145+
// [mostly] same width
1146+
continue;
1147+
1148+
} else if( fless(ligatureRanges[i].width, regularRanges[skip + i].width) ) {
1149+
// original is wider than our result - use the original glyph
1150+
// FIXME: this is currently the only way to detect emoji (except for 'glyph[i] == 5')
1151+
glyphs[i] = refGlyphs[skip + i];
1152+
continue;
10931153
}
10941154

1095-
CFRange range = CFRangeMake(0, count);
1096-
CTRunGetGlyphs(run, range, &glyphs[offset]);
1097-
CTRunGetPositions(run, range, &positions[offset]);
1155+
// no, that's a ligature
1156+
// count how many positions this glyph would take up in the base text
1157+
CFIndex j = 0;
1158+
float width = ceil(regularRanges[skip + i].width);
10981159

1099-
offset += count;
1100-
if(offset >= *length) {
1101-
// don't copy more glyphs then there is space for
1102-
break;
1160+
while( (int)width < (int)ligatureRanges[i].width
1161+
&& skip + i + j < *length )
1162+
{
1163+
width += ceil(regularRanges[++j + skip + i].width);
11031164
}
1165+
skip += j;
11041166
}
1105-
// fixup relative positioning
1106-
CFIndex i;
1107-
for( i = 0; i < offset; ++i ) {
1108-
positions[i].x += refPos.x;
1109-
positions[i].y += refPos.y;
1110-
}
1167+
1168+
#undef fless
1169+
#undef fequal
1170+
11111171
// as ligatures combine characters it is required to adjust the
11121172
// original length value
11131173
*length = offset;
@@ -1120,10 +1180,11 @@ - (void)batchDrawData:(NSData *)data
11201180
{
11211181
if (CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, length)) {
11221182
// All chars were mapped to glyphs, so draw all at once and return.
1123-
length = gatherGlyphs(glyphs, length);
11241183
if (useLigatures) {
1125-
memset(glyphs, 0, sizeof(CGGlyph) * length);
1126-
ligatureGlyphsForChars(chars, glyphs, positions, &length, fontRef);
1184+
ligatureGlyphsForChars(chars, glyphs, positions, &length, fontRef);
1185+
} else {
1186+
// only fixup surrogate pairs if we're not using ligatures
1187+
length = gatherGlyphs(glyphs, length);
11271188
}
11281189

11291190
CTFontDrawGlyphs(fontRef, glyphs, positions, length, context);

0 commit comments

Comments
 (0)