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.
This commit is contained in:
René Köcher
2015-11-07 23:25:35 +01:00
parent e0c895dadf
commit 91152d1fca
+84 -34
View File
@@ -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