I am using Core Text to draw some text. I would like to get the various run bounds, but when I call CTRunGetImageBounds
, the rect that is returned is the correct size, but in the wrong location. Specifically, the line origin is at the end of the text as a whole.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
self.transform = CGAffineTransformMakeScale(1.0, -1.0);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
[[UIColor whiteColor] set];
CGContextFillRect(context, self.bounds);
NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithString:@"Blue should be underlined."];
NSRange blueRange = NSMakeRange(0, 4);
[attrString beginEditing];
//make all text size 20.0
[attrString addAttribute:(NSString *)kCTFontAttributeName value:(id)CTFontCreateWithName((CFStringRef)@"Helvetica", 20.0, NULL) range:NSMakeRange(0, [attrString length])];
//make the text appear in blue
[attrString addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)[[UIColor blueColor] CGColor] range:blueRange];
//next make the text appear with an underline
[attrString addAttribute:(NSString *)kCTUnderlineStyleAttributeName value:[NSNumber numberWithInt:1] range:blueRange];
[attrString endEditing];
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = CGRectMake(10.0, 10.0, 200.0, 200.0);
[[UIColor redColor] set];
CGContextFillRect(context, bounds);
CGPathAddRect(path, NULL, bounds);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CTFrameDraw(frame, context);
for (id lineObj in (NSArray *)CTFrameGetLines(frame)) {
CTLineRef line = (CTLineRef)lineObj;
for (id runObj in (NSArray *)CTLineGetGlyphRuns(line)) {
CTRunRef run = (CTRunRef)runObj;
CGRect runBounds = CTRunGetImageBounds(run, context, CFRangeMake(0, 0));
NSLog(@"bounds: %@", NSStringFromCGRect(runBounds));
[[UIColor greenColor] set];
CGContextFillRect(context, runBounds);
}
}
CFRelease(framesetter);
CFRelease(frame);
[attrString release];
}
Produces:
Here is what I came up with. This is completely accurate.
CTFrameRef frame = [self _frameWithRect:rect];
NSArray *lines = (NSArray *)CTFrameGetLines(frame);
CGPoint origins[[lines count]];//the origins of each line at the baseline
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
NSUInteger lineIndex = 0;
for (id lineObj in lines) {
CTLineRef line = (CTLineRef)lineObj;
for (id runObj in (NSArray *)CTLineGetGlyphRuns(line)) {
CTRunRef run = (CTRunRef)runObj;
CFRange runRange = CTRunGetStringRange(run);
CGRect runBounds;
CGFloat ascent;//height above the baseline
CGFloat descent;//height below the baseline
runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
runBounds.size.height = ascent + descent;
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
runBounds.origin.x = origins[lineIndex].x + rect.origin.x + xOffset;
runBounds.origin.y = origins[lineIndex].y + rect.origin.y;
runBounds.origin.y -= descent;
//do something with runBounds
}
lineIndex++;
}
Before you can use CTRunGetImageBounds() or CTLineDraw() you need to set the starting text position with a call to CGContextSetTextPosition() before drawing or computing each line. The reason is a CTLine has no idea where to start drawing (or computing) on its own, it uses the last text position, so you need to give it an XY starting point. To get the origins for a frame's array of lines, call CTFrameGetLineOrigins() with an array of points. Then, before drawing or computing each line - set the origin of that line via CGContextSetTextPosition().
NSArray *lines = (NSArray *)CTFrameGetLines(frame);
CGPoint origins[[lines count]]; // the origins of each line at the baseline
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
int i, lineCount;
lineCount = [lines count];
for (i = 0; i < lineCount; i++) {
CTLineRef line = (CTLineRef)[lines objectAtIndex:i];
CGContextSetTextPosition(context, origins[i].x, origins[i].y);
CTLineDraw(line, context);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With