iOS Reference Library Apple Developer
Search

Common Operations

This chapter describes some common text-layout and font-handling operations and shows, through portions of sample code, how they can be accomplished using Core Text. In addition to the code fragments in this chapter, see the following sample code applications that use Core Text:

Simple Paragraphs

One of the most common operations in typesetting is laying out a multiline paragraph within an arbitrarily sized rectangular area. Core Text makes this operation easy, requiring only a few lines of Core Text–specific code. To lay out the paragraph, you need a graphics context to draw into, a rectangular path to provide the area where the text is laid out, and an attributed string. Most of the code in this example is required to create and initialize the context, path, and string. After that is done, Core Text requires only three lines of code to do the layout.

Listing 2-1 uses Cocoa to simplify initialization of the graphics context. To see how that operation is done in Carbon, see the CoreTextTest sample code or “Graphics Contexts“ in the Quartz 2D Programming Guide.

Listing 2-1  Typesetting a simple paragraph

// Initialize a graphics context and set the text matrix to a known value.
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext]
                                                             graphicsPort];
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
 
// Initialize a rectangular path.
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = CGRectMake(10.0, 10.0, 200.0, 200.0);
CGPathAddRect(path, NULL, bounds);
 
// Initialize an attributed string.
CFStringRef string = CFSTR("We hold this truth to be self-evident, that
                             everyone is created equal.");
CFMutableAttributedStringRef attrString =
                   CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringReplaceString (attrString,
                    CFRangeMake(0, 0), string);
 
// Create a color and add it as an attribute to the string.
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };
CGColorRef red = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50),
                                kCTForegroundColorAttributeName, red);
 
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter =
                  CTFramesetterCreateWithAttributedString(attrString);
CFRelease(attrString);
 
// Create the frame and draw it into the graphics context
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
                                      CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CTFrameDraw(frame, context);
CFRelease(frame);

Simple Text Labels

Another very common typesetting operation is drawing a single line of text to use as a label for a user-interface element. In Core Text this requires only two lines of code, one to create the line object with an attributed string and another to draw the line into a graphic context.

Listing 2-2 omits initialization of the plain text string, font, and graphics context, but it shows how to create an attributes dictionary and use it to create the attributed string. To see how to create a Core Text font, see “Font Creation and Storage.”

Listing 2-2  Typesetting a simple text label

CFStringRef string; CTFontRef font; CGContextRef context;
// Initialize string, font, and context
CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { font };
 
CFDictionaryRef attributes =
    CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
        (const void**)&values, sizeof(keys) / sizeof(keys[0]),
        &kCFTypeDictionaryCallBacks,
        &kCFTypeDictionaryValueCallbacks);
 
CFAttributedStringRef attrString =
    CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
CFRelease(string);
CFRelease(attributes);
 
CTLineRef line = CTLineCreateWithAttributedString(attrString);
 
// Set text position and draw the line into the graphics context
CGContextSetTextPosition(context, 10.0, 10.0);
CTLineDraw(line, context);
CFRelease(line);

Columnar Layout

Laying out text in multiple columns is another common typesetting operation. Strictly speaking, Core Text itself only performs the layout of one column at a time and does not calculate the column sizes or locations. You do those operations before calling Core Text to lay out the text within the rectangular path area you’ve calculated. In this sample, Core Text, in addition to laying out the text in each column, also provides the subrange within the text string for each column.

Listing 2-3 mixes Cocoa method calls in Objective-C with function calls into Carbon frameworks and Core Text. It includes an implementation of the drawRect: method of NSView, which calls the local createColumns function, defined first in this listing. This code resides in an NSView subclass in a Cocoa document-based application. The NSView subclass includes an attributedString accessor method, which is not shown here but is called in this listing to return the attributed string to be laid out.

Listing 2-3  Performing columnar text layout

