@@ -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
10431090gatherGlyphs (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