What is the simplest accurate means of getting the bounds for a given line of text, using Core Text?
I have tried multiple techniques, but am getting inconsistent results.
I have been trying to figure out how to find the actual bounds of what is for now, a single line of text.
The Core Text documentation is at best incomplete on what the frame, line, and run level routines return, stating that they return the "actual" bounds, when they in fact disagree (at least on the frame level), and don't return the actual bounds for the specific string, but appear to return a size larger than the font size would predict.
However, when I get the bounds on individual glyphs, I do see differences between different characters, suggesting to me that this is the only accurate method (iterating through all glyphs, finding the effective union of them all).
I have written some tests in a playground file, am I missing something obvious? My final goal (that partly / mostly works) is to have a custom CALayer that takes an attributed string, and scales it to fill the entire bounds. This then plays nicely with auto layout, in that I can create a label that scales with my layout, as desired.
My playground snippet is below, note the values generated on the right column.
// Playground - noun: a place where people can play
import UIKit
import CoreText
import QuartzCore
print("start")
let f = UIFont.systemFontOfSize(12)
let t = NSAttributedString(string: "A", attributes: [NSFontAttributeName:f])
let line = CTLineCreateWithAttributedString(t)
var ascent:CGFloat = 0, descent:CGFloat = 0, leading:CGFloat = 0
CTLineGetTypographicBounds(line, &ascent, &descent, &leading)
ascent
descent
ascent + descent
leading
let boundingSize = CGSizeMake(CGFloat.max, CGFloat.max)
let fs = CTFramesetterCreateWithAttributedString(t)
let frameSize = CTFramesetterSuggestFrameSizeWithConstraints(fs, CFRangeMake(0, t.length),
nil, boundingSize, nil)
frameSize
let lineWidth = frameSize.width
let lineHeight = frameSize.height
// for each run
let runs = CTLineGetGlyphRuns(line) as [AnyObject] as! [CTRun]
for (runIndex, run) in runs.enumerate() {
// get font
let attributes = CTRunGetAttributes(run) as NSDictionary as! [String:AnyObject]
let runFont:CTFont = attributes[kCTFontAttributeName as String] as! CTFont
// get glyphs for current run
let glyphCount = CTRunGetGlyphCount(run)
let range = CFRangeMake(0, glyphCount)
var glyphs = Array<CGGlyph>(count: glyphCount, repeatedValue: CGGlyph())
var positions = [CGPoint](count: glyphCount, repeatedValue: CGPointZero)
CTRunGetGlyphs(run, range, &glyphs)
CTRunGetPositions(run, range, &positions)
let runWidth = CTRunGetTypographicBounds(run, range, &ascent, &descent, &leading)
runWidth
ascent
descent
leading
// for each glyph in run
for (glyphInRun, var glyph) in glyphs.enumerate() {
let glyphFrame = withUnsafePointer(&glyph) { pointer -> CGRect in
return CTFontGetBoundingRectsForGlyphs(runFont, .Default, pointer, nil, 1) }
glyphFrame.size
}
}
print("done")
Thanks!
You're using CTRunGetTypographicBounds
. Typographic bounds means the bounds used for typesetting, so the ascent, descent, and advance are based on the font metrics, not on the actual appearance of the glyphs in the run.
It sounds like you want CTRunGetImageBounds
, which returns a CGRect
that “tightly encloses the paths of the run's glyphs”. Note that you can pass nil
for the context argument (in my testing).
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