Bounding Rectangle using Core Text

1.4k views Asked by At

Please correct me if I'm wrong.

I tried to work out the exact bounding rectangle of a character using Core Text. But the height I received was always bigger than the actual height of the drawn character on the screen. In this case, the actual height is around 20 but the function just give me 46 no matter what.

Could anyone shed some light on this?

Thanks.

Here is the code

- (void)viewDidLoad{
    [super viewDidLoad];
    NSString *testString = @"A";
    NSAttributedString *textString =  [[NSAttributedString alloc] initWithString:testString attributes:@{
                                                                                                            NSFontAttributeName: [UIFont fontWithName:@"Helvetica" size:40]
                                                                                                            }];
    NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:textString];
    NSLayoutManager *textLayout = [[NSLayoutManager alloc] init];
    // Add layout manager to text storage object
    [textStorage addLayoutManager:textLayout];
    // Create a text container
    NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.view.bounds.size];
    // Add text container to text layout manager
    [textLayout addTextContainer:textContainer];

    NSRange range = NSMakeRange (0, testString.length);

    CGRect boundingBox = [textLayout boundingRectForGlyphRange:range inTextContainer:textContainer];

    //BoundingBox:{{5, 0}, {26.679688, 46}}
    // Instantiate UITextView object using the text container
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(20,20,self.view.bounds.size.width-20,self.view.bounds.size.height-20)
                                      textContainer:textContainer];
    // Add text view to the main view of the view controler

    [self.view addSubview:textView];
}
1

There are 1 answers

0
Mark Essel On

I'm currently working on this for Core Text rendering, and surprised that this type of information isn't supplied directly (for related graphics like fitted backgrounds/outlines)

These are both works in progress from other stackoverflow questions and my own testing to get perfect bounding boxes (tight)

Common font properties

    let leading = floor( CTFontGetLeading(fontCT) + 0.5)
    let ascent = floor( CTFontGetAscent(fontCT) + 0.5)
    let descent = floor( CTFontGetDescent(fontCT) + 0.5)
    var lineHeight = ascent + descent + leading
    var ascenderDelta = CGFloat(0)
    if leading > 0 {
        ascenderDelta = 0
    }
    else {
        ascenderDelta = floor( 0.2 * lineHeight + 0.5 )
    }
    lineHeight = lineHeight + ascenderDelta

For paragraph styles

    var para = NSMutableAttributedString()
    // append attributed strings and set NSMutableParagraphStyle
    /* ... */
    let options : NSStringDrawingOptions = .UsesFontLeading | .UsesLineFragmentOrigin | .UsesDeviceMetrics
    let rect = para.boundingRectWithSize(CGSizeMake(fontBoxWidth,10000), options:  options, context: nil)
    var backgroundBounds = CGRectMake(boundingBox.origin.x + point.x, boundingBox.origin.y + point.y + lineHeight, boundingBox.width, boundingBox.height + ascenderDelta)

For CTFrames

    let lines = CTFrameGetLines(frame) as NSArray
    let numLines = CFArrayGetCount(lines)

    for var index = 0; index < numLines; index++ {
        var ascent = CGFloat(0),
        descent = CGFloat(0),
        leading = CGFloat(0),
        width = CGFloat(0)
        let line = lines[index] as! CTLine
        width = CGFloat(CTLineGetTypographicBounds(line, &ascent,  &descent, &leading))
        // adjust with common font property code
        var  lineOrigin : CGPoint = CGPointMake(0,0)
        CTFrameGetLineOrigins(frame, CFRangeMake(index, 1), &lineOrigin)
        let bounds = CGRectMake(point.x + lineOrigin.x, point.y + lineOrigin.y - descent, width, ascent + descent)