From 91152d1fcaaa3c315dded1f2cb6e0d6d9b23121f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81=20Ko=CC=88cher?= Date: Sat, 7 Nov 2015 23:25:35 +0100 Subject: [PATCH] Fix spacing issues on ligatures as seen on #36. Do a proper analysis of the ligatures (if any) and try to avoid double character draws / moving spaces etc. --- src/MacVim/MMCoreTextView.m | 118 +++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 34 deletions(-) diff --git a/src/MacVim/MMCoreTextView.m b/src/MacVim/MMCoreTextView.m index e36ebb88c0..de996e788b 100644 --- a/src/MacVim/MMCoreTextView.m +++ b/src/MacVim/MMCoreTextView.m @@ -1039,6 +1039,52 @@ lookupFont(NSMutableArray *fontCache, const unichar *chars, UniCharCount count, return newFontRef; } + static CFAttributedStringRef +attributedStringForString(NSString *string, const CTFontRef font, BOOL useLigatures) +{ + NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys: + (id)font, kCTFontAttributeName, + // 2 - full ligatures including rare + // 0 - no ligatures + [NSNumber numberWithInteger: (useLigatures) ? 2 : 0], kCTLigatureAttributeName, + nil + ]; + + return CFAttributedStringCreate(NULL, string, attrs); +} + + static UniCharCount +fetchGlyphsAndAdvances(const CTLineRef line, CGGlyph *glyphs, CGSize *advances, UniCharCount length) +{ + NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line); + + // get a hold on the actual character widths and glyphs in line + UniCharCount offset = 0; + for (id item in glyphRuns) { + CTRunRef run = (CTRunRef)item; + CFIndex count = CTRunGetGlyphCount(run); + + if(count > 0 && count - offset > length) { + count = length - offset; + } + + CFRange range = CFRangeMake(0, count); + + if( glyphs != NULL ) { + CTRunGetGlyphs(run, range, &glyphs[offset]); + } + if( advances != NULL ) { + CTRunGetAdvances(run, range, &advances[offset]); + } + + offset += count; + if(offset >= length) { + break; + } + } + return offset; +} + static UniCharCount gatherGlyphs(CGGlyph glyphs[], UniCharCount count) { @@ -1062,55 +1108,59 @@ ligatureGlyphsForChars(const unichar *chars, CGGlyph *glyphs, CGPoint *positions * 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. */ - NSString *text = [NSString stringWithCharacters:chars - length:*length]; + NSString *plainText = [NSString stringWithCharacters:chars length:*length]; - NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys: - (id)font, kCTFontAttributeName, - // 2 - full ligatures including rare - [NSNumber numberWithInteger: 2], kCTLigatureAttributeName, - nil - ]; + CFAttributedStringRef regularText = attributedStringForString(plainText, font, NO); + CFAttributedStringRef ligatureText = attributedStringForString(plainText, font, YES); - NSAttributedString *attrText = [[NSAttributedString alloc] initWithString:text - attributes:attrs]; + CGPoint refPositions[*length]; + memcpy(refPositions, positions, sizeof(CGSize) * (*length)); - CGPoint refPos = positions[0]; + CTLineRef regular = CTLineCreateWithAttributedString((CFAttributedStringRef)regularText); + CTLineRef ligature = CTLineCreateWithAttributedString((CFAttributedStringRef)ligatureText); - CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attrText); + CFRelease(regularText); + CFRelease(ligatureText); - UniCharCount offset = 0; - NSArray *glyphRuns = (NSArray*)CTLineGetGlyphRuns(line); + CGSize ligatureRanges[*length], regularRanges[*length]; - for (id item in glyphRuns) { - CTRunRef run = (CTRunRef)item; - CFIndex count = CTRunGetGlyphCount(run); + // get the (ligature)glyphs and advances for the new text + UniCharCount offset = fetchGlyphsAndAdvances(ligature, glyphs, ligatureRanges, length); + // do the same but only for the advances for the base text + fetchGlyphsAndAdvances(regular, NULL, regularRanges, length); - if(count > 0) { - if(count - offset > *length) { - count = (*length) - offset; - } + // tricky part: compare both advance ranges and chomp positions which + // are covered by a single ligature +#define fequal(a, b) (fabs( (a) - (b) ) < FLT_EPSILON) + CFIndex skip = 0; + for( CFIndex 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; } - CFRange range = CFRangeMake(0, count); - CTRunGetGlyphs(run, range, &glyphs[offset]); - CTRunGetPositions(run, range, &positions[offset]); + // 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); - offset += count; - if(offset >= *length) { - // don't copy more glyphs then there is space for - break; + while( (int)width < (int)ligatureRanges[i].width + && skip + i + j < *length ) + { + width += ceil(regularRanges[++j + skip + i].width); } + skip += j; } - // fixup relative positioning - CFIndex i; - for( i = 0; i < offset; ++i ) { - positions[i].x += refPos.x; - positions[i].y += refPos.y; - } +#undef fequal + // as ligatures combine characters it is required to adjust the // original length value *length = offset; + + CFRelease(regular); + CFRelease(ligature); } static void