6

I have a certain amount of text that fill some CTFrame (more than one). To create all frames (one for each page), I'm filling one frame, getting the text that didn't fitted the frame using CTFrameGetVisibleStringRange and repeating this process until all text is processed.

On all frames, except the last, the text occupies the same height of page. On last frame I'd like to know the real height the text occupies, to know where I could start drawing more text.

Is there any way to do this?

UPDATE

As requested on comments, here's my solution using @omz 's suggestion:

I'm using ARC on my project:

CTFrameRef locCTFrame = (__bridge CTFrameRef)ctFrame;

//Save CTLines
lines = (NSArray *) ((__bridge id)CTFrameGetLines(locCTFrame));

//Get line origins
CGPoint lOrigins[MAXLINESPERPAGE];
CTFrameGetLineOrigins(locCTFrame, CFRangeMake(0, 0), lOrigins);
CGFloat colHeight = self.frame.size.height;

//Save the amount of the height used by text
percentFull = ((colHeight - lOrigins[[lines count] - 1].y) / colHeight);
javsmo
  • 1,337
  • 1
  • 14
  • 23

4 Answers4

11
+ (CGSize)measureFrame:(CTFrameRef)frame
{
    // 1. measure width
    CFArrayRef  lines       = CTFrameGetLines(frame);
    CFIndex     numLines    = CFArrayGetCount(lines);
    CGFloat     maxWidth    = 0;

    for(CFIndex index = 0; index < numLines; index++)
    {
        CTLineRef   line = (CTLineRef) CFArrayGetValueAtIndex(lines, index);
        CGFloat     ascent, descent, leading, width;

        width = CTLineGetTypographicBounds(line, &ascent,  &descent, &leading);

        if(width > maxWidth)
            maxWidth = width;
    }

    // 2. measure height
    CGFloat ascent, descent, leading;

    CTLineGetTypographicBounds((CTLineRef) CFArrayGetValueAtIndex(lines, 0), &ascent,  &descent, &leading);
    CGFloat firstLineHeight = ascent + descent + leading;

    CTLineGetTypographicBounds((CTLineRef) CFArrayGetValueAtIndex(lines, numLines - 1), &ascent,  &descent, &leading);
    CGFloat lastLineHeight  = ascent + descent + leading;

    CGPoint firstLineOrigin;
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 1), &firstLineOrigin);

    CGPoint lastLineOrigin;
    CTFrameGetLineOrigins(frame, CFRangeMake(numLines - 1, 1), &lastLineOrigin);

    CGFloat textHeight = ABS(firstLineOrigin.y - lastLineOrigin.y) + firstLineHeight + lastLineHeight;

    return CGSizeMake(maxWidth, textHeight);
}
abuharsky
  • 1,151
  • 12
  • 21
  • Using the CTFrameGetLineOrigins as suggested by @omz, I could get the last line's origin and discover the height used, including blank lines. – javsmo Oct 15 '12 at 17:57
  • I tried to post the code here but became unreadable. I'll edit the question. – javsmo Oct 17 '12 at 15:09
  • Of all the solutions for getting the height of a complex attributed string this has been the closest for me so far. – mwhuss Jun 12 '14 at 17:50
  • 2
    This has generally worked well for me, except that I'm having issues with the height. If I'm understanding correctly, the textHeight calculation is actually too large. Take the one line case - firstLineHeight == lastLineHeight and firstLineOrigin.y == lastLineOrigin.y. Then textHeight will be twice the line height, when you actually just want the line height, correct? – jeremywhuff Jul 24 '14 at 16:50
3

You could either get the line origin of the last line in the frame with CTFrameGetLineOrigins or use the CTFramesetterSuggestFrameSizeWithConstraints function to get the size of a rectangular frame for a given range. The latter wouldn't work if you use non-rectangular paths for setting the actual frames though.

omz
  • 53,243
  • 5
  • 129
  • 141
  • Thanks @omz I think I'll use the first one, as I saw on another [answer](http://stackoverflow.com/questions/2707710/core-texts-ctframesettersuggestframesizewithconstraints-returns-incorrect-siz) that `CTFramesetterSuggestFrameSizeWithConstraints` some times returns an incorrect value. – javsmo Dec 04 '11 at 18:43
0

I think user1021430 is correct in saying that the height is not correctly calculated.

To get the correct height, you wan to get the top of the first line (origin + first ascent) and the bottom of the last line (origin - descent), and then subtract the two come up with the actual height.

CGSize
MeasureTextWithinFrame(
    CTFrameRef      frame)
{
    CGSize textSize = CGSizeMake(0.0f, 0.0f);
    
    CFArrayRef  lines       = CTFrameGetLines(frame);
    CFIndex     numLines    = CFArrayGetCount(lines);

    // if there is at least one line
    if (numLines > 0) {
    
        // measure width
        for (CFIndex index = 0; index < numLines; index++)  {
            CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex(lines, index);
            CGFloat ascent, descent, leading, width;
            width = CTLineGetTypographicBounds(line, &ascent,  &descent, &leading);
            if (width > textSize.width)
                textSize.width = width;
        }
    
        // measure height
        CGFloat firstAscent, firstDescent, firstLeading;
        CTLineGetTypographicBounds((CTLineRef) CFArrayGetValueAtIndex(lines, 0), &firstAscent,  &firstDescent, &firstLeading);

        CGPoint firstLineOrigin;
        CTFrameGetLineOrigins(frame, CFRangeMake(0, 1), &firstLineOrigin);

        CGFloat lastAscent, lastDescent, lastLeading;
        CTLineGetTypographicBounds((CTLineRef) CFArrayGetValueAtIndex(lines, numLines - 1), &lastAscent,  &lastDescent, &lastLeading);
        
        CGPoint lastLineOrigin;
        CTFrameGetLineOrigins(frame, CFRangeMake(numLines - 1, 1), &lastLineOrigin);
        
        float top = firstLineOrigin.y + firstAscent;
        float bottom = lastLineOrigin.y - lastDescent;
        textSize.height = ABS(top - bottom);
    }
    
    return textSize;
}
Mark Coniglio
  • 351
  • 1
  • 10
  • I haven't tested this code since 6 years. It may have changed since then. I'll check and update the answer. – javsmo Jun 18 '21 at 20:35
0

Use CTLineGetTypographicBounds.

DD_
  • 7,230
  • 11
  • 38
  • 59
virushuo
  • 660
  • 3
  • 5