- (CFArrayRef)createColumns {
    CGRect bounds = CGRectMake(0, 0, NSWidth([self bounds]),
                                NSHeight([self bounds]));
    int column;
    CGRect* columnRects = (CGRect*)calloc(_columnCount, sizeof(*columnRects));
 
    // Start by setting the first column to cover the entire view.
    columnRects[0] = bounds;
    // Divide the columns equally across the frame's width.
    CGFloat columnWidth = CGRectGetWidth(bounds) / _columnCount;
    for (column = 0; column < _columnCount - 1; column++) {
        CGRectDivide(columnRects[column], &columnRects[column],
                     &columnRects[column + 1], columnWidth, CGRectMinXEdge);
    }
 
    // Inset all columns by a few pixels of margin.
    for (column = 0; column < _columnCount; column++) {
        columnRects[column] = CGRectInset(columnRects[column], 10.0, 10.0);
    }
 
// Create an array of layout paths, one for each column.
    CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault,
                         _columnCount, &kCFTypeArrayCallBacks);
    for (column = 0; column < _columnCount; column++) {
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, columnRects[column]);
        CFArrayInsertValueAtIndex(array, column, path);
        CFRelease(path);
    }
    free(columnRects);
    return array;
}
 
- (void)drawRect:(NSRect)rect {
    // Draw a white background.
    [[NSColor whiteColor] set];
    [NSBezierPath fillRect:[self bounds]];
 
    // Initialize the text matrix to a known value.
    CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext]
                                          graphicsPort];
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
 
    CTFramesetterRef framesetter =
        CTFramesetterCreateWithAttributedString(
                            (CFAttributedStringRef)[self attributedString]);
    CFArrayRef columnPaths = [self createColumns];
 
    CFIndex pathCount = CFArrayGetCount(columnPaths);
    CFIndex startIndex = 0;
    int column;
    for (column = 0; column < pathCount; column++) {
        CGPathRef path = (CGPathRef)CFArrayGetValueAtIndex(columnPaths, column);
 
        // Create a frame for this column and draw it.
        CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
                                      CFRangeMake(startIndex, 0), path, NULL);
        CTFrameDraw(frame, context);
 
        // Start the next frame at the first character not visible in this frame.
        CFRange frameRange = CTFrameGetVisibleStringRange(frame);
        startIndex += frameRange.length;
 
        CFRelease(frame);
    }
    CFRelease(columnPaths);
}

Manual Line Breaking

You usually don't need to do manual line breaking unless you have a special hyphenation process or a similar requirement. A framesetter performs line breaking automatically. Listing 2-4 shows how to create a typesetter, an object used by the framesetter, and use it directly to find appropriate line breaks and create a typeset line manually. This sample also shows how to center the line before drawing.

Listing 2-4  Performing manual line breaking

double width; CGContextRef context; CGPoint textPosition; CFAttributedStringRef attrString;
// Initialize those variables.
 
// Create a typesetter using the attributed string.
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(attrString);
 
// Find a break for line from the beginning of the string to the given width.
CFIndex start = 0;
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);
 
// Use the returned character count (to the break) to create the line.
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));
 
// Get the offset needed to center the line.
float flush = 0.5; // centered
double penOffset = CTLineGetPenOffsetForFlush(line, flush, width);
 
// Move the given text drawing position by the calculated offset and draw the line.
CGContextSetTextPosition(context, textPosition.x + penOffset, textPosition.y);
CTLineDraw(line, context);
 
// Move the index beyond the line break.
start += count;

Font Creation and Storage

The example function in Listing 2-5 creates a font descriptor from a PostScript font name and a float specifying the point size.

Listing 2-5  Creating a font descriptor from a name and point size

CTFontDescriptorRef CreateFontDescriptorFromName(CFStringRef iPostScriptName,
                                                  CGFloat iSize)
{
    assert(iPostScriptName != NULL);
    return CTFontDescriptorCreateWithNameAndSize(iPostScriptName, iSize);
}

The example function in Listing 2-6 creates a font descriptor from a font family name and font traits.

Listing 2-6  Creating a font descriptor from a family and traits

CTFontDescriptorRef CreateFontDescriptorFromFamilyAndTraits(CFStringRef iFamilyName,
          CTFontSymbolicTraits iTraits, CGFloat iSize)
{
    CTFontDescriptorRef descriptor = NULL;
    CFMutableDictionaryRef attributes;
 
    assert(iFamilyName != NULL);
    // Create a mutable dictionary to hold our attributes.
    attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
              &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    check(attributes != NULL);
 
    if (attributes != NULL) {
        CFMutableDictionaryRef traits;
        CFNumberRef symTraits;
 
        // Add a family name to our attributes.
        CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, iFamilyName);
 
        // Create the traits dictionary.
        symTraits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
                                    &iTraits);
        check(symTraits != NULL);
 
        if (symTraits != NULL) {
            // Create a dictionary to hold our traits values.
            traits = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            check(traits != NULL);
 
            if (traits != NULL) {
                // Add the symbolic traits value to the traits dictionary.
                CFDictionaryAddValue(traits, kCTFontSymbolicTrait, symTraits);
 
                // Add the traits attribute to our attributes.
                CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits);
                CFRelease(traits);
            }
            CFRelease(symTraits);
        }
        // Create the font descriptor with our attributes and input size.
        descriptor = CTFontDescriptorCreateWithAttributes(attributes);
        check(descriptor != NULL);
 
        CFRelease(attributes);
    }
    // Return our font descriptor.
    return descriptor;
}

The example function in Listing 2-7 creates a font from a provided font descriptor. It calls CTFontCreateWithFontDescriptor, passing NULL for the matrix parameter to specify the default (identity) matrix.

Listing 2-7  Creating a font from a font descriptor

CTFontRef CreateFont(CTFontDescriptorRef iFontDescriptor, CGFloat iSize)
{
    check(iFontDescriptor != NULL);
 
    // Create the font from the font descriptor and input size. Pass
    // NULL for the matrix parameter to use the default matrix (identity).
 
    return CTFontCreateWithFontDescriptor(iFontDescriptor, iSize, NULL);
}

The example function in Listing 2-8 creates XML data to serialize a font for embedding in a document. Alternatively, and preferably, NSArchiver could be used. This is just one way to accomplish this task, but it preserves all data from the font needed to re-create the exact font at a later time.

Listing 2-8  Serializing a font

CFDataRef CreateFlattenedFontData(CTFontRef iFont)
{
    CFDataRef           result = NULL;
    CTFontDescriptorRef descriptor;
    CFDictionaryRef     attributes;
 
    check(iFont != NULL);
 
    // Get the font descriptor for the font.
    descriptor = CTFontCopyFontDescriptor(iFont);
    check(descriptor != NULL);
 
    if (descriptor != NULL) {
        // Get the font attributes from the descriptor. This should be enough
        // information to recreate the descriptor and the font later.
        attributes = CTFontDescriptorCopyAttributes(descriptor);
        check(attributes != NULL);
 
        if (attributes != NULL) {
            // If attributes are a valid property list, directly flatten
            // the property list. Otherwise we may need to analyze the attributes
            // and remove or manually convert them to serializable forms.
            // This is left as an exercise for the reader.
           if (CFPropertyListIsValid(attributes, kCFPropertyListXMLFormat_v1_0)) {
                result = CFPropertyListCreateXMLData(kCFAllocatorDefault,
                                                                 attributes);
                check(result != NULL);
            }
        }
    }
    return result;
}

The example function in Listing 2-9 creates a font reference from flattened XML data. It shows how to unflatten font attributes and create a font with those attributes.

Listing 2-9  Creating a font from serialized data

CTFontRef CreateFontFromFlattenedFontData(CFDataRef iData)
{
    CTFontRef           font = NULL;
    CFDictionaryRef     attributes;
    CTFontDescriptorRef descriptor;
 
    check(iData != NULL);
 
    // Create our font attributes from the property list. We will create
    //  an immutable object for simplicity, but if you needed to massage
    // the attributes or convert certain attributes from their serializable
    // form to the Core Text usable form, you could do it here.
    attributes =
          (CFDictionaryRef)CFPropertyListCreateFromXMLData(kCFAllocatorDefault,
                      iData, kCFPropertyListImmutable, NULL);
    check(attributes != NULL);
 
    if (attributes != NULL) {
        // Create the font descriptor from the attributes.
        descriptor = CTFontDescriptorCreateWithAttributes(attributes);
        check(descriptor != NULL);
 
        if (descriptor != NULL) {
            // Create the font from the font descriptor. We will use
            // 0.0 and NULL for the size and matrix parameters. This
            // causes the font to be created with the size and/or matrix
            // that exist in the descriptor, if present. Otherwise default
            // values are used.
            font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);
            check(font != NULL);
        }
    }
    return font;
}

Accessing Font Metrics

For every font, glyph designers provide a set of measurements, called metrics, which describe the spacing around each glyph in the font. The typesetter uses these metrics to determine glyph placement. Font metrics are parameters such as ascent, descent, leading, cap height, x-height, and so on.

The sample functions in this section illustrate how to query a font for its font metric data. The example function in Listing 2-10 shows how to use line metrics accessors to calculate the line height for a font. In most cases you should not need to do this yourself. If you have a CTLineRef object for a line of text , you could call CTLineGetTypographicBounds to get the line metrics for the line.

Listing 2-10  Calculating line height

CGFloat GetLineHeightForFont(CTFontRef iFont)
{
    CGFloat lineHeight = 0.0;
 
    check(iFont != NULL);
 
    // Get the ascent from the font, already scaled for the font's size
    lineHeight += CTFontGetAscent(iFont);
 
    // Get the descent from the font, already scaled for the font's size
    lineHeight += CTFontGetDescent(iFont);
 
    // Get the leading from the font, already scaled for the font's size
    lineHeight += CTFontGetLeading(iFont);
 
    return lineHeight;
}
 

The example function in Listing 2-11 demonstrates how to get glyphs for the characters in a string with a single font. Most of the time you should just use a CTLine object to get this information because one font may not encode the entire string. In addition, simple character-to-glyph mapping will not get the correct appearance for complex scripts. This simple glyph mapping may be appropriate if you are trying to display specific Unicode characters for a font.

Listing 2-11  Getting glyphs for characters

void GetGlyphsForCharacters(CTFontRef iFont, CFStringRef iString)
{
    UniChar *characters;
    CGGlyph *glyphs;
    CFIndex count;
 
    assert(iFont != NULL && iString != NULL);
 
    // Get our string length.
    count = CFStringGetLength(iString);
 
    // Allocate our buffers for characters and glyphs.
    characters = (UniChar *)malloc(sizeof(UniChar) * count);
    assert(characters != NULL);
 
    glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * count);
    assert(glyphs != NULL);
 
    // Get the characters from the string.
    CFStringGetCharacters(iString, CFRangeMake(0, count), characters);
 
    // Get the glyphs for the characters.
    CTFontGetGlyphsForCharacters(iFont, characters, glyphs, count);
 
    // Do something with the glyphs here, if a character is unmapped
 
 
    // Free our buffers
        free(characters);
        free(glyphs);
}

Creating Related Fonts

The example functions in this section show how to query fonts for their attributes. The example function in Listing 2-12 makes a font bold or unbold based on the value of the Boolean parameter passed with the function call. If the current font family does not have the requested style, the function returns NULL.

Listing 2-12  Changing traits of a font

CTFontRef CreateBoldFont(CTFontRef iFont, Boolean iMakeBold)
{
    CTFontSymbolicTraits desiredTrait = 0;
    CTFontSymbolicTraits traitMask;
 
    // If we are trying to make the font bold, set the desired trait
    // to be bold.
    if (iMakeBold)
        desiredTrait = kCTFontBoldTrait;
 
    // Mask off the bold trait to indicate that it is the only trait
    // desired to be modified. As CTFontSymbolicTraits is a bit field,
    // we could choose to change multiple traits if we desired.
    traitMask = kCTFontBoldTrait;
 
    // Create a copy of the original font with the masked trait set to the
    // desired value. If the font family does not have the appropriate style,
    // this will return NULL.
 
    return CTFontCreateCopyWithSymbolicTraits(iFont, 0.0, NULL, desiredTrait, traitMask);
}

The example function in Listing 2-13 converts a given font to a similar font in another font family, preserving traits if possible. It may return NULL.

Listing 2-13  Converting a font to another family

CTFontRef CreateFontConvertedToFamily(CTFontRef iFont, CFStringRef iFamily)
{
    // Create a copy of the original font with the new family. This call
    // attempts to preserve traits, and may return NULL if that is not possible.
    // Pass in 0.0 and NULL for size and matrix to preserve the values from
    // the original font.
 
    return CTFontCreateCopyWithFamily(iFont, 0.0, NULL, iFamily);
}



Last updated: 2010-03-03

Did this document help you? Yes It's good, but... Not helpful...