Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bounding box of character with diacritics using CoreText

Tags:

cocoa

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.

like image 752
GorillaPatch Avatar asked May 08 '12 20:05

GorillaPatch


People also ask

What is a bounding box in object detection?

Bounding Boxes In object detection, we usually use a bounding box to describe the spatial location of an object. The bounding box is rectangular, which is determined by the x and y coordinates of the upper-left corner of the rectangle and the such coordinates of the lower-right corner.

How do you find the bounding box of a rectangle?

The bounding box is rectangular, which is determined by the x and y coordinates of the upper-left corner of the rectangle and the such coordinates of the lower-right corner. Another commonly used bounding box representation is the ( x, y) -axis coordinates of the bounding box center, and the width and height of the box.

How does text detection work in OCR?

In text detection, our goal is to automatically compute the bounding boxes for every region of text in an image: Figure 2: Once text has been localized/detected in an image, we can decode it using OCR software. Tesseract can be used for text localization/detection as well as OCR. Once we have those regions, we can then OCR them.

Can tesseract perform text detection and OCR?

You’re in luck, Bryan. Tesseract does have the ability to perform text detection and OCR in a single function call — and as you’ll find out, it’s quite easy to do! To learn how to detect, localize, and OCR text with Tesseract, just keep reading. Looking for the source code to this post?


2 Answers

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.

like image 200
Max Seelemann Avatar answered Sep 29 '22 22:09

Max Seelemann


Try CTLineGetImageBounds. Note, however, that this will include parts of a glyph that extend below the baseline, which is relevant for the lowercase ä, for example.

Brief explanation: Rounded shapes must extend beyond straight shapes in order to appear with the same visual weight. See lowercase b or d, for example, which have both a round and a straight shape at the baseline.

like image 26
Martin Winter Avatar answered Sep 29 '22 23:09

Martin Winter