Bounding box of character with diacritics using CoreText

cocoa

Solution

In the first example, you seem to ignore the fact that the bounding rect for glyphs has most probably a negative y origin. The returned rect usually treats `y=0` as the baseline for text. You thus set an offset in bounds rect and that is probably also the reason the layer has an offset in the text. (didn't try but think so)

If you're not interested in the bounds of a specific text but choosing a height that encloses all kinds of text, you might also want to go for `CTFontGetBoundingBox`.

Problem

I am trying to get the bounding box of a character in MacOS X/iOS using various techniques. I am now presenting all my attempts. Until now the code fails if I want to get the size of diacritic characters such as "Ä". Using `CTFontGetBoundingRectsForGlyphs` ``` -(void) resizeLayer1:(CATextLayer *)l toString:(NSString *)string { // need to set CGFont explicitly to convert font property to a CGFontRef CGFontRef layerFont = CGFontCreateWithFontName((CFStringRef)@"Helvetica"); l.font = layerFont; string = l.string; NSUInteger len = [string length]; // get characters from NSString UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len); CFStringGetCharacters((__bridge CFStringRef)l.string, CFRangeMake(0, [l.string length]), characters); // Get CTFontRef from CGFontRef CTFontRef coreTextFont = CTFontCreateWithGraphicsFont(layerFont, l.fontSize, NULL, NULL); // allocate glyphs and bounding box arrays for holding the result // assuming that each character is only one glyph, which is wrong CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph)*len); CTFontGetGlyphsForCharacters(coreTextFont, characters, glyphs, len); // get bounding boxes for glyphs CGRect *bb = (CGRect *)malloc(sizeof(CGRect)*len); CTFontGetBoundingRectsForGlyphs(coreTextFont, kCTFontDefaultOrientation, glyphs, bb, len); CFRelease(coreTextFont); l.position = CGPointMake(200.f, 100.f); l.bounds = bb[0]; l.backgroundColor = CGColorCreateGenericRGB(0.f, .5f, .9f, 1.f); free(characters); free(glyphs); free(bb); } ``` Result It kind of works, but there is some padding going on as the glyph is rendered by the CATextLayer Using `CTFramesetterSuggestFrameSizeWithConstraints` ``` -(void) resizeLayer2:(CATextLayer *)l toString:(NSString *)string { // need to set CGFont explicitly to convert font property to a CGFontRef CGFontRef layerFont = CGFontCreateWithFontName((CFStringRef)@"Helvetica"); l.font = layerFont; string = l.string; NSUInteger len = [string length]; // get characters from NSString UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len); CFStringGetCharacters((__bridge CFStringRef)l.string, CFRangeMake(0, [l.string length]), characters); // Get CTFontRef from CGFontRef CTFontRef coreTextFont = CTFontCreateWithGraphicsFont(layerFont, l.fontSize, NULL, NULL); CFMutableAttributedStringRef attrStr = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); CFAttributedStringReplaceString(attrStr, CFRangeMake(0, 0), (__bridge CFStringRef)string); CTTextAlignment alignment = kCTJustifiedTextAlignment; CTParagraphStyleSetting _settings[] = { {kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment} }; CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(_settings, sizeof(_settings) / sizeof(_settings[0])); CFAttributedStringSetAttribute(attrStr, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTParagraphStyleAttributeName, paragraphStyle); CFAttributedStringSetAttribute(attrStr, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTFontAttributeName, coreTextFont); CFRelease(paragraphStyle); CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrStr); CFRelease(attrStr); CFRange range; CGFloat maxWidth = CGFLOAT_MAX; CGFloat maxHeight = 10000.f; CGSize constraint = CGSizeMake(maxWidth, maxHeight); // checking frame sizes CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, len), nil, constraint, &range); // allocate glyphs and bounding box arrays for holding the result // assuming that each character is only one glyph, which is wrong CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph)*len); CTFontGetGlyphsForCharacters(coreTextFont, characters, glyphs, len); // get bounding boxes for glyphs CGRect *bb = (CGRect *)malloc(sizeof(CGRect)*len); CTFontGetBoundingRectsForGlyphs(coreTextFont, kCTFontDefaultOrientation, glyphs, bb, len); CFRelease(coreTextFont); bb[0].origin = l.bounds.origin; coreTextSize.width = ceilf(coreTextSize.width); coreTextSize.height = ceilf(coreTextSize.height); bb[0].size = coreTextSize; l.position = CGPointMake(200.f, 100.f); // after setting the bounds the layer gets transparent l.bounds = bb[0]; l.opaque = YES; return; l.backgroundColor = CGColorCreateGenericRGB(0.f, .5f, .9f, 1.f); free(characters); free(glyphs); free(bb); } ``` Result The bounding box of the string is correct. It gets problematic with diacritics: the Ä is missing its dots, because the bounding box is not tall enough. Using `CTLineGetTypographicBounds` ``` -(void) resizeLayer3:(CATextLayer *)l toString:(NSString *)string { // need to set CGFont explicitly to convert font property to a CGFontRef CGFontRef layerFont = CGFontCreateWithFontName((CFStringRef)@"Helvetica"); l.font = layerFont; string = l.string; NSUInteger len = [string length]; // get characters from NSString UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len); CFStringGetCharacters((__bridge CFStringRef)l.string, CFRangeMake(0, [l.string length]), characters); // Get CTFontRef from CGFontRef CTFontRef coreTextFont = CTFontCreateWithGraphicsFont(layerFont, l.fontSize, NULL, NULL); CFMutableAttributedStringRef attrStr = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); CFAttributedStringReplaceString(attrStr, CFRangeMake(0, 0), (__bridge CFStringRef)string); CFAttributedStringSetAttribute(attrStr, CFRangeMake(0, CFAttributedStringGetLength(attrStr)), kCTFontAttributeName, coreTextFont); CTLineRef line = CTLineCreateWithAttributedString(attrStr); CGFloat ascent; CGFloat descent; CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, NULL); CGFloat height = ascent+descent; CGSize coreTextSize = CGSizeMake(width,height); // get bounding boxes for glyphs CGRect *bb = (CGRect *)malloc(sizeof(CGRect)*len); CFRelease(coreTextFont); bb[0].origin = CGPointZero; coreTextSize.width = ceilf(coreTextSize.width); coreTextSize.height = ceilf(coreTextSize.height); bb[0].size = coreTextSize; l.position = CGPointMake(200.f, 100.f); // after setting the bounds the layer gets transparent l.bounds = bb[0]; l.opaque = YES; return; l.backgroundColor = CGColorCreateGenericRGB(0.f, .5f, .9f, 1.f); free(characters); free(bb); } ``` Result The bounding box of the string is correct. It gets problematic with diacritics: the Ä is missing its dots, because the bounding box is not tall enough. Using `CTFontGetBoundingRectsForGlyphs` ``` -(void) resizeLayer4:(CATextLayer *)l toString:(NSString *)string { // need to set CGFont explicitly to convert font property to a CGFontRef CGFontRef layerFont = CGFontCreateWithFontName((CFStringRef)@"Helvetica"); l.font = layerFont; string = l.string; NSUInteger len = [string length]; // get characters from NSString UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len); CFStringGetCharacters((__bridge CFStringRef)l.string, CFRangeMake(0, [l.string length]), characters); // Get CTFontRef from CGFontRef CTFontRef coreTextFont = CTFontCreateWithGraphicsFont(layerFont, l.fontSize, NULL, NULL); // allocate glyphs and bounding box arrays for holding the result // assuming that each character is only one glyph, which is wrong CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph)*len); CTFontGetGlyphsForCharacters(coreTextFont, characters, glyphs, len); CGPathRef glyphPath = CTFontCreatePathForGlyph(coreTextFont, glyphs[1], NULL); CGRect rect = CGPathGetBoundingBox(glyphPath); // get bounding boxes for glyphs CGRect *bb = (CGRect *)malloc(sizeof(CGRect)*len); CTFontGetBoundingRectsForGlyphs(coreTextFont, kCTFontDefaultOrientation, glyphs, bb, len); CFRelease(coreTextFont); l.position = CGPointMake(200.f, 100.f); l.bounds = rect; l.backgroundColor = CGColorCreateGenericRGB(0.f, .5f, .9f, 1.f); free(characters); free(glyphs); free(bb); } ``` Result The bounding box of the string is correct. It gets problematic with diacritics as the diacritics itself are a glyph and the character are another glyph meaning that Ä consists of two glyphs. How could one use that? Are there any other possibilities I have overlooked and are worthwhile trying? EDIT The third option `CTLineGetTypographicBounds` seems to work. I am running however in a different problem, that the first line in a `CATextLayer` is missing its diacritic marks. The first line is somehow just not tall enough. That would mean I am barking up the wrong tree here.

Original source