Search code examples
iphoneobjective-ccore-text

iPhone sdk: NSMutableAttributedString buggy behavior when using CTFramesetterCreateFrame


I'm trying to draw text with some part of it in different color

First issue it that when using the following code:

NSMutableAttributedString *test1 = [[NSMutableAttributedString alloc] initWithString:@"fi"];

// Should color only the first character "f"
[test1 addAttribute:(id)kCTForegroundColorAttributeName value:(id)[[UIColor redColor] CGColor] range:NSMakeRange(0, 1)];

CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)test1);

CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));

CTFrameRef textFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, test1.string.length), path, NULL);

And then drawing the frame:

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);

CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);

// Flip the coordinate system
CGContextScaleCTM(context, 1.0, -1.0);

CTFrameDraw(textFrame, context);

CGContextRestoreGState(context);

For some reason it shows both "f" and "i" colored in red. When I change the text to "f1" , "f!", or anything else it works OK. Even when I use "afi" and range as (1,1) it colors both "f" and "i" as if it treats it as a single character.

Second issue, when drawing inside a frame with width which is less that the width of the text:

NSMutableAttributedString *test2 = [[NSMutableAttributedString alloc] initWithString:@"aaaaaaaaaaaaaaaaaaaa"];

CTLineBreakMode lineBreakMode;
lineBreakMode = kCTLineBreakByTruncatingTail;

// Apply selected truncation
CTParagraphStyleSetting paragraphStyleSetting = {kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(&paragraphStyleSetting, 1);
[test1 addAttribute:(NSString *)kCTParagraphStyleAttributeName value:(__bridge id)paragraphStyle range:NSMakeRange(0, test2.string.length)];
[test1 addAttribute:(id)kCTForegroundColorAttributeName value:(id)[[UIColor redColor] CGColor] range:NSMakeRange(7, 16)];

CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)test2);

CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));

CTFrameRef textFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, test1.string.length), path, NULL);

There are total 20 characters, the ones at indexes 7 to 16 appear colored in red (as they should), CoreText adds a truncation mark as I requested but the problem is that it colored in red too and I want it to remain black (as the remaining truncated characters color is black).

I did some study and it seems that when increasing the frame size by some (random/font specific?) value in the command:

CGPathAddRect(path, NULL, CGRectMake(0, 0, self.frame.size.width + 10.0f, self.frame.size.height));

It keeps the truncation mark black, but because of the extra space sometimes the truncation mark is clipped


Solution

  • This sounds like an issue with ligatures. A good font has special characters for some combinations of characters that look better than the individual characters. For example, in a lot of fonts To doesn't look good if rendered individually since there's a lot of space between the vertical bar of T and the start of the o. So ligatures are provided that have a more appealing spacing. Sometimes the characters even get connect, your fi is a good example: some fonts provide a ligature that makes the top of the f double as the dot for the i.

    So you may want to turn the ligatures off but setting the attribute kCTLigatureAttributeName to 0.