I'm using CoreText to render my text in a CGPath
that is typically a rectangle. The width of the path is 230 pixels. But when I'm rendering my text, CTLineGetTypographicBounds
returns widths of 292 pixels instead of the maximum 230.
It all worked fine until I started to use CTRunDelegateCallbacks
delegates. I use it because I render images in-place. The callbacks just return the correct width and height of the messages. I don't see what's wrong. It only happens when I add a lot of smileys to the NSAttributedString
. I expect that it sets it correctly acros multiple lines but it doesn't.
static void _deallocCallback (void* ref)
{
[(id)ref release];
}
static CGFloat _ascentCallback (void *ref)
{
UIImage* smiley = [(NSDictionary*)ref objectForKey:@"smiley"];
return smiley.size.height; // returns 16
}
static CGFloat _widthCallback (void* ref)
{
UIImage* smiley = [(NSDictionary*)ref objectForKey:@"smiley"];
return smiley.size.width; // returns 16
}
- (void)renderText:(NSString*)text inRect:(CGRect)rect
{
CGMutablePathRef path = createMyPath();
NSAttributedString* attrString = [self _createAttributedString:text];
// Create the frame setter and it's frame
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, NULL);
// Get the height of the lines
CFArrayRef lines = CTFrameGetLines(frame);
CFIndex lineCount = CFArrayGetCount(lines);
CGFloat ascent, descent, leading;
// Get the origins of each line
CGPoint origins[lineCount];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
// Loop through every line
for (CFIndex i = 0; i < lineCount; i++)
{
// Get the typographic bounds of this line
CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
textWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
// __this returns values wider than rect in path__
}
}
- (NSAttributedString*)_createAttributedString:(NSString*)inString
{
// Create our default font attribute
NSDictionary* fontAttribute = [NSDictionary dictionaryWithObject:(id)_font forKey:(NSString*)kCTFontAttributeName];
// Create the attributed string
NSMutableAttributedString* attributedString = [[NSMutableAttributedString alloc] init];
CTRunDelegateCallbacks callbacks;
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = _ascentCallback;
callbacks.getDescent = NULL;
callbacks.getWidth = _widthCallback;
callbacks.dealloc = _deallocCallback;
UIImage* smiley = getSmiley(@"sad.png");
// Do this multiple times to add a long list of smileys
NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys: smiley, @"smiley", nil];
CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, attributes);
NSDictionary* attributeDelegate = [NSDictionary dictionaryWithObjectsAndKeys:(id)delegate, (NSString *)kCTRunDelegateAttributeName, smiley, @"smiley", nil];
[attributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:@" " attributes:attributeDelegate] autorelease]];
[attributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:@" blablabla " attributes:fontAttribute] autorelease]];
return attributedString;
}
Thanks, Nicolas
I do not consider this a beautiful solution but it seems to fix your problem. The following line in your code is responsible for the text not rendering/wrapping correctly:
[attributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:@" " attributes:attributeDelegate] autorelease]];
It appears CoreText ignores these spaces that are added to attributedString
.
You can fix this by simply adding a 'dummy' character that is in fact rendered but overlapped by the image and thus invisible. This character will be seen as a new 'word' by Coretext and should render/wrap just fine.
For example:
[attributedString appendAttributedString:[[[NSAttributedString alloc] initWithString:@"•" attributes:attributeDelegate] autorelease]];