Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Text's CTFramesetterSuggestFrameSizeWithConstraints() returns incorrect size every time

According to the docs, CTFramesetterSuggestFrameSizeWithConstraints () "determines the frame size needed for a string range".

Unfortunately the size returned by this function is never accurate. Here is what I am doing:

    NSAttributedString *string = [[[NSAttributedString alloc] initWithString:@"lorem ipsum" attributes:nil] autorelease];
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) string);
    CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, CGSizeMake(rect.size.width, CGFLOAT_MAX), NULL);

The returned size always has the correct width calculated, however the height is always slightly shorter than what is expected.

Is this the correct way to use this method?

Is there any other way to layout Core Text?

Seems I am not the only one to run into problems with this method. See https://devforums.apple.com/message/181450.

Edit: I measured the same string with Quartz using sizeWithFont:, supplying the same font to both the attributed string, and to Quartz. Here are the measurements I received:

Core Text: 133.569336 x 16.592285

Quartz: 135.000000 x 31.000000

like image 525
nsapplication Avatar asked Apr 25 '10 09:04

nsapplication


3 Answers

try this.. seem to work:

+(CGFloat)heightForAttributedString:(NSAttributedString *)attrString forWidth:(CGFloat)inWidth
{
    CGFloat H = 0;

    // Create the framesetter with the attributed string.
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString( (CFMutableAttributedStringRef) attrString); 

    CGRect box = CGRectMake(0,0, inWidth, CGFLOAT_MAX);

    CFIndex startIndex = 0;

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, box);

    // Create a frame for this column and draw it.
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(startIndex, 0), path, NULL);

    // Start the next frame at the first character not visible in this frame.
    //CFRange frameRange = CTFrameGetVisibleStringRange(frame);
    //startIndex += frameRange.length;

    CFArrayRef lineArray = CTFrameGetLines(frame);
    CFIndex j = 0, lineCount = CFArrayGetCount(lineArray);
    CGFloat h, ascent, descent, leading;

    for (j=0; j < lineCount; j++)
    {
        CTLineRef currentLine = (CTLineRef)CFArrayGetValueAtIndex(lineArray, j);
        CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading);
        h = ascent + descent + leading;
        NSLog(@"%f", h);
        H+=h;
    }

    CFRelease(frame);
    CFRelease(path);
    CFRelease(framesetter);


    return H;
}
like image 163
ing.conti Avatar answered Jan 04 '23 11:01

ing.conti


For a single line frame, try this:

line = CTLineCreateWithAttributedString((CFAttributedStringRef) string);
CGFloat ascent;
CGFloat descent;
CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
CGFloat height = ascent+descent;
CGSize textSize = CGSizeMake(width,height);

For multiline frames, you also need to add the line's lead (see a sample code in Core Text Programming Guide)

For some reason, CTFramesetterSuggestFrameSizeWithConstraints() is using the difference in ascent and descent to calculate the height:

CGFloat wrongHeight = ascent-descent;
CGSize textSize = CGSizeMake(width, wrongHeight);

It could be a bug?

I'm having some other problems with the width of the frame; It's worth checking out as it only shows in special cases. See this question for more.

like image 26
mohsenr Avatar answered Jan 04 '23 13:01

mohsenr


The problem is that you have to apply a paragraph style to the text before you measure it. If you don't then you get the default leading of 0.0. I provided a code sample for how to do this in my answer to a duplicate of this question here https://stackoverflow.com/a/10019378/1313863.

like image 23
Chris DeSalvo Avatar answered Jan 04 '23 12:01

Chris DeSalvo