Thursday, June 16, 2016

IOS: how to calculate the size of text to draw BEFORE to create the context?

Leave a Comment

I need to create an image containing one line of text. But the problem, i first need to create the context (CGBitmapContextCreate) with require the width and the height of the image to have the possibility to later calculate the bounds of the text (via CTLineGetImageBounds). but i want the size of the image = the bounds of the text :( how can i do ?

actually i use

CGBitmapContextCreate CTLineGetImageBounds CTLineDraw 

maybe it's possible to call CTLineGetImageBounds without a context ?

Note: i m on delphi, it's not really a problem as i can have access to all the API, i just need the function name

6 Answers

Answers 1

You can calculate the space an NSString will take up based on the font you want to use by doing the following:

NSString *testString = @"A test string"; CGSize boundingSize = self.bounds.size; CGRect labelRect = [testString                     boundingRectWithSize:boundingSize                     options:NSStringDrawingUsesLineFragmentOrigin                     attributes:@{                          NSFontAttributeName : [UIFont systemFontOfSize:14]                                 }                     context:nil]; 

Where bounding size is the maximum size you want the image to be. Then you can use the calculated size to create your image.

Answers 2

There are a couple of ways to calculate a more or less precise bounding box. You can use font metrics, iterate CTRuns or use cocoa's string drawing API for calculation.

Yet, I want to draw your attention to the problem that this is not generally possible. While the results from above algorithms are sufficient in many cases, there are a couple of scenarios where they are utterly wrong:

  • some letters have parts that reach very far out of the metrics defined in the font. Here's an example from Zapfino: a variation of lower case "f". (Green is Text Edit's selection)

Zapfino lower case f variation.

  • There are effects that reach out of the calculated box. Outline and shadow are just examples. Shadows can have a very far distance and huge blur.

enter image description here

  • There might be custom text attributes that your algorithm is not aware of.

  • A text can contain "attachments" (like images, I think emoji are also implemented as attachments). Those have to be taken into account as well.

When I had to solve this problem before, I finally reached the following solution:

  1. Create a transparent bitmap context at maximum size (in this case a full page).
  2. Draw the text at the desired position.
  3. Walk over the bytes that back the context from outside to inside, scanning the first non-transparent pixel along each edge.
  4. Cut the resulting rectangle using CGImageCreateWithImageInRect.

That's utterly slow, but speed was no priority for my use case.

Answers 3

Below code using to calculate the NSString Size based on the font

extension UIFont { func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {     return NSString(string: string).boundingRectWithSize(CGSize(width: width, height: DBL_MAX),         options: NSStringDrawingOptions.UsesLineFragmentOrigin,         attributes: [NSFontAttributeName: self],         context: nil).size } } 

OR Alternatively you could cast it into an NSString

if let ns_str:NSString = str as NSString? {  let sizeOfString = ns_str.boundingRectWithSize(                              CGSizeMake(self.titleLabel.frame.size.width, CGFloat.infinity),                               options: NSStringDrawingOptions.UsesLineFragmentOrigin,                               attributes: [NSFontAttributeName: lbl.font],                               context: nil).size } 

Answers 4

It sounds like you've already done most of the work. You just need to create a temporary context to call CTLineGetImageBounds against. I believe it's ok for that context to be very small (one pixel, for instance, maybe even zero), and of course you can reuse it. Ideally it should have the same attributes (other than size) as your final context, though even that probably won't matter in most cases. It's only required for its metadata.

Answers 5

OK, i found the solution: use CTLineGetImageBounds(aline, null{context}); simply pass null for the context and it's work as expected ;)

Answers 6

This the could you need to write in order to get the size of the text with the font.

let widthOfLabel = 400.0 let size = font?.sizeOfString(self.viewCenter.text!, constrainedToWidth: Double(widthOfLabel)) 

You have to use the below extension of the font in order to get the size of the text with the font.

extension UIFont {     func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {         return NSString(string: string).boundingRectWithSize(CGSize(width: width, height: DBL_MAX),                                                              options: NSStringDrawingOptions.UsesLineFragmentOrigin,                                                              attributes: [NSFontAttributeName: self],                                                              context: nil).size     } } 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment