2010-08-26-uilabel-with-a-custom-cgfont.md 5.7 KB


title: UILabel with a (custom) CGFont author: Marcus Rohrmoser type: post date: 2010-08-26T16:05:18+00:00 url: /2010/08/uilabel-with-a-custom-cgfont/ yourls_shorturl:

categories:

  • en
    • development tags:
    • CGFont
    • Cocoa
    • iOS
    • iPhone
    • Objective C
    • OTF
    • TrueType
    • TTF
    • UIFont
    • UILabel

UILabel's font property accepts UIFonts – but strange enough there's no way to get a custom loaded CGFont (from a ttf or otf file) converted into such an UIFont. You're stuck with the iPhone's pre-installed fonts – at least when you have to support iOS 3.0 devices.

After googling a bit and searching Stackoverflow I found the solutions presented there not ideal or great, but too heavy weight.

So I inherited UILabel with a very lean custom class UILabelWithCGFont and overloaded it's drawTextInRect: method like this:

-(void)drawTextInRect:(CGRect)rect
{
    MRLogD(@"(%f,%f) (%f,%f)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
    if ( _CGFont == NULL ) {
        [super drawTextInRect:rect];
        return;
    }
    NSAssert(_mapping != NULL, @"Mapping function pointer not set.");

    // prepare the target graphics context.
    const CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSaveGState(ctx);
    {
        // prepare the glyphs array to draw
        const NSString *txt = self.text;
        const size_t glyphCount = txt.length;
        CGGlyph glyphs[glyphCount];
        {
            // turn the string txt into glyphs (indices into the font):
            // give non-allocating unicode character retrieval a try:
            const UniChar *raw_unichars = CFStringGetCharactersPtr( (CFStringRef)txt );
            const UniChar *unichars = raw_unichars == NULL ? malloc( glyphCount * sizeof(UniChar) ) : raw_unichars;
            NSAssert(unichars != NULL, @"unichars not allocated");
            if ( raw_unichars == NULL )
                CFStringGetCharacters( (CFStringRef)txt, CFRangeMake(0, txt.length), (UniChar *)unichars );
            for ( int i = glyphCount - 1; i >= 0; i-- )
                glyphs[i] = _mapping(unichars[i]);
            if ( raw_unichars == NULL )
                free( (void *)unichars );
        }

        CGContextSetFont(ctx, _CGFont);
        CGContextSetFontSize(ctx, self.font.pointSize);
        CGContextSetTextMatrix( ctx, CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0) );

        // first print 'invisible' to measure size:
        CGContextSetTextDrawingMode(ctx, kCGTextInvisible);
        const CGPoint pre = CGContextGetTextPosition(ctx);
        CGContextShowGlyphs(ctx, glyphs, glyphCount);
        const CGPoint post = CGContextGetTextPosition(ctx);
        // restore text position
        CGContextSetTextPosition(ctx, pre.x, pre.y);

        // centered horizontal + vertical:
        NSAssert( (int)rect.origin.x == 0, @"origin.x not zero" );
        NSAssert( (int)rect.origin.y == 0, @"origin.y not zero" );
        NSAssert(self.baselineAdjustment == UIBaselineAdjustmentAlignCenters, @"vertical alignment not 'center'");
        NSAssert(self.textAlignment == UITextAlignmentCenter, @"horizontal alignment not 'center'");
        const CGPoint p = CGPointMake( ( rect.size.width - (post.x - pre.x) ) / 2, (rect.size.height + self.font.pointSize + pre.y) / 2 );

        // finally render it to the graphics context:
        CGContextSetTextDrawingMode(ctx, kCGTextFill);
        CGContextSetFillColorWithColor(ctx, self.textColor.CGColor);
        CGContextShowGlyphsAtPoint(ctx, p.x, p.y, glyphs, glyphCount);
    }
    CGContextRestoreGState(ctx);
}

Usage: Just turn the UILabel instances in Interface Builder into UILabelWithCGFont and implement the UIViewController::viewDidLoad method like this:

CGGlyph unicode2glyphDeutscheDruckschrift(UniChar c)
{
    if ( '0' < = c && c <= '9' )
        return c + (16 - '0');
    if ( 'A' <= c && c <= 'Z' )
        return c + (32 - 'A');
    if ( 'a' <= c && c <= 'z' )
        return c + (58 - 'a');
    return 0;
}

-(void)viewDidLoad
{
    [super viewDidLoad];
    ...
    [fontLabel setFontFromFile:@"DeutscheDruckschrift" ofType:@"ttf" mapping:unicode2glyphDeutscheDruckschrift];
    ...
}

See this github gist for the complete implementation.

The mapping from Unicode character codes to glyph indices (inside the font description) currently is done via a C mapping function you have to provide a function pointer for. A later implementation could map the unicode character code to the glyph name and leverage  CGFontGetGlyphWithGlyphName and render the custom mapping function obsolete.