I have a NSAttributedString
, a rect to draw the whole string, I want to get the rect of the last character just like the image below, how to get that using Core Text ?
Specifically using CoreText, here's a function that should work (with a few comments in the code explaining what's going on):
- (CGRect)lastCharacterRectForAttributedString:(NSAttributedString *)attributedString drawingRect:(CGRect)drawingRect
{
// Start by creating a CTFrameRef using the attributed string and rect.
CTFrameRef textFrame = NULL;
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attributedString));
CGPathRef drawingPath = CGPathCreateWithRect(drawingRect, NULL);
textFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attributedString length]), drawingPath, NULL);
CFRelease(framesetter);
CFRelease(drawingPath);
// Line origins can be obtained from the CTFrameRef. Get the final one.
CGPoint finalLineOrigin;
CFArrayRef lines = CTFrameGetLines(textFrame);
if (CFArrayGetCount(lines) == 0) { // Safety check
CFRelease(textFrame);
return CGRectNull;
}
const CFIndex finalLineIdx = CFArrayGetCount(lines) - 1;
CTFrameGetLineOrigins(textFrame, CFRangeMake(finalLineIdx, 1), &finalLineOrigin);
// Get the glyph runs from the final line. Get the last glyph position from the final run.
CGPoint glyphPosition;
CFArrayRef runs = CTLineGetGlyphRuns(CFArrayGetValueAtIndex(lines, finalLineIdx));
if (CFArrayGetCount(runs) == 0) { // Safety check
CFRelease(textFrame);
return CGRectNull;
}
CTRunRef finalRun = CFArrayGetValueAtIndex(runs, CFArrayGetCount(runs) - 1);
if (CTRunGetGlyphCount(finalRun) == 0) { // Safety check
CFRelease(textFrame);
return CGRectNull;
}
const CFIndex lastGlyphIdx = CTRunGetGlyphCount(finalRun) - 1;
CTRunGetPositions(finalRun, CFRangeMake(lastGlyphIdx, 1), &glyphPosition);
// The bounding box of the glyph itself is extracted from the font.
CGRect glyphBounds;
CFDictionaryRef runAttributes = CTRunGetAttributes(finalRun);
CTFontRef font = CFDictionaryGetValue(runAttributes, NSFontAttributeName);
CGGlyph glyph;
CTRunGetGlyphs(finalRun, CFRangeMake(lastGlyphIdx, 1), &glyph);
CTFontGetBoundingRectsForGlyphs(font, kCTFontDefaultOrientation, &glyph, &glyphBounds, 1);
// Option 1 - The rect you've drawn in your question isn't tight to the final character; it looks approximately the height of the line. If that's what you're after:
CGRect lineBounds = CTLineGetBoundsWithOptions(CFArrayGetValueAtIndex(lines, finalLineIdx), 0);
CGRect desiredRect = CGRectMake(
CGRectGetMinX(drawingRect) + finalLineOrigin.x + glyphPosition.x + CGRectGetMinX(glyphBounds),
CGRectGetMinY(drawingRect) + (CGRectGetHeight(drawingRect) - (finalLineOrigin.y + CGRectGetMaxY(lineBounds))),
CGRectGetWidth(glyphBounds),
CGRectGetHeight(lineBounds)
);
// Option 2 - If you want a rect that closely bounds the final character, use this:
/*
CGRect desiredRect = CGRectMake(
CGRectGetMinX(drawingRect) + finalLineOrigin.x + glyphPosition.x + CGRectGetMinX(glyphBounds),
CGRectGetMinY(drawingRect) + (CGRectGetHeight(drawingRect) - (finalLineOrigin.y + glyphPosition.y + CGRectGetMaxY(glyphBounds))),
CGRectGetWidth(glyphBounds),
CGRectGetHeight(glyphBounds)
);
*/
CFRelease(textFrame);
return desiredRect;
}
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