/* * cocos2d for iPhone: http://www.cocos2d-iphone.org * * Copyright (c) 2008-2010 Ricardo Quesada * Copyright (c) 2011 Zynga Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * Portions of this code are based and inspired on: * http://www.71squared.co.uk/2009/04/iphone-game-programming-tutorial-4-bitmap-font-class * by Michael Daley * * * Use any of these editors to generate BMFonts: * http://glyphdesigner.71squared.com/ (Commercial, Mac OS X) * http://www.n4te.com/hiero/hiero.jnlp (Free, Java) * http://slick.cokeandcode.com/demos/hiero.jnlp (Free, Java) * http://www.angelcode.com/products/bmfont/ (Free, Windows only) */ #import "ccConfig.h" #import "CCLabelBMFont.h" #import "CCSprite.h" #import "CCDrawingPrimitives.h" #import "CCConfiguration.h" #import "Support/CCFileUtils.h" #import "Support/CGPointExtension.h" #import "Support/uthash.h" #pragma mark - #pragma mark FNTConfig Cache - free functions NSMutableDictionary *configurations = nil; CCBMFontConfiguration* FNTConfigLoadFile( NSString *fntFile) { CCBMFontConfiguration *ret = nil; if( configurations == nil ) configurations = [[NSMutableDictionary dictionaryWithCapacity:3] retain]; ret = [configurations objectForKey:fntFile]; if( ret == nil ) { ret = [CCBMFontConfiguration configurationWithFNTFile:fntFile]; [configurations setObject:ret forKey:fntFile]; } return ret; } void FNTConfigRemoveCache( void ) { [configurations removeAllObjects]; } #pragma mark - Hash Element // Equal function for targetSet. typedef struct _KerningHashElement { int key; // key for the hash. 16-bit for 1st element, 16-bit for 2nd element int amount; UT_hash_handle hh; } tKerningHashElement; #pragma mark - #pragma mark BitmapFontConfiguration @interface CCBMFontConfiguration (Private) -(void) parseConfigFile:(NSString*)controlFile; -(void) parseCharacterDefinition:(NSString*)line charDef:(ccBMFontDef*)characterDefinition; -(void) parseInfoArguments:(NSString*)line; -(void) parseCommonArguments:(NSString*)line; -(void) parseImageFileName:(NSString*)line fntFile:(NSString*)fntFile; -(void) parseKerningCapacity:(NSString*)line; -(void) parseKerningEntry:(NSString*)line; -(void) purgeKerningDictionary; @end @implementation CCBMFontConfiguration +(id) configurationWithFNTFile:(NSString*)FNTfile { return [[[self alloc] initWithFNTfile:FNTfile] autorelease]; } -(id) initWithFNTfile:(NSString*)fntFile { if((self=[super init])) { kerningDictionary_ = NULL; [self parseConfigFile:fntFile]; } return self; } - (void) dealloc { CCLOGINFO( @"cocos2d: deallocing %@", self); [self purgeKerningDictionary]; [atlasName_ release]; [super dealloc]; } - (NSString*) description { return [NSString stringWithFormat:@"<%@ = %08X | Kernings:%d | Image = %@>", [self class], self, HASH_COUNT(kerningDictionary_), atlasName_]; } -(void) purgeKerningDictionary { tKerningHashElement *current; while(kerningDictionary_) { current = kerningDictionary_; HASH_DEL(kerningDictionary_,current); free(current); } } - (void)parseConfigFile:(NSString*)fntFile { NSString *fullpath = [CCFileUtils fullPathFromRelativePath:fntFile]; NSError *error; NSString *contents = [NSString stringWithContentsOfFile:fullpath encoding:NSUTF8StringEncoding error:&error]; NSAssert1( contents, @"cocos2d: Error parsing FNTfile: %@", error); // Move all lines in the string, which are denoted by \n, into an array NSArray *lines = [[NSArray alloc] initWithArray:[contents componentsSeparatedByString:@"\n"]]; // Create an enumerator which we can use to move through the lines read from the control file NSEnumerator *nse = [lines objectEnumerator]; // Create a holder for each line we are going to work with NSString *line; // Loop through all the lines in the lines array processing each one while( (line = [nse nextObject]) ) { // parse spacing / padding if([line hasPrefix:@"info face"]) { // XXX: info parsing is incomplete // Not needed for the Hiero editors, but needed for the AngelCode editor // [self parseInfoArguments:line]; } // Check to see if the start of the line is something we are interested in else if([line hasPrefix:@"common lineHeight"]) { [self parseCommonArguments:line]; } else if([line hasPrefix:@"page id"]) { [self parseImageFileName:line fntFile:fntFile]; } else if([line hasPrefix:@"chars c"]) { // Ignore this line } else if([line hasPrefix:@"char"]) { // Parse the current line and create a new CharDef ccBMFontDef characterDefinition; [self parseCharacterDefinition:line charDef:&characterDefinition]; // Add the CharDef returned to the charArray BMFontArray_[ characterDefinition.charID ] = characterDefinition; } else if([line hasPrefix:@"kernings count"]) { [self parseKerningCapacity:line]; } else if([line hasPrefix:@"kerning first"]) { [self parseKerningEntry:line]; } } // Finished with lines so release it [lines release]; } -(void) parseImageFileName:(NSString*)line fntFile:(NSString*)fntFile { NSString *propertyValue = nil; // Break the values for this line up using = NSArray *values = [line componentsSeparatedByString:@"="]; // Get the enumerator for the array of components which has been created NSEnumerator *nse = [values objectEnumerator]; // We need to move past the first entry in the array before we start assigning values [nse nextObject]; // page ID. Sanity check propertyValue = [nse nextObject]; NSAssert( [propertyValue intValue] == 0, @"XXX: LabelBMFont only supports 1 page"); // file propertyValue = [nse nextObject]; NSArray *array = [propertyValue componentsSeparatedByString:@"\""]; propertyValue = [array objectAtIndex:1]; NSAssert(propertyValue,@"LabelBMFont file could not be found"); // Supports subdirectories NSString *dir = [fntFile stringByDeletingLastPathComponent]; atlasName_ = [dir stringByAppendingPathComponent:propertyValue]; [atlasName_ retain]; } -(void) parseInfoArguments:(NSString*)line { // // possible lines to parse: // info face="Script" size=32 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,4,3,2 spacing=0,0 outline=0 // info face="Cracked" size=36 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 // NSArray *values = [line componentsSeparatedByString:@"="]; NSEnumerator *nse = [values objectEnumerator]; NSString *propertyValue = nil; // We need to move past the first entry in the array before we start assigning values [nse nextObject]; // face (ignore) [nse nextObject]; // size (ignore) [nse nextObject]; // bold (ignore) [nse nextObject]; // italic (ignore) [nse nextObject]; // charset (ignore) [nse nextObject]; // unicode (ignore) [nse nextObject]; // strechH (ignore) [nse nextObject]; // smooth (ignore) [nse nextObject]; // aa (ignore) [nse nextObject]; // padding (ignore) propertyValue = [nse nextObject]; { NSArray *paddingValues = [propertyValue componentsSeparatedByString:@","]; NSEnumerator *paddingEnum = [paddingValues objectEnumerator]; // padding top propertyValue = [paddingEnum nextObject]; padding_.top = [propertyValue intValue]; // padding right propertyValue = [paddingEnum nextObject]; padding_.right = [propertyValue intValue]; // padding bottom propertyValue = [paddingEnum nextObject]; padding_.bottom = [propertyValue intValue]; // padding left propertyValue = [paddingEnum nextObject]; padding_.left = [propertyValue intValue]; CCLOG(@"cocos2d: padding: %d,%d,%d,%d", padding_.left, padding_.top, padding_.right, padding_.bottom); } // spacing (ignore) [nse nextObject]; } -(void) parseCommonArguments:(NSString*)line { // // line to parse: // common lineHeight=104 base=26 scaleW=1024 scaleH=512 pages=1 packed=0 // NSArray *values = [line componentsSeparatedByString:@"="]; NSEnumerator *nse = [values objectEnumerator]; NSString *propertyValue = nil; // We need to move past the first entry in the array before we start assigning values [nse nextObject]; // Character ID propertyValue = [nse nextObject]; commonHeight_ = [propertyValue intValue]; // base (ignore) [nse nextObject]; // scaleW. sanity check propertyValue = [nse nextObject]; NSAssert( [propertyValue intValue] <= [[CCConfiguration sharedConfiguration] maxTextureSize], @"CCLabelBMFont: page can't be larger than supported"); // scaleH. sanity check propertyValue = [nse nextObject]; NSAssert( [propertyValue intValue] <= [[CCConfiguration sharedConfiguration] maxTextureSize], @"CCLabelBMFont: page can't be larger than supported"); // pages. sanity check propertyValue = [nse nextObject]; NSAssert( [propertyValue intValue] == 1, @"CCBitfontAtlas: only supports 1 page"); // packed (ignore) What does this mean ?? } - (void)parseCharacterDefinition:(NSString*)line charDef:(ccBMFontDef*)characterDefinition { // Break the values for this line up using = NSArray *values = [line componentsSeparatedByString:@"="]; NSEnumerator *nse = [values objectEnumerator]; NSString *propertyValue; // We need to move past the first entry in the array before we start assigning values [nse nextObject]; // Character ID propertyValue = [nse nextObject]; propertyValue = [propertyValue substringToIndex: [propertyValue rangeOfString: @" "].location]; characterDefinition->charID = [propertyValue intValue]; NSAssert(characterDefinition->charID < kCCBMFontMaxChars, @"BitmpaFontAtlas: CharID bigger than supported"); // Character x propertyValue = [nse nextObject]; characterDefinition->rect.origin.x = [propertyValue intValue]; // Character y propertyValue = [nse nextObject]; characterDefinition->rect.origin.y = [propertyValue intValue]; // Character width propertyValue = [nse nextObject]; characterDefinition->rect.size.width = [propertyValue intValue]; // Character height propertyValue = [nse nextObject]; characterDefinition->rect.size.height = [propertyValue intValue]; // Character xoffset propertyValue = [nse nextObject]; characterDefinition->xOffset = [propertyValue intValue]; // Character yoffset propertyValue = [nse nextObject]; characterDefinition->yOffset = [propertyValue intValue]; // Character xadvance propertyValue = [nse nextObject]; characterDefinition->xAdvance = [propertyValue intValue]; } -(void) parseKerningCapacity:(NSString*) line { // When using uthash there is not need to parse the capacity. // NSAssert(!kerningDictionary, @"dictionary already initialized"); // // // Break the values for this line up using = // NSArray *values = [line componentsSeparatedByString:@"="]; // NSEnumerator *nse = [values objectEnumerator]; // NSString *propertyValue; // // // We need to move past the first entry in the array before we start assigning values // [nse nextObject]; // // // count // propertyValue = [nse nextObject]; // int capacity = [propertyValue intValue]; // // if( capacity != -1 ) // kerningDictionary = ccHashSetNew(capacity, targetSetEql); } -(void) parseKerningEntry:(NSString*) line { NSArray *values = [line componentsSeparatedByString:@"="]; NSEnumerator *nse = [values objectEnumerator]; NSString *propertyValue; // We need to move past the first entry in the array before we start assigning values [nse nextObject]; // first propertyValue = [nse nextObject]; int first = [propertyValue intValue]; // second propertyValue = [nse nextObject]; int second = [propertyValue intValue]; // second propertyValue = [nse nextObject]; int amount = [propertyValue intValue]; tKerningHashElement *element = calloc( sizeof( *element ), 1 ); element->amount = amount; element->key = (first<<16) | (second&0xffff); HASH_ADD_INT(kerningDictionary_,key, element); } @end #pragma mark - #pragma mark CCLabelBMFont @interface CCLabelBMFont (Private) -(NSString*) atlasNameFromFntFile:(NSString*)fntFile; -(int) kerningAmountForFirst:(unichar)first second:(unichar)second; @end @implementation CCLabelBMFont @synthesize opacity = opacity_, color = color_; #pragma mark LabelBMFont - Purge Cache +(void) purgeCachedData { FNTConfigRemoveCache(); } #pragma mark LabelBMFont - Creation & Init +(id) labelWithString:(NSString *)string fntFile:(NSString *)fntFile { return [[[self alloc] initWithString:string fntFile:fntFile] autorelease]; } // XXX - deprecated - Will be removed in 1.0.1 +(id) bitmapFontAtlasWithString:(NSString*)string fntFile:(NSString*)fntFile { return [self labelWithString:string fntFile:fntFile]; } -(id) initWithString:(NSString*)theString fntFile:(NSString*)fntFile { [configuration_ release]; // allow re-init configuration_ = FNTConfigLoadFile(fntFile); [configuration_ retain]; NSAssert( configuration_, @"Error creating config for LabelBMFont"); if ((self=[super initWithFile:configuration_->atlasName_ capacity:[theString length]])) { opacity_ = 255; color_ = ccWHITE; contentSize_ = CGSizeZero; opacityModifyRGB_ = [[textureAtlas_ texture] hasPremultipliedAlpha]; anchorPoint_ = ccp(0.5f, 0.5f); [self setString:theString]; } return self; } -(void) dealloc { [string_ release]; [configuration_ release]; [super dealloc]; } #pragma mark LabelBMFont - Atlas generation -(int) kerningAmountForFirst:(unichar)first second:(unichar)second { int ret = 0; unsigned int key = (first<<16) | (second & 0xffff); if( configuration_->kerningDictionary_ ) { tKerningHashElement *element = NULL; HASH_FIND_INT(configuration_->kerningDictionary_, &key, element); if(element) ret = element->amount; } return ret; } -(void) createFontChars { NSInteger nextFontPositionX = 0; NSInteger nextFontPositionY = 0; unichar prev = -1; NSInteger kerningAmount = 0; CGSize tmpSize = CGSizeZero; NSInteger longestLine = 0; NSUInteger totalHeight = 0; NSUInteger quantityOfLines = 1; NSUInteger stringLen = [string_ length]; if( ! stringLen ) return; // quantity of lines NEEDS to be calculated before parsing the lines, // since the Y position needs to be calcualted before hand for(NSUInteger i=0; i < stringLen-1;i++) { unichar c = [string_ characterAtIndex:i]; if( c=='\n') quantityOfLines++; } totalHeight = configuration_->commonHeight_ * quantityOfLines; nextFontPositionY = -(configuration_->commonHeight_ - configuration_->commonHeight_*quantityOfLines); for(NSUInteger i=0; icommonHeight_; continue; } kerningAmount = [self kerningAmountForFirst:prev second:c]; ccBMFontDef fontDef = configuration_->BMFontArray_[c]; CGRect rect = fontDef.rect; CCSprite *fontChar; fontChar = (CCSprite*) [self getChildByTag:i]; if( ! fontChar ) { fontChar = [[CCSprite alloc] initWithBatchNode:self rectInPixels:rect]; [self addChild:fontChar z:0 tag:i]; [fontChar release]; } else { // reusing fonts [fontChar setTextureRectInPixels:rect rotated:NO untrimmedSize:rect.size]; // restore to default in case they were modified fontChar.visible = YES; fontChar.opacity = 255; } float yOffset = configuration_->commonHeight_ - fontDef.yOffset; fontChar.positionInPixels = ccp( (float)nextFontPositionX + fontDef.xOffset + fontDef.rect.size.width*0.5f + kerningAmount, (float)nextFontPositionY + yOffset - rect.size.height*0.5f ); // update kerning nextFontPositionX += configuration_->BMFontArray_[c].xAdvance + kerningAmount; prev = c; // Apply label properties [fontChar setOpacityModifyRGB:opacityModifyRGB_]; // Color MUST be set before opacity, since opacity might change color if OpacityModifyRGB is on [fontChar setColor:color_]; // only apply opacity if it is different than 255 ) // to prevent modifying the color too (issue #610) if( opacity_ != 255 ) [fontChar setOpacity: opacity_]; if (longestLine < nextFontPositionX) longestLine = nextFontPositionX; } tmpSize.width = longestLine; tmpSize.height = totalHeight; [self setContentSizeInPixels:tmpSize]; } #pragma mark LabelBMFont - CCLabelProtocol protocol - (void) setString:(NSString*) newString { [string_ release]; string_ = [newString copy]; CCNode *child; CCARRAY_FOREACH(children_, child) child.visible = NO; [self createFontChars]; } -(NSString*) string { return string_; } -(void) setCString:(char*)label { [self setString:[NSString stringWithUTF8String:label]]; } #pragma mark LabelBMFont - CCRGBAProtocol protocol -(void) setColor:(ccColor3B)color { color_ = color; CCSprite *child; CCARRAY_FOREACH(children_, child) [child setColor:color_]; } -(void) setOpacity:(GLubyte)opacity { opacity_ = opacity; id child; CCARRAY_FOREACH(children_, child) [child setOpacity:opacity_]; } -(void) setOpacityModifyRGB:(BOOL)modify { opacityModifyRGB_ = modify; id child; CCARRAY_FOREACH(children_, child) [child setOpacityModifyRGB:modify]; } -(BOOL) doesOpacityModifyRGB { return opacityModifyRGB_; } #pragma mark LabelBMFont - AnchorPoint -(void) setAnchorPoint:(CGPoint)point { if( ! CGPointEqualToPoint(point, anchorPoint_) ) { [super setAnchorPoint:point]; [self createFontChars]; } } #pragma mark LabelBMFont - Debug draw #if CC_LABELBMFONT_DEBUG_DRAW -(void) draw { [super draw]; CGSize s = [self contentSize]; CGPoint vertices[4]={ ccp(0,0),ccp(s.width,0), ccp(s.width,s.height),ccp(0,s.height), }; ccDrawPoly(vertices, 4, YES); } #endif // CC_LABELBMFONT_DEBUG_DRAW @end