UILabel with a (custom) CGFont

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
-(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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.

flattr this!

Comments 3

  1. f3r3nc wrote:

    Great post, amazed, no one has thanked you :)

    do you know any way for general string -> glyph conversion?

    Posted 02 Jun 2011 at 1:04 am
  2. Marcus Rohrmoser wrote:

    Thanks a lot!

    http://github.com/zynga/FontLabel does a complete char -> glyph.

    Posted 02 Jun 2011 at 12:07 pm
  3. f3r3nc wrote:

    this is great!
    thanks

    Posted 03 Jun 2011 at 5:26 pm

Post a Comment

Your email is never published nor shared. Required fields are marked *