Created
August 26, 2010 15:48
-
-
Save mro/551646 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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