Vertical align with Core Text?
Asked Answered
H

4

9

An image clarifying things

How do I change the vertical alignment of the text in a CTFramesetter frame? I want my text to be in the middle instead of being at the top. I am using Core Text framework. There is a setting of the paragraph to change horizontal aligment but not vertical.

Helotism answered 19/7, 2010 at 19:28 Comment(0)
H
8

Finally figured it out ...

CGRect boundingBox = CTFontGetBoundingBox(font);

//Get the position on the y axis
float midHeight = self.frame.size.height / 2;
midHeight -= boundingBox.size.height / 2;

CGPathAddRect(path, NULL, CGRectMake(0, midHeight, self.frame.size.width, boundingBox.size.height));
Helotism answered 27/7, 2010 at 10:10 Comment(3)
This does not work for some fonts. Just tested using AcademyEngravedLetPlain font, and the font does not get drawnOpprobrious
But I've managed to work it out by setting the rect size height as the original bounds height, not the boundingBox heightOpprobrious
You can use the size returned by CTFramesetterSuggestFrameSizeWithConstraints to calculate the midHeight, it supports multiple lines.Pitzer
L
5

Thanks Nick, that was a great snippet.

Just expanding on that, if your doing Top, Middle and Bottom alignment with an enum, for example you could do it like so:

if (VerticalAlignmentTop == currentTextAlignment) {
    CGPathAddRect(path, NULL, rect); // Draw normally (top)
}
else if (VerticalAlignmentMiddle == currentTextAlignment) {
    CGRect boundingBox = CTFontGetBoundingBox(fontRef);

    //Get the position on the y axis (middle)
    float midHeight = rect.size.height / 2;
    midHeight -= boundingBox.size.height / 2;

    CGPathAddRect(path, NULL, CGRectMake(0, midHeight, rect.size.width, boundingBox.size.height));  
}
else {
    CGRect boundingBox = CTFontGetBoundingBox(fontRef);

    CGPathAddRect(path, NULL, CGRectMake(0, 0, rect.size.width, boundingBox.size.height));  
}
Lattonia answered 17/11, 2011 at 13:6 Comment(0)
S
3

You can use [NSString boundingRectWithSize:options:attributes:context:] to get the rectangle of your string's bounding box, which allows multi-line text as well. In your draw text method, do the following (RECT is the rectangle where you want to draw the text):

// get the graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);

// flip the context coordinate
CGContextTranslateCTM(context, 0.0f, 2*RECT.origin.y+RECT.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);

// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);

// set text horizontal alignment
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter;

NSDictionary *attributes = @{NSParagraphStyleAttributeName:paragraphStyle, NSFontAttributeName:YOUR_FONT, NSForegroundColorAttributeName:TEXT_COLOR};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:YOUR_TEXT attributes:attributes];

CGMutablePathRef path = CGPathCreateMutable();

// set text vertical alignment
CGSize textSize = [text boundingRectWithSize:RECT.size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
CGPathAddRect(path, NULL, CGRectMake(RECT.origin.x, RECT.origin.y-(RECT.size.height-textSize.height)/2.0f, RECT.size.width, RECT.size.height));

CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attrString.length), path, NULL);
CTFrameDraw(frame, context);

CFRelease(frame);
CFRelease(path);
CFRelease(frameSetter);

[attrString release];
[paragraphStyle release];

CGContextRestoreGState(context);
Subulate answered 10/12, 2013 at 17:0 Comment(0)
M
2

This accounts for the fact that multiple font types and styles can be used in a frame (calculates both height and width of text, look in the if(index == lastLineIndex) block to see where the height is calculated):

- (CGSize) measureFrame: (CTFrameRef) frame forContext: (CGContext *) cgContext
{
    CGPathRef framePath = CTFrameGetPath(frame);
    CGRect frameRect = CGPathGetBoundingBox(framePath);

    CFArrayRef lines = CTFrameGetLines(frame);
    CFIndex numLines = CFArrayGetCount(lines);

    CGFloat maxWidth = 0;
    CGFloat textHeight = 0;

    // Now run through each line determining the maximum width of all the lines.
    // We special case the last line of text. While we've got it's descent handy,
    // we'll use it to calculate the typographic height of the text as well.

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

        if(width > maxWidth)
        {
            maxWidth = width;
        }

        if(index == lastLineIndex)
        {
            // Get the origin of the last line. We add the descent to this
            // (below) to get the bottom edge of the last line of text.

            CGPoint lastLineOrigin;
            CTFrameGetLineOrigins(frame, CFRangeMake(lastLineIndex, 1), &lastLineOrigin);

            // The height needed to draw the text is from the bottom of the last line
            // to the top of the frame.

            textHeight =  CGRectGetMaxY(frameRect) - lastLineOrigin.y + descent;
        }
    }

    // For some text the exact typographic bounds is a fraction of a point too
    // small to fit the text when it is put into a context. We go ahead and round
    // the returned drawing area up to the nearest point.  This takes care of the
    // discrepencies.

    return CGSizeMake(ceil(maxWidth), ceil(textHeight));
}

Reference: Scott Thompson (http://lists.apple.com/archives/quartz-dev/2008/Mar/msg00079.html)

Muddy answered 14/3, 2012 at 7:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.