Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
//
// UILabelWithCGFont.h
//
// Created by Marcus Rohrmoser on 26.08.10.
// Copyright 2010 Marcus Rohrmoser mobile Software. All rights reserved.
//
/** <a href="http://www.devx.com/tips/Tip/13829">Function pointer</a> for unicode -> glyph conversion.
*
* Example implementation:
* \code
* 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;
* }
* \endcode
*/
typedef CGGlyph (*t_UniChar2CGGlyph)(UniChar);
/** Overload UILabel::drawTextInRect: to render using a \c CGFont.
*
* Inspired by http://stackoverflow.com/questions/360751/can-i-embed-a-custom-font-in-an-iphone-application
*/
@interface UILabelWithCGFont : UILabel {
/// \c NULL falls back to normal \c UILabel behaviour. Set via UILabelWithCustomFont::setCGFont:mapping:
CGFontRef _CGFont;
/// <a href="http://www.devx.com/tips/Tip/13829">Function pointer</a> for unicode -> glyph conversion.
t_UniChar2CGGlyph _mapping;
}
@property (readonly, getter = CGFont) CGFontRef _CGFont;
@property (readonly, getter = mapping) t_UniChar2CGGlyph _mapping;
/**
* @param name The name of the resource file - see <tt>NSBundle::pathForResource:ofType:</tt>
* @param extension If extension is an empty string or nil, the extension is assumed not to exist and the file is the first file encountered that exactly matches name. - see <tt>NSBundle::pathForResource:ofType:</tt>
*/
+(CGFontRef)createFontFromFile:(NSString *)name ofType:(NSString *)type;
/** Set UILabelWithCustomFont::customFont - usually a ttf or otf font.
*
* Uses <tt>[[NSBundle bundleForClass:self.class] pathForResource:... ofType:...]</tt>.
*
* Call with all parameters \c NULL to return to default behaviour.
*
* @param font
* @param mapping C function - see t_UniChar2CGGlyph for an example implementation
*/
-(void)setCGFont:(CGFontRef)font mapping:(t_UniChar2CGGlyph)mapping;
/** Convenience wrapper for UILabelWithCustomFont::setCGFont:mapping:.
*
* Uses <tt>[[NSBundle bundleForClass:self.class] pathForResource:... ofType:...]</tt>.
*
* Call with all parameters \c nil / \c NULL to return to default behaviour.
*
* @param name The name of the resource file - see <tt>NSBundle::pathForResource:ofType:</tt>
* @param extension If extension is an empty string or nil, the extension is assumed not to exist and the file is the first file encountered that exactly matches name. - see <tt>NSBundle::pathForResource:ofType:</tt>
* @param mapping C function - see t_UniChar2CGGlyph for an example implementation
*/
-(void)setFontFromFile:(NSString *)name ofType:(NSString *)extension mapping:(t_UniChar2CGGlyph)mapping;
@end
//
// UILabelWithCGFont.m
//
// Created by Marcus Rohrmoser on 26.08.10.
// Copyright 2010 Marcus Rohrmoser mobile Software. All rights reserved.
//
#import "UILabelWithCGFont.h"
#ifndef MRLogD
#define MRLogD(x, ...) NSLog(@"%s " x, __FUNCTION__, ## __VA_ARGS__)
#endif
#ifndef DEBUG
#define DEBUG 1
#endif
#define DEBUG_LIST_FONT_GLYPH_NAMES 0
@implementation UILabelWithCGFont
+(CGFontRef)createFontFromFile:(NSString *)name ofType:(NSString *)type
{
MRLogD();
NSString *fontPath = [[NSBundle bundleForClass:self.class] pathForResource:name ofType:type];
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename([fontPath UTF8String]);
CGFontRef customFont = CGFontCreateWithDataProvider(fontDataProvider);
CGDataProviderRelease(fontDataProvider);
return customFont;
}
@synthesize _CGFont, _mapping;
-(void)dealloc
{
MRLogD();
if ( _CGFont != NULL )
CGFontRelease(_CGFont);
[super dealloc];
}
-(void)setFontFromFile:(NSString *)name ofType:(NSString *)type mapping:(t_UniChar2CGGlyph)mapping_
{
CGFontRef fo = [UILabelWithCGFont createFontFromFile:name ofType:type];
[self setCGFont:fo mapping:mapping_];
if ( fo != NULL )
CGFontRelease(fo);
}
-(void)setCGFont:(CGFontRef)font_ mapping:(t_UniChar2CGGlyph)mapping_;
{
if ( _CGFont != NULL )
CGFontRelease(_CGFont);
_CGFont = font_;
_mapping = mapping_;
[self setNeedsDisplay];
if ( _CGFont == NULL && _mapping == NULL )
return;
NSAssert(_CGFont != NULL, @"");
NSAssert(_mapping != NULL, @"Mapping must be specified");
CGFontRetain(_CGFont);
#if DEBUG
{
NSString *fontName = (NSString *)CGFontCopyFullName(_CGFont);
NSAssert(fontName != nil, @"Font Name Nil.");
NSAssert(fontName.length > 0, @"Font Name Empty.");
MRLogD(@"'%@'", fontName);
[fontName release];
}
#endif
#if DEBUG_LIST_FONT_GLYPH_NAMES
// list all glyph names within the font:
size_t count = CGFontGetNumberOfGlyphs(_CGFont);
NSMutableString *names = [NSMutableString stringWithCapacity:(count * 10)];
for ( CGGlyph i = 0; i < count; i++ ) {
NSString *name = (NSString *)CGFontCopyGlyphNameForGlyph(_CGFont, i);
// NSLog(@"\tglyph[%d] = '%@'", i, name);
[names appendFormat:@"@\"%@\" /* %d */, ", name, i];
[name release];
}
MRLogD(@"glyphs: %@", names);
#endif
#if !NS_BLOCK_ASSERTIONS
// @".notdef", @"space", @"exclam", @"quotedbl", @"numbersign", @"dollar", @"percent", @"ampersand", @"quotesingle", @"parenleft", @"parenright", @"asterisk", @"plus", @"comma", @"minus", @"period", @"zero", @"one", @"two", @"three", @"four", @"five", @"six", @"seven", @"eight", @"nine", @"colon", @"semicolon", @"less", @"equal", @"greater", @"question", @"A", @"B", @"C", @"D", @"E", @"F", @"G", @"H", @"I", @"J", @"K", @"L", @"M", @"N", @"O", @"P", @"Q", @"R", @"S", @"T", @"U", @"V", @"W", @"X", @"Y", @"Z", @"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j", @"k", @"l", @"m", @"n", @"o", @"p", @"q", @"r", @"s", @"t", @"u", @"v", @"w", @"x", @"y", @"z", @"bar", @"braceright", @"asciitilde", @"quotesinglbase", @"quotedblbase", @"ellipsis", @"dagger", @"quoteleft", @"quoteright", @"quotedblleft", @"quotedblright", @"Adieresis", @"Odieresis", @"Udieresis", @"germandbls", @"adieresis", @"odieresis", @"udieresis"
// check basic glyphs
NSArray *numbers = [NSArray arrayWithObjects:@"zero", @"one", @"two", @"three", @"four", @"five", @"six", @"seven", @"eight", @"nine", nil];
for ( int i = numbers.count - 1; i >= 0; i-- ) {
UniChar uc = '0' + i;
NSString *name = (NSString *)CGFontCopyGlyphNameForGlyph( _CGFont, _mapping(uc) );
NSString *msg = [NSString stringWithFormat:@"mismatch for UniChar %d", uc];
NSAssert([[numbers objectAtIndex:i] isEqualToString:name], msg);
[name release];
}
for ( UniChar uc = 'a'; uc <= 'z'; uc++ ) {
NSString *name = (NSString *)CGFontCopyGlyphNameForGlyph( _CGFont, _mapping(uc) );
NSString *msg = [NSString stringWithFormat:@"mismatch for UniChar %d", uc];
NSAssert(name.length == 1 && uc == [name characterAtIndex:0], msg);
[name release];
}
for ( UniChar uc = 'A'; uc <= 'Z'; uc++ ) {
NSString *name = (NSString *)CGFontCopyGlyphNameForGlyph( _CGFont, _mapping(uc) );
NSString *msg = [NSString stringWithFormat:@"mismatch for UniChar %d", uc];
NSAssert(name.length == 1 && uc == [name characterAtIndex:0], msg);
[name release];
}
#endif
}
-(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);
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment