0

Is this possible? Basically, I have a bunch of NSAttributedString objects and corresponding CTLine objects. I want to get the image bounds before the drawRect stage. So at this point, there is nothing to draw into. I will then use these image bounds to decide exactly what I need to create for drawing.

EDIT: Another measurement of the size would probably work just fine. But calling the deceptively named CTLineGetTypographicBounds function only returns the width. If I pass in addresses of ascent and descent floats, they come back as zero.

EDIT: The given answer works great in MacOS. Can anyone do it in iOS?

William Jockusch
  • 26,513
  • 49
  • 182
  • 323

4 Answers4

2

If you are developing for iOS6+. You can use the following method:

CTLineRef line;
// Create the line...

CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds);
// use bounds...

This is gives the same bounds as CTLineGetImageBounds() assuming you have no transforms applied in your context, but does not require the context. For iOS 5 and below, you would need to use the method described by Иван.

CTLineGetTypographicBounds() gives me a different width than this function or image bounds. I am not sure why. And the ascent and descent returned are those of the font and not the characters displayed in the CTLineRef.

Kostub Deshmukh
  • 2,852
  • 2
  • 25
  • 35
  • The width given by CTLineGetTypographicBounds() is the advance width (the distance from the typographic origin of the line to what should be the typographic origin of the next character). This differs from the image bounds because for (virtually all) letters the glyph will either extend beyond the advance width or be narrower than it. – John Colanduoni May 06 '15 at 14:27
2

Yes, you can, but not so easy.

You should, generally use CTLineGetTypographicBounds() which, for me, does return ascent, descent and leading, but a bit messed up - 'ascent' equals the total height (i.e. what should be ascent + descent) and 'descent' is always the maximum descent of the font - no matter if you have descending characters or not.

Other way is to retrieve the CTRun(s) from the line (CTLineGetGlyphRuns), then get the glyphs array (CTRunGetGlyphs or CTRunGetGlyphsPtr) and then using CTFontGetBoundingRectsForGlyphs and CTFontGetAdvancesForGlyphs build up the information you need.

EDIT: I've just found this method: "- (NSRect) boundingRectWithSize:(NSSize)size options:(NSStringDrawingOptions)options" of NSAttributedString which seems to do exactly what is needed.

Hope, this is helpful...

Jonan Gueorguiev
  • 1,146
  • 12
  • 20
  • Hmmmm, I wonder if it could depend on whether you are in iOS or macOS? I'm working in iOS, and they are always zero. Apple has some sample code for Mac where they do return with nonzero values. – William Jockusch Dec 15 '11 at 19:34
2

Bounds returned by CTLineGetTypographicBounds() are not the same as image bounds. As the name, (and Иван's answer) suggests, ascent etc. are defined for the font and won't change based on the string. For example, you would use it if you want to find the correct line height if you have a multiline text, as line height normally should not depend on the exact characters you use.

CTLineGetImageBounds() on the other hand, returns the bounds that exactly fit the image. For example, if you want to draw a box around a single line, this is what you need.

CTLineGetImageBounds() needs a context because there may be text transforms and things like that. If you don't want to worry about that, just use a dummy context. For example:

CTLineRef line;

// create the line...

UIGraphicsBeginImageContext(CGSizeMake(1, 1));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextPosition(context, 0, 0);
CGRect bounds = CTLineGetImageBounds(line, context);
UIGraphicsEndImageContext();

// use bounds...
mohsenr
  • 7,235
  • 3
  • 28
  • 28
0

Another method is to convert the string to glyphs usingCTFontGetGlyphsForCharacters() and then calling CTFontGetBoundingRectsForGlyphs() with the glyph array you get from the first function. The latter function returns "the overall bounding rectangle for the glyph run" so don't worry about having to do processing on the individual bounding rects. If used both these functions successfully in iOS.

If you do this remember the mapping between glyphs and characters is not always one to one, especially when the string has non-English characters.

Merk
  • 1,441
  • 1
  • 15
  • 28