Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSString boundingRectWithSize returning unnecessarily tall height

Tags:

When using [NSString boundingRectWithSize:options:attributes] the size of the rect that is returned is taller than I would expect for certain strings. The height returned appears to represent the maximum possible height of a string with the given attributes, rather than the height of the string itself.

Assuming the same attributes and options, the height returned for the string "cars" is the same height returned for the string "ÉTAS-UNIS" (note the accent on the E).

I would have expected boundingRectWithSize to only consider the characters in the given string, which in my opinion would have it return a shorter height for the string "cars".

In the attached screenshots, I've filled the rect returned from boundingRectWithSize and outlined in red what I would have assumed the bounding rect should have been. The width of the rect is pretty much as I would expect but the height is considerably taller than I would have expected. Why is that?

Bounding Rect Example

Sample code:

NSRect boundingRect = NSZeroRect;
NSSize constraintSize = NSMakeSize(CGFLOAT_MAX, 0);

NSString *lowercaseString = @"cars";
NSString *uppercaseString = @"ÉTAS-UNIS";
NSString *capitalizedString = @"Japan";

NSFont *drawingFont = [NSFont fontWithName:@"Georgia" size:24.0];
NSDictionary *attributes = @{NSFontAttributeName : drawingFont, NSForegroundColorAttributeName : [NSColor blackColor]};

boundingRect = [lowercaseString boundingRectWithSize:constraintSize options:0 attributes:attributes];
NSLog(@"Lowercase rect: %@", NSStringFromRect(boundingRect));

boundingRect = [uppercaseString boundingRectWithSize:constraintSize options:0 attributes:attributes];
NSLog(@"Uppercase rect: %@", NSStringFromRect(boundingRect));

boundingRect = [capitalizedString boundingRectWithSize:constraintSize options:0 attributes:attributes];
NSLog(@"Capitalized rect: %@", NSStringFromRect(boundingRect));

Output:

Lowercase rect: {{0, -6}, {43.1953125, 33}}
Uppercase rect: {{0, -6}, {128.44921875, 33}}
Capitalized rect: {{0, -6}, {64.5, 33}}
like image 435
kennyc Avatar asked Jan 22 '14 00:01

kennyc


2 Answers

You might want to use NSStringDrawingUsesDeviceMetrics in the options. From the docs:

NSStringDrawingUsesDeviceMetrics

Use the image glyph bounds (instead of the typographic bounds) when computing layout.

like image 60
omz Avatar answered Sep 19 '22 18:09

omz


@omz still gets credit for making this work for me. His answer made me look around CoreText some more since I assume something like boundingRectWithSize ultimately calls a CoreText function.

In Session 226 from WWDC 2012 there was an entire section devoted to calculating the metrics of a line and, to my surprise, they talked about a new CoreText function called CTLineGetBoundsWithOptions.

As far as I can tell, that method is only documented in CTLine.h and in the CoreText Changes document. It certainly doesn't come up (for me) when doing a normal search in the documentation.

But it appears to work and in my testing it returns the exact same result as boundingRectWithSize for all the fonts installed on my system. Even better, is that it appears to be almost 2x faster than boundingRectWithSize.

As the WWDC video mentions, it's a bit obscure why you'd need to calculate the bounds of a string without taking things like line height into account, but if you do need to do that, then I think this might be the best method to use. As always, YMMV.

Rough sample code:

NSFont *font = [NSFont systemFontOfSize:13.0];
NSDictionary *attributes = @{(__bridge NSString *)kCTFontAttributeName : font};
NSAttributedString *attributeString = [[NSAttributedString alloc] initWithString:self.text attributes:attributes];

CTLineRef line = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)attributeString);
CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds);

CFRelease(line);

return NSRectFromCGRect(bounds);
like image 29
kennyc Avatar answered Sep 21 '22 18:09

kennyc