Amar Kulo Amar Kulo - 15 days ago 11
iOS Question

UILabel italic font clipping

I'm trying to solve this problem as in the application I'm working on I have a lot of fonts, so size of label is being calculated dynamically when user changes font.

The problem that I have is that UILabel is being clipped at the end if font is Italic like on picture bellow:

UILabel clipping example

This is what I have tried so far:


  • calculating of width with the help of CoreText and
    CGSize CTFramesetterSuggestFrameSizeWithConstraints ( CTFramesetterRef framesetter, CFRange stringRange, CFDictionaryRef frameAttributes, CGSize constraints, CFRange *fitRange );

  • calculating of width with the help of NSAttributedString and
    - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context

  • calculating of width with the help of NSString and
    - (CGSize)sizeWithAttributes:(NSDictionary<NSString *,id> *)attrs

  • using temp UITextView and
    sizeThatFits´ and
    fitToSize`



As there is a lot of fonts in application I need to set width of label dynamically, so subclassing of UILabel and adding few more points on
drawFrameInRect
is not working.

Here is sample code on Github.

Any help/advice is appreciated.

Answer

Even I don't have the time to test it, I'm pretty sure that the problem is that the label size is calculated from the advances. The advance is the amount of movement from one character's base point to the next ones. Typically for an italic font the advance can be smaller than the bounds. Therefore adding the advances will cut the end of the layout.

I.e. Baskerville-Italic H:

(lldb) p bounds[0].size
(CGSize) $6 = (width=33.53515625, height=26.484375)
(lldb) p advances[0]
(CGSize) $7 = (width=30, height=0)

So I think that you have to add the difference between advance and bounding box of the last character, if the text is layout in an ideal way (no compression and so on).

To get both values, you have to call some CT functions:

// What you probably have
CTFontRef font = …;
CFStringRef string = …;
CFAAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);

// Get the character and glyph
CFIndex count = CFStringGetLength(string); // The length of the string
UniChar character;
CFStringGetCharacters( string, CFMakeRange( count-1, 1), &character );
CGGlyph glyph;
CTStringGetGlyphsForCharacters( font, &character, &glyph,1  );

// Get the advance
CGSize advance;
CTFontGetAdvancesForGlyphs( font, 0, &glyph, &advance, 1);

// Get the bounds
CGRect bounds;
CTFontGetAdvancesForGlyphs( font, 0, &glyph, &bounds, 1);

Typed in Safari.

Addition: You have the same problem at the left side: The advance to the uppercase L is 0 (the first character), but the bounding box has a negative x. Therefore the left side is clipped. Add space there, too.. (What is easier by far.)