From 9cd57b731ab1c666d4a1cb725538fdc137763d12 Mon Sep 17 00:00:00 2001 From: Starla Insigna Date: Sat, 30 Jul 2011 11:19:14 -0400 Subject: Initial commit (version 0.2.1) --- libs/cocos2d/CCLabelBMFont.m | 675 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 675 insertions(+) create mode 100755 libs/cocos2d/CCLabelBMFont.m (limited to 'libs/cocos2d/CCLabelBMFont.m') diff --git a/libs/cocos2d/CCLabelBMFont.m b/libs/cocos2d/CCLabelBMFont.m new file mode 100755 index 0000000..614cd6e --- /dev/null +++ b/libs/cocos2d/CCLabelBMFont.m @@ -0,0 +1,675 @@ +/* + * 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 -- cgit 1.4.1