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/FontLabel/FontLabel.h | 44 ++ libs/FontLabel/FontLabel.m | 195 +++++++ libs/FontLabel/FontLabelStringDrawing.h | 69 +++ libs/FontLabel/FontLabelStringDrawing.m | 892 ++++++++++++++++++++++++++++++ libs/FontLabel/FontManager.h | 85 +++ libs/FontLabel/FontManager.m | 123 ++++ libs/FontLabel/ZAttributedString.h | 77 +++ libs/FontLabel/ZAttributedString.m | 597 ++++++++++++++++++++ libs/FontLabel/ZAttributedStringPrivate.h | 24 + libs/FontLabel/ZFont.h | 47 ++ libs/FontLabel/ZFont.m | 170 ++++++ 11 files changed, 2323 insertions(+) create mode 100755 libs/FontLabel/FontLabel.h create mode 100755 libs/FontLabel/FontLabel.m create mode 100755 libs/FontLabel/FontLabelStringDrawing.h create mode 100755 libs/FontLabel/FontLabelStringDrawing.m create mode 100755 libs/FontLabel/FontManager.h create mode 100755 libs/FontLabel/FontManager.m create mode 100755 libs/FontLabel/ZAttributedString.h create mode 100755 libs/FontLabel/ZAttributedString.m create mode 100755 libs/FontLabel/ZAttributedStringPrivate.h create mode 100755 libs/FontLabel/ZFont.h create mode 100755 libs/FontLabel/ZFont.m (limited to 'libs/FontLabel') diff --git a/libs/FontLabel/FontLabel.h b/libs/FontLabel/FontLabel.h new file mode 100755 index 0000000..6de9c2c --- /dev/null +++ b/libs/FontLabel/FontLabel.h @@ -0,0 +1,44 @@ +// +// FontLabel.h +// FontLabel +// +// Created by Kevin Ballard on 5/8/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +@class ZFont; +@class ZAttributedString; + +@interface FontLabel : UILabel { + void *reserved; // works around a bug in UILabel + ZFont *zFont; + ZAttributedString *zAttributedText; +} +@property (nonatomic, setter=setCGFont:) CGFontRef cgFont __AVAILABILITY_INTERNAL_DEPRECATED; +@property (nonatomic, assign) CGFloat pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +@property (nonatomic, retain, setter=setZFont:) ZFont *zFont; +// if attributedText is nil, fall back on using the inherited UILabel properties +// if attributedText is non-nil, the font/text/textColor +// in addition, adjustsFontSizeToFitWidth does not work with attributed text +@property (nonatomic, copy) ZAttributedString *zAttributedText; +// -initWithFrame:fontName:pointSize: uses FontManager to look up the font name +- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize; +- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font; +- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +@end diff --git a/libs/FontLabel/FontLabel.m b/libs/FontLabel/FontLabel.m new file mode 100755 index 0000000..58975b1 --- /dev/null +++ b/libs/FontLabel/FontLabel.m @@ -0,0 +1,195 @@ +// +// FontLabel.m +// FontLabel +// +// Created by Kevin Ballard on 5/8/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "FontLabel.h" +#import "FontManager.h" +#import "FontLabelStringDrawing.h" +#import "ZFont.h" + +@interface ZFont (ZFontPrivate) +@property (nonatomic, readonly) CGFloat ratio; +@end + +@implementation FontLabel +@synthesize zFont; +@synthesize zAttributedText; + +- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize { + return [self initWithFrame:frame zFont:[[FontManager sharedManager] zFontWithName:fontName pointSize:pointSize]]; +} + +- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font { + if ((self = [super initWithFrame:frame])) { + zFont = [font retain]; + } + return self; +} + +- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize { + return [self initWithFrame:frame zFont:[ZFont fontWithCGFont:font size:pointSize]]; +} + +- (CGFontRef)cgFont { + return self.zFont.cgFont; +} + +- (void)setCGFont:(CGFontRef)font { + if (self.zFont.cgFont != font) { + self.zFont = [ZFont fontWithCGFont:font size:self.zFont.pointSize]; + } +} + +- (CGFloat)pointSize { + return self.zFont.pointSize; +} + +- (void)setPointSize:(CGFloat)pointSize { + if (self.zFont.pointSize != pointSize) { + self.zFont = [ZFont fontWithCGFont:self.zFont.cgFont size:pointSize]; + } +} + +- (void)setZAttributedText:(ZAttributedString *)attStr { + if (zAttributedText != attStr) { + [zAttributedText release]; + zAttributedText = [attStr copy]; + [self setNeedsDisplay]; + } +} + +- (void)drawTextInRect:(CGRect)rect { + if (self.zFont == NULL && self.zAttributedText == nil) { + [super drawTextInRect:rect]; + return; + } + + if (self.zAttributedText == nil) { + // this method is documented as setting the text color for us, but that doesn't appear to be the case + if (self.highlighted) { + [(self.highlightedTextColor ?: [UIColor whiteColor]) setFill]; + } else { + [(self.textColor ?: [UIColor blackColor]) setFill]; + } + + ZFont *actualFont = self.zFont; + CGSize origSize = rect.size; + if (self.numberOfLines == 1) { + origSize.height = actualFont.leading; + CGPoint point = CGPointMake(rect.origin.x, + rect.origin.y + roundf(((rect.size.height - actualFont.leading) / 2.0f))); + CGSize size = [self.text sizeWithZFont:actualFont]; + if (self.adjustsFontSizeToFitWidth && self.minimumFontSize < actualFont.pointSize) { + if (size.width > origSize.width) { + CGFloat desiredRatio = (origSize.width * actualFont.ratio) / size.width; + CGFloat desiredPointSize = desiredRatio * actualFont.pointSize / actualFont.ratio; + actualFont = [actualFont fontWithSize:MAX(MAX(desiredPointSize, self.minimumFontSize), 1.0f)]; + size = [self.text sizeWithZFont:actualFont]; + } + if (!CGSizeEqualToSize(origSize, size)) { + switch (self.baselineAdjustment) { + case UIBaselineAdjustmentAlignCenters: + point.y += roundf((origSize.height - size.height) / 2.0f); + break; + case UIBaselineAdjustmentAlignBaselines: + point.y += (self.zFont.ascender - actualFont.ascender); + break; + case UIBaselineAdjustmentNone: + break; + } + } + } + size.width = MIN(size.width, origSize.width); + // adjust the point for alignment + switch (self.textAlignment) { + case UITextAlignmentLeft: + break; + case UITextAlignmentCenter: + point.x += (origSize.width - size.width) / 2.0f; + break; + case UITextAlignmentRight: + point.x += origSize.width - size.width; + break; + } + [self.text drawAtPoint:point forWidth:size.width withZFont:actualFont lineBreakMode:self.lineBreakMode]; + } else { + CGSize size = [self.text sizeWithZFont:actualFont constrainedToSize:origSize lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines]; + CGPoint point = rect.origin; + point.y += roundf((rect.size.height - size.height) / 2.0f); + rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)}; + [self.text drawInRect:rect withZFont:actualFont lineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines]; + } + } else { + ZAttributedString *attStr = self.zAttributedText; + if (self.highlighted) { + // modify the string to change the base color + ZMutableAttributedString *mutStr = [[attStr mutableCopy] autorelease]; + NSRange activeRange = NSMakeRange(0, attStr.length); + while (activeRange.length > 0) { + NSRange effective; + UIColor *color = [attStr attribute:ZForegroundColorAttributeName atIndex:activeRange.location + longestEffectiveRange:&effective inRange:activeRange]; + if (color == nil) { + [mutStr addAttribute:ZForegroundColorAttributeName value:[UIColor whiteColor] range:effective]; + } + activeRange.location += effective.length, activeRange.length -= effective.length; + } + attStr = mutStr; + } + CGSize size = [attStr sizeConstrainedToSize:rect.size lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines]; + CGPoint point = rect.origin; + point.y += roundf((rect.size.height - size.height) / 2.0f); + rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)}; + [attStr drawInRect:rect withLineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines]; + } +} + +- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines { + if (self.zFont == NULL && self.zAttributedText == nil) { + return [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines]; + } + + if (numberOfLines == 1) { + // if numberOfLines == 1 we need to use the version that converts spaces + CGSize size; + if (self.zAttributedText == nil) { + size = [self.text sizeWithZFont:self.zFont]; + } else { + size = [self.zAttributedText size]; + } + bounds.size.width = MIN(bounds.size.width, size.width); + bounds.size.height = MIN(bounds.size.height, size.height); + } else { + if (numberOfLines > 0) bounds.size.height = MIN(bounds.size.height, self.zFont.leading * numberOfLines); + if (self.zAttributedText == nil) { + bounds.size = [self.text sizeWithZFont:self.zFont constrainedToSize:bounds.size lineBreakMode:self.lineBreakMode]; + } else { + bounds.size = [self.zAttributedText sizeConstrainedToSize:bounds.size lineBreakMode:self.lineBreakMode]; + } + } + return bounds; +} + +- (void)dealloc { + [zFont release]; + [zAttributedText release]; + [super dealloc]; +} +@end diff --git a/libs/FontLabel/FontLabelStringDrawing.h b/libs/FontLabel/FontLabelStringDrawing.h new file mode 100755 index 0000000..821da22 --- /dev/null +++ b/libs/FontLabel/FontLabelStringDrawing.h @@ -0,0 +1,69 @@ +// +// FontLabelStringDrawing.h +// FontLabel +// +// Created by Kevin Ballard on 5/5/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import "ZAttributedString.h" + +@class ZFont; + +@interface NSString (FontLabelStringDrawing) +// CGFontRef-based methods +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size + lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize + lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED; +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize + lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment __AVAILABILITY_INTERNAL_DEPRECATED; + +// ZFont-based methods +- (CGSize)sizeWithZFont:(ZFont *)font; +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size; +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode + numberOfLines:(NSUInteger)numberOfLines; +- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font; +- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font; +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode + alignment:(UITextAlignment)alignment; +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode + alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines; +@end + +@interface ZAttributedString (ZAttributedStringDrawing) +- (CGSize)size; +- (CGSize)sizeConstrainedToSize:(CGSize)size; +- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode + numberOfLines:(NSUInteger)numberOfLines; +- (CGSize)drawAtPoint:(CGPoint)point; +- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)drawInRect:(CGRect)rect; +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode; +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment; +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment + numberOfLines:(NSUInteger)numberOfLines; +@end diff --git a/libs/FontLabel/FontLabelStringDrawing.m b/libs/FontLabel/FontLabelStringDrawing.m new file mode 100755 index 0000000..2907372 --- /dev/null +++ b/libs/FontLabel/FontLabelStringDrawing.m @@ -0,0 +1,892 @@ +// +// FontLabelStringDrawing.m +// FontLabel +// +// Created by Kevin Ballard on 5/5/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "FontLabelStringDrawing.h" +#import "ZFont.h" +#import "ZAttributedStringPrivate.h" + +@interface ZFont (ZFontPrivate) +@property (nonatomic, readonly) CGFloat ratio; +@end + +#define kUnicodeHighSurrogateStart 0xD800 +#define kUnicodeHighSurrogateEnd 0xDBFF +#define kUnicodeHighSurrogateMask kUnicodeHighSurrogateStart +#define kUnicodeLowSurrogateStart 0xDC00 +#define kUnicodeLowSurrogateEnd 0xDFFF +#define kUnicodeLowSurrogateMask kUnicodeLowSurrogateStart +#define kUnicodeSurrogateTypeMask 0xFC00 +#define UnicharIsHighSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeHighSurrogateMask) +#define UnicharIsLowSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeLowSurrogateMask) +#define ConvertSurrogatePairToUTF32(high, low) ((UInt32)((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)) + +typedef enum { + kFontTableFormat4 = 4, + kFontTableFormat12 = 12, +} FontTableFormat; + +typedef struct fontTable { + NSUInteger retainCount; + CFDataRef cmapTable; + FontTableFormat format; + union { + struct { + UInt16 segCountX2; + UInt16 *endCodes; + UInt16 *startCodes; + UInt16 *idDeltas; + UInt16 *idRangeOffsets; + } format4; + struct { + UInt32 nGroups; + struct { + UInt32 startCharCode; + UInt32 endCharCode; + UInt32 startGlyphCode; + } *groups; + } format12; + } cmap; +} fontTable; + +static FontTableFormat supportedFormats[] = { kFontTableFormat4, kFontTableFormat12 }; +static size_t supportedFormatsCount = sizeof(supportedFormats) / sizeof(FontTableFormat); + +static fontTable *newFontTable(CFDataRef cmapTable, FontTableFormat format) { + fontTable *table = (struct fontTable *)malloc(sizeof(struct fontTable)); + table->retainCount = 1; + table->cmapTable = CFRetain(cmapTable); + table->format = format; + return table; +} + +static fontTable *retainFontTable(fontTable *table) { + if (table != NULL) { + table->retainCount++; + } + return table; +} + +static void releaseFontTable(fontTable *table) { + if (table != NULL) { + if (table->retainCount <= 1) { + CFRelease(table->cmapTable); + free(table); + } else { + table->retainCount--; + } + } +} + +static const void *fontTableRetainCallback(CFAllocatorRef allocator, const void *value) { + return retainFontTable((fontTable *)value); +} + +static void fontTableReleaseCallback(CFAllocatorRef allocator, const void *value) { + releaseFontTable((fontTable *)value); +} + +static const CFDictionaryValueCallBacks kFontTableDictionaryValueCallBacks = { + .version = 0, + .retain = &fontTableRetainCallback, + .release = &fontTableReleaseCallback, + .copyDescription = NULL, + .equal = NULL +}; + +// read the cmap table from the font +// we only know how to understand some of the table formats at the moment +static fontTable *readFontTableFromCGFont(CGFontRef font) { + CFDataRef cmapTable = CGFontCopyTableForTag(font, 'cmap'); + NSCAssert1(cmapTable != NULL, @"CGFontCopyTableForTag returned NULL for 'cmap' tag in font %@", + (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)")); + const UInt8 * const bytes = CFDataGetBytePtr(cmapTable); + NSCAssert1(OSReadBigInt16(bytes, 0) == 0, @"cmap table for font %@ has bad version number", + (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)")); + UInt16 numberOfSubtables = OSReadBigInt16(bytes, 2); + const UInt8 *unicodeSubtable = NULL; + //UInt16 unicodeSubtablePlatformID; + UInt16 unicodeSubtablePlatformSpecificID; + FontTableFormat unicodeSubtableFormat; + const UInt8 * const encodingSubtables = &bytes[4]; + for (UInt16 i = 0; i < numberOfSubtables; i++) { + const UInt8 * const encodingSubtable = &encodingSubtables[8 * i]; + UInt16 platformID = OSReadBigInt16(encodingSubtable, 0); + UInt16 platformSpecificID = OSReadBigInt16(encodingSubtable, 2); + // find the best subtable + // best is defined by a combination of encoding and format + // At the moment we only support format 4, so ignore all other format tables + // We prefer platformID == 0, but we will also accept Microsoft's unicode format + if (platformID == 0 || (platformID == 3 && platformSpecificID == 1)) { + BOOL preferred = NO; + if (unicodeSubtable == NULL) { + preferred = YES; + } else if (platformID == 0 && platformSpecificID > unicodeSubtablePlatformSpecificID) { + preferred = YES; + } + if (preferred) { + UInt32 offset = OSReadBigInt32(encodingSubtable, 4); + const UInt8 *subtable = &bytes[offset]; + UInt16 format = OSReadBigInt16(subtable, 0); + for (size_t i = 0; i < supportedFormatsCount; i++) { + if (format == supportedFormats[i]) { + if (format >= 8) { + // the version is a fixed-point + UInt16 formatFrac = OSReadBigInt16(subtable, 2); + if (formatFrac != 0) { + // all the current formats with a Fixed version are always *.0 + continue; + } + } + unicodeSubtable = subtable; + //unicodeSubtablePlatformID = platformID; + unicodeSubtablePlatformSpecificID = platformSpecificID; + unicodeSubtableFormat = format; + break; + } + } + } + } + } + fontTable *table = NULL; + if (unicodeSubtable != NULL) { + table = newFontTable(cmapTable, unicodeSubtableFormat); + switch (unicodeSubtableFormat) { + case kFontTableFormat4: + // subtable format 4 + //UInt16 length = OSReadBigInt16(unicodeSubtable, 2); + //UInt16 language = OSReadBigInt16(unicodeSubtable, 4); + table->cmap.format4.segCountX2 = OSReadBigInt16(unicodeSubtable, 6); + //UInt16 searchRange = OSReadBigInt16(unicodeSubtable, 8); + //UInt16 entrySelector = OSReadBigInt16(unicodeSubtable, 10); + //UInt16 rangeShift = OSReadBigInt16(unicodeSubtable, 12); + table->cmap.format4.endCodes = (UInt16*)&unicodeSubtable[14]; + table->cmap.format4.startCodes = (UInt16*)&((UInt8*)table->cmap.format4.endCodes)[table->cmap.format4.segCountX2+2]; + table->cmap.format4.idDeltas = (UInt16*)&((UInt8*)table->cmap.format4.startCodes)[table->cmap.format4.segCountX2]; + table->cmap.format4.idRangeOffsets = (UInt16*)&((UInt8*)table->cmap.format4.idDeltas)[table->cmap.format4.segCountX2]; + //UInt16 *glyphIndexArray = &idRangeOffsets[segCountX2]; + break; + case kFontTableFormat12: + table->cmap.format12.nGroups = OSReadBigInt32(unicodeSubtable, 12); + table->cmap.format12.groups = (void *)&unicodeSubtable[16]; + break; + default: + releaseFontTable(table); + table = NULL; + } + } + CFRelease(cmapTable); + return table; +} + +// outGlyphs must be at least size n +static void mapCharactersToGlyphsInFont(const fontTable *table, unichar characters[], size_t charLen, CGGlyph outGlyphs[], size_t *outGlyphLen) { + if (table != NULL) { + NSUInteger j = 0; + switch (table->format) { + case kFontTableFormat4: { + for (NSUInteger i = 0; i < charLen; i++, j++) { + unichar c = characters[i]; + UInt16 segOffset; + BOOL foundSegment = NO; + for (segOffset = 0; segOffset < table->cmap.format4.segCountX2; segOffset += 2) { + UInt16 endCode = OSReadBigInt16(table->cmap.format4.endCodes, segOffset); + if (endCode >= c) { + foundSegment = YES; + break; + } + } + if (!foundSegment) { + // no segment + // this is an invalid font + outGlyphs[j] = 0; + } else { + UInt16 startCode = OSReadBigInt16(table->cmap.format4.startCodes, segOffset); + if (!(startCode <= c)) { + // the code falls in a hole between segments + outGlyphs[j] = 0; + } else { + UInt16 idRangeOffset = OSReadBigInt16(table->cmap.format4.idRangeOffsets, segOffset); + if (idRangeOffset == 0) { + UInt16 idDelta = OSReadBigInt16(table->cmap.format4.idDeltas, segOffset); + outGlyphs[j] = (c + idDelta) % 65536; + } else { + // use the glyphIndexArray + UInt16 glyphOffset = idRangeOffset + 2 * (c - startCode); + outGlyphs[j] = OSReadBigInt16(&((UInt8*)table->cmap.format4.idRangeOffsets)[segOffset], glyphOffset); + } + } + } + } + break; + } + case kFontTableFormat12: { + UInt32 lastSegment = UINT32_MAX; + for (NSUInteger i = 0; i < charLen; i++, j++) { + unichar c = characters[i]; + UInt32 c32 = c; + if (UnicharIsHighSurrogate(c)) { + if (i+1 < charLen) { // do we have another character after this one? + unichar cc = characters[i+1]; + if (UnicharIsLowSurrogate(cc)) { + c32 = ConvertSurrogatePairToUTF32(c, cc); + i++; + } + } + } + // Start the heuristic search + // If this is an ASCII char, just do a linear search + // Otherwise do a hinted, modified binary search + // Start the first pivot at the last range found + // And when moving the pivot, limit the movement by increasing + // powers of two. This should help with locality + __typeof__(table->cmap.format12.groups[0]) *foundGroup = NULL; + if (c32 <= 0x7F) { + // ASCII + for (UInt32 idx = 0; idx < table->cmap.format12.nGroups; idx++) { + __typeof__(table->cmap.format12.groups[idx]) *group = &table->cmap.format12.groups[idx]; + if (c32 < OSSwapBigToHostInt32(group->startCharCode)) { + // we've fallen into a hole + break; + } else if (c32 <= OSSwapBigToHostInt32(group->endCharCode)) { + // this is the range + foundGroup = group; + break; + } + } + } else { + // heuristic search + UInt32 maxJump = (lastSegment == UINT32_MAX ? UINT32_MAX / 2 : 8); + UInt32 lowIdx = 0, highIdx = table->cmap.format12.nGroups; // highIdx is the first invalid idx + UInt32 pivot = (lastSegment == UINT32_MAX ? lowIdx + (highIdx - lowIdx) / 2 : lastSegment); + while (highIdx > lowIdx) { + __typeof__(table->cmap.format12.groups[pivot]) *group = &table->cmap.format12.groups[pivot]; + if (c32 < OSSwapBigToHostInt32(group->startCharCode)) { + highIdx = pivot; + } else if (c32 > OSSwapBigToHostInt32(group->endCharCode)) { + lowIdx = pivot + 1; + } else { + // we've hit the range + foundGroup = group; + break; + } + if (highIdx - lowIdx > maxJump * 2) { + if (highIdx == pivot) { + pivot -= maxJump; + } else { + pivot += maxJump; + } + maxJump *= 2; + } else { + pivot = lowIdx + (highIdx - lowIdx) / 2; + } + } + if (foundGroup != NULL) lastSegment = pivot; + } + if (foundGroup == NULL) { + outGlyphs[j] = 0; + } else { + outGlyphs[j] = (CGGlyph)(OSSwapBigToHostInt32(foundGroup->startGlyphCode) + + (c32 - OSSwapBigToHostInt32(foundGroup->startCharCode))); + } + } + break; + } + } + if (outGlyphLen != NULL) *outGlyphLen = j; + } else { + // we have no table, so just null out the glyphs + bzero(outGlyphs, charLen*sizeof(CGGlyph)); + if (outGlyphLen != NULL) *outGlyphLen = 0; + } +} + +static BOOL mapGlyphsToAdvancesInFont(ZFont *font, size_t n, CGGlyph glyphs[], CGFloat outAdvances[]) { + int advances[n]; + if (CGFontGetGlyphAdvances(font.cgFont, glyphs, n, advances)) { + CGFloat ratio = font.ratio; + + for (size_t i = 0; i < n; i++) { + outAdvances[i] = advances[i]*ratio; + } + return YES; + } else { + bzero(outAdvances, n*sizeof(CGFloat)); + } + return NO; +} + +static id getValueOrDefaultForRun(ZAttributeRun *run, NSString *key) { + id value = [run.attributes objectForKey:key]; + if (value == nil) { + static NSDictionary *defaultValues = nil; + if (defaultValues == nil) { + defaultValues = [[NSDictionary alloc] initWithObjectsAndKeys: + [ZFont fontWithUIFont:[UIFont systemFontOfSize:12]], ZFontAttributeName, + [UIColor blackColor], ZForegroundColorAttributeName, + [UIColor clearColor], ZBackgroundColorAttributeName, + [NSNumber numberWithInt:ZUnderlineStyleNone], ZUnderlineStyleAttributeName, + nil]; + } + value = [defaultValues objectForKey:key]; + } + return value; +} + +static void readRunInformation(NSArray *attributes, NSUInteger len, CFMutableDictionaryRef fontTableMap, + NSUInteger index, ZAttributeRun **currentRun, NSUInteger *nextRunStart, + ZFont **currentFont, fontTable **currentTable) { + *currentRun = [attributes objectAtIndex:index]; + *nextRunStart = ([attributes count] > index+1 ? [[attributes objectAtIndex:index+1] index] : len); + *currentFont = getValueOrDefaultForRun(*currentRun, ZFontAttributeName); + if (!CFDictionaryGetValueIfPresent(fontTableMap, (*currentFont).cgFont, (const void **)currentTable)) { + *currentTable = readFontTableFromCGFont((*currentFont).cgFont); + CFDictionarySetValue(fontTableMap, (*currentFont).cgFont, *currentTable); + releaseFontTable(*currentTable); + } +} + +static CGSize drawOrSizeTextConstrainedToSize(BOOL performDraw, NSString *string, NSArray *attributes, CGSize constrainedSize, NSUInteger maxLines, + UILineBreakMode lineBreakMode, UITextAlignment alignment, BOOL ignoreColor) { + NSUInteger len = [string length]; + NSUInteger idx = 0; + CGPoint drawPoint = CGPointZero; + CGSize retValue = CGSizeZero; + CGContextRef ctx = (performDraw ? UIGraphicsGetCurrentContext() : NULL); + + BOOL convertNewlines = (maxLines == 1); + + // Extract the characters from the string + // Convert newlines to spaces if necessary + unichar *characters = (unichar *)malloc(sizeof(unichar) * len); + if (convertNewlines) { + NSCharacterSet *charset = [NSCharacterSet newlineCharacterSet]; + NSRange range = NSMakeRange(0, len); + size_t cIdx = 0; + while (range.length > 0) { + NSRange newlineRange = [string rangeOfCharacterFromSet:charset options:0 range:range]; + if (newlineRange.location == NSNotFound) { + [string getCharacters:&characters[cIdx] range:range]; + cIdx += range.length; + break; + } else { + NSUInteger delta = newlineRange.location - range.location; + if (newlineRange.location > range.location) { + [string getCharacters:&characters[cIdx] range:NSMakeRange(range.location, delta)]; + } + cIdx += delta; + characters[cIdx] = (unichar)' '; + cIdx++; + delta += newlineRange.length; + range.location += delta, range.length -= delta; + if (newlineRange.length == 1 && range.length >= 1 && + [string characterAtIndex:newlineRange.location] == (unichar)'\r' && + [string characterAtIndex:range.location] == (unichar)'\n') { + // CRLF sequence, skip the LF + range.location += 1, range.length -= 1; + } + } + } + len = cIdx; + } else { + [string getCharacters:characters range:NSMakeRange(0, len)]; + } + + // Create storage for glyphs and advances + CGGlyph *glyphs; + CGFloat *advances; + { + NSUInteger maxRunLength = 0; + ZAttributeRun *a = [attributes objectAtIndex:0]; + for (NSUInteger i = 1; i < [attributes count]; i++) { + ZAttributeRun *b = [attributes objectAtIndex:i]; + maxRunLength = MAX(maxRunLength, b.index - a.index); + a = b; + } + maxRunLength = MAX(maxRunLength, len - a.index); + maxRunLength++; // for a potential ellipsis + glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * maxRunLength); + advances = (CGFloat *)malloc(sizeof(CGFloat) * maxRunLength); + } + + // Use this table to cache all fontTable objects + CFMutableDictionaryRef fontTableMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, + &kFontTableDictionaryValueCallBacks); + + // Fetch initial style values + NSUInteger currentRunIdx = 0; + ZAttributeRun *currentRun; + NSUInteger nextRunStart; + ZFont *currentFont; + fontTable *currentTable; + +#define READ_RUN() readRunInformation(attributes, len, fontTableMap, \ + currentRunIdx, ¤tRun, &nextRunStart, \ + ¤tFont, ¤tTable) + + READ_RUN(); + + // fetch the glyphs for the first run + size_t glyphCount; + NSUInteger glyphIdx; + +#define READ_GLYPHS() do { \ + mapCharactersToGlyphsInFont(currentTable, &characters[currentRun.index], (nextRunStart - currentRun.index), glyphs, &glyphCount); \ + mapGlyphsToAdvancesInFont(currentFont, (nextRunStart - currentRun.index), glyphs, advances); \ + glyphIdx = 0; \ + } while (0) + + READ_GLYPHS(); + + NSMutableCharacterSet *alphaCharset = [NSMutableCharacterSet alphanumericCharacterSet]; + [alphaCharset addCharactersInString:@"([{'\"\u2019\u02BC"]; + + // scan left-to-right looking for newlines or until we hit the width constraint + // When we hit a wrapping point, calculate truncation as follows: + // If we have room to draw at least one more character on the next line, no truncation + // Otherwise apply the truncation algorithm to the current line. + // After calculating any truncation, draw. + // Each time we hit the end of an attribute run, calculate the new font and make sure + // it fits (vertically) within the size constraint. If not, truncate this line. + // When we draw, iterate over the attribute runs for this line and draw each run separately + BOOL lastLine = NO; // used to indicate truncation and to stop the iterating + NSUInteger lineCount = 1; + while (idx < len && !lastLine) { + if (maxLines > 0 && lineCount == maxLines) { + lastLine = YES; + } + // scan left-to-right + struct { + NSUInteger index; + NSUInteger glyphIndex; + NSUInteger currentRunIdx; + } indexCache = { idx, glyphIdx, currentRunIdx }; + CGSize lineSize = CGSizeMake(0, currentFont.leading); + CGFloat lineAscender = currentFont.ascender; + struct { + NSUInteger index; + NSUInteger glyphIndex; + NSUInteger currentRunIdx; + CGSize lineSize; + } lastWrapCache = {0, 0, 0, CGSizeZero}; + BOOL inAlpha = NO; // used for calculating wrap points + + BOOL finishLine = NO; + for (;idx <= len && !finishLine;) { + NSUInteger skipCount = 0; + if (idx == len) { + finishLine = YES; + lastLine = YES; + } else { + if (idx >= nextRunStart) { + // cycle the font and table and grab the next set of glyphs + do { + currentRunIdx++; + READ_RUN(); + } while (idx >= nextRunStart); + READ_GLYPHS(); + // re-scan the characters to synchronize the glyph index + for (NSUInteger j = currentRun.index; j < idx; j++) { + if (UnicharIsHighSurrogate(characters[j]) && j+1 lineSize.height) { + lineSize.height = currentFont.leading; + if (retValue.height + currentFont.ascender > constrainedSize.height) { + lastLine = YES; + finishLine = YES; + } + } + lineAscender = MAX(lineAscender, currentFont.ascender); + } + unichar c = characters[idx]; + // Mark a wrap point before spaces and after any stretch of non-alpha characters + BOOL markWrap = NO; + if (c == (unichar)' ') { + markWrap = YES; + } else if ([alphaCharset characterIsMember:c]) { + if (!inAlpha) { + markWrap = YES; + inAlpha = YES; + } + } else { + inAlpha = NO; + } + if (markWrap) { + lastWrapCache = (__typeof__(lastWrapCache)){ + .index = idx, + .glyphIndex = glyphIdx, + .currentRunIdx = currentRunIdx, + .lineSize = lineSize + }; + } + // process the line + if (c == (unichar)'\n' || c == 0x0085) { // U+0085 is the NEXT_LINE unicode character + finishLine = YES; + skipCount = 1; + } else if (c == (unichar)'\r') { + finishLine = YES; + // check for CRLF + if (idx+1 < len && characters[idx+1] == (unichar)'\n') { + skipCount = 2; + } else { + skipCount = 1; + } + } else if (lineSize.width + advances[glyphIdx] > constrainedSize.width) { + finishLine = YES; + if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) { + lastLine = YES; + } + // walk backwards if wrapping is necessary + if (lastWrapCache.index > indexCache.index && lineBreakMode != UILineBreakModeCharacterWrap && + (!lastLine || lineBreakMode != UILineBreakModeClip)) { + // we're doing some sort of word wrapping + idx = lastWrapCache.index; + lineSize = lastWrapCache.lineSize; + if (!lastLine) { + // re-check if this is the last line + if (lastWrapCache.currentRunIdx != currentRunIdx) { + currentRunIdx = lastWrapCache.currentRunIdx; + READ_RUN(); + READ_GLYPHS(); + } + if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) { + lastLine = YES; + } + } + glyphIdx = lastWrapCache.glyphIndex; + // skip any spaces + for (NSUInteger j = idx; j < len && characters[j] == (unichar)' '; j++) { + skipCount++; + } + } + } + } + if (finishLine) { + // TODO: support head/middle truncation + if (lastLine && idx < len && lineBreakMode == UILineBreakModeTailTruncation) { + // truncate + unichar ellipsis = 0x2026; // ellipsis (…) + CGGlyph ellipsisGlyph; + mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL); + CGFloat ellipsisWidth; + mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth); + while ((idx - indexCache.index) > 1 && lineSize.width + ellipsisWidth > constrainedSize.width) { + // we have more than 1 character and we're too wide, so back up + idx--; + if (UnicharIsHighSurrogate(characters[idx]) && UnicharIsLowSurrogate(characters[idx+1])) { + idx--; + } + if (idx < currentRun.index) { + ZFont *oldFont = currentFont; + do { + currentRunIdx--; + READ_RUN(); + } while (idx < currentRun.index); + READ_GLYPHS(); + glyphIdx = glyphCount-1; + if (oldFont != currentFont) { + mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL); + mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth); + } + } else { + glyphIdx--; + } + lineSize.width -= advances[glyphIdx]; + } + // skip any spaces before truncating + while ((idx - indexCache.index) > 1 && characters[idx-1] == (unichar)' ') { + idx--; + if (idx < currentRun.index) { + currentRunIdx--; + READ_RUN(); + READ_GLYPHS(); + glyphIdx = glyphCount-1; + } else { + glyphIdx--; + } + lineSize.width -= advances[glyphIdx]; + } + lineSize.width += ellipsisWidth; + glyphs[glyphIdx] = ellipsisGlyph; + idx++; + glyphIdx++; + } + retValue.width = MAX(retValue.width, lineSize.width); + retValue.height += lineSize.height; + + // draw + if (performDraw) { + switch (alignment) { + case UITextAlignmentLeft: + drawPoint.x = 0; + break; + case UITextAlignmentCenter: + drawPoint.x = (constrainedSize.width - lineSize.width) / 2.0f; + break; + case UITextAlignmentRight: + drawPoint.x = constrainedSize.width - lineSize.width; + break; + } + NSUInteger stopGlyphIdx = glyphIdx; + NSUInteger lastRunIdx = currentRunIdx; + NSUInteger stopCharIdx = idx; + idx = indexCache.index; + if (currentRunIdx != indexCache.currentRunIdx) { + currentRunIdx = indexCache.currentRunIdx; + READ_RUN(); + READ_GLYPHS(); + } + glyphIdx = indexCache.glyphIndex; + for (NSUInteger drawIdx = currentRunIdx; drawIdx <= lastRunIdx; drawIdx++) { + if (drawIdx != currentRunIdx) { + currentRunIdx = drawIdx; + READ_RUN(); + READ_GLYPHS(); + } + NSUInteger numGlyphs; + if (drawIdx == lastRunIdx) { + numGlyphs = stopGlyphIdx - glyphIdx; + idx = stopCharIdx; + } else { + numGlyphs = glyphCount - glyphIdx; + idx = nextRunStart; + } + CGContextSetFont(ctx, currentFont.cgFont); + CGContextSetFontSize(ctx, currentFont.pointSize); + // calculate the fragment size + CGFloat fragmentWidth = 0; + for (NSUInteger g = 0; g < numGlyphs; g++) { + fragmentWidth += advances[glyphIdx + g]; + } + + if (!ignoreColor) { + UIColor *foregroundColor = getValueOrDefaultForRun(currentRun, ZForegroundColorAttributeName); + UIColor *backgroundColor = getValueOrDefaultForRun(currentRun, ZBackgroundColorAttributeName); + if (backgroundColor != nil && ![backgroundColor isEqual:[UIColor clearColor]]) { + [backgroundColor setFill]; + UIRectFillUsingBlendMode((CGRect){ drawPoint, { fragmentWidth, lineSize.height } }, kCGBlendModeNormal); + } + [foregroundColor setFill]; + } + + CGContextShowGlyphsAtPoint(ctx, drawPoint.x, drawPoint.y + lineAscender, &glyphs[glyphIdx], numGlyphs); + NSNumber *underlineStyle = getValueOrDefaultForRun(currentRun, ZUnderlineStyleAttributeName); + if ([underlineStyle integerValue] & ZUnderlineStyleMask) { + // we only support single for the time being + UIRectFill(CGRectMake(drawPoint.x, drawPoint.y + lineAscender, fragmentWidth, 1)); + } + drawPoint.x += fragmentWidth; + glyphIdx += numGlyphs; + } + drawPoint.y += lineSize.height; + } + idx += skipCount; + glyphIdx += skipCount; + lineCount++; + } else { + lineSize.width += advances[glyphIdx]; + glyphIdx++; + idx++; + if (idx < len && UnicharIsHighSurrogate(characters[idx-1]) && UnicharIsLowSurrogate(characters[idx])) { + // skip the second half of the surrogate pair + idx++; + } + } + } + } + CFRelease(fontTableMap); + free(glyphs); + free(advances); + free(characters); + +#undef READ_GLYPHS +#undef READ_RUN + + return retValue; +} + +static NSArray *attributeRunForFont(ZFont *font) { + return [NSArray arrayWithObject:[ZAttributeRun attributeRunWithIndex:0 + attributes:[NSDictionary dictionaryWithObject:font + forKey:ZFontAttributeName]]]; +} + +static CGSize drawTextInRect(CGRect rect, NSString *text, NSArray *attributes, UILineBreakMode lineBreakMode, + UITextAlignment alignment, NSUInteger numberOfLines, BOOL ignoreColor) { + CGContextRef ctx = UIGraphicsGetCurrentContext(); + + CGContextSaveGState(ctx); + + // flip it upside-down because our 0,0 is upper-left, whereas ttfs are for screens where 0,0 is lower-left + CGAffineTransform textTransform = CGAffineTransformMake(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f); + CGContextSetTextMatrix(ctx, textTransform); + + CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y); + + CGContextSetTextDrawingMode(ctx, kCGTextFill); + CGSize size = drawOrSizeTextConstrainedToSize(YES, text, attributes, rect.size, numberOfLines, lineBreakMode, alignment, ignoreColor); + + CGContextRestoreGState(ctx); + + return size; +} + +@implementation NSString (FontLabelStringDrawing) +// CGFontRef-based methods +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize { + return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize]]; +} + +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size { + return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size]; +} + +- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size + lineBreakMode:(UILineBreakMode)lineBreakMode { + return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size lineBreakMode:lineBreakMode]; +} + +- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize { + return [self drawAtPoint:point withZFont:[ZFont fontWithCGFont:font size:pointSize]]; +} + +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize { + return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize]]; +} + +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize lineBreakMode:(UILineBreakMode)lineBreakMode { + return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode]; +} + +- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize + lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment { + return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode alignment:alignment]; +} + +// ZFont-based methods +- (CGSize)sizeWithZFont:(ZFont *)font { + CGSize size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1, + UILineBreakModeClip, UITextAlignmentLeft, YES); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size { + return [self sizeWithZFont:font constrainedToSize:size lineBreakMode:UILineBreakModeWordWrap]; +} + +/* + According to experimentation with UIStringDrawing, this can actually return a CGSize whose height is greater + than the one passed in. The two cases are as follows: + 1. If the given size parameter's height is smaller than a single line, the returned value will + be the height of one line. + 2. If the given size parameter's height falls between multiples of a line height, and the wrapped string + actually extends past the size.height, and the difference between size.height and the previous multiple + of a line height is >= the font's ascender, then the returned size's height is extended to the next line. + To put it simply, if the baseline point of a given line falls in the given size, the entire line will + be present in the output size. + */ +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode { + size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, 0, lineBreakMode, UITextAlignmentLeft, YES); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode + numberOfLines:(NSUInteger)numberOfLines { + size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, numberOfLines, lineBreakMode, UITextAlignmentLeft, YES); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font { + return [self drawAtPoint:point forWidth:CGFLOAT_MAX withZFont:font lineBreakMode:UILineBreakModeClip]; +} + +- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode { + return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self, attributeRunForFont(font), lineBreakMode, UITextAlignmentLeft, 1, YES); +} + +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font { + return [self drawInRect:rect withZFont:font lineBreakMode:UILineBreakModeWordWrap]; +} + +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode { + return [self drawInRect:rect withZFont:font lineBreakMode:lineBreakMode alignment:UITextAlignmentLeft]; +} + +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode + alignment:(UITextAlignment)alignment { + return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, 0, YES); +} + +- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode + alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines { + return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, numberOfLines, YES); +} +@end + +@implementation ZAttributedString (ZAttributedStringDrawing) +- (CGSize)size { + CGSize size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1, + UILineBreakModeClip, UITextAlignmentLeft, NO); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)sizeConstrainedToSize:(CGSize)size { + return [self sizeConstrainedToSize:size lineBreakMode:UILineBreakModeWordWrap]; +} + +- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode { + size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, 0, lineBreakMode, UITextAlignmentLeft, NO); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode + numberOfLines:(NSUInteger)numberOfLines { + size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, numberOfLines, lineBreakMode, UITextAlignmentLeft, NO); + return CGSizeMake(ceilf(size.width), ceilf(size.height)); +} + +- (CGSize)drawAtPoint:(CGPoint)point { + return [self drawAtPoint:point forWidth:CGFLOAT_MAX lineBreakMode:UILineBreakModeClip]; +} + +- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode { + return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self.string, self.attributes, lineBreakMode, UITextAlignmentLeft, 1, NO); +} + +- (CGSize)drawInRect:(CGRect)rect { + return [self drawInRect:rect withLineBreakMode:UILineBreakModeWordWrap]; +} + +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode { + return [self drawInRect:rect withLineBreakMode:lineBreakMode alignment:UITextAlignmentLeft]; +} + +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment { + return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, 0, NO); +} + +- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment + numberOfLines:(NSUInteger)numberOfLines { + return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, numberOfLines, NO); +} +@end diff --git a/libs/FontLabel/FontManager.h b/libs/FontLabel/FontManager.h new file mode 100755 index 0000000..1592b8a --- /dev/null +++ b/libs/FontLabel/FontManager.h @@ -0,0 +1,85 @@ +// +// FontManager.h +// FontLabel +// +// Created by Kevin Ballard on 5/5/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +@class ZFont; + +@interface FontManager : NSObject { + CFMutableDictionaryRef fonts; + NSMutableDictionary *urls; +} ++ (FontManager *)sharedManager; +/*! + @method + @abstract Loads a TTF font from the main bundle + @param filename The name of the font file to load (with or without extension). + @return YES if the font was loaded, NO if an error occurred + @discussion If the font has already been loaded, this method does nothing and returns YES. + This method first attempts to load the font by appending .ttf to the filename. + If that file does not exist, it tries the filename exactly as given. +*/ +- (BOOL)loadFont:(NSString *)filename; +/*! + @method + @abstract Loads a font from the given file URL + @param url A file URL that points to a font file + @return YES if the font was loaded, NO if an error occurred + @discussion If the font has already been loaded, this method does nothing and returns YES. +*/ +- (BOOL)loadFontURL:(NSURL *)url; +/*! + @method + @abstract Returns the loaded font with the given filename + @param filename The name of the font file that was given to -loadFont: + @return A CGFontRef, or NULL if the specified font cannot be found + @discussion If the font has not been loaded yet, -loadFont: will be + called with the given name first. +*/ +- (CGFontRef)fontWithName:(NSString *)filename __AVAILABILITY_INTERNAL_DEPRECATED; +/*! + @method + @abstract Returns a ZFont object corresponding to the loaded font with the given filename and point size + @param filename The name of the font file that was given to -loadFont: + @param pointSize The point size of the font + @return A ZFont, or NULL if the specified font cannot be found + @discussion If the font has not been loaded yet, -loadFont: will be + called with the given name first. +*/ +- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize; +/*! + @method + @abstract Returns a ZFont object corresponding to the loaded font with the given file URL and point size + @param url A file URL that points to a font file + @param pointSize The point size of the font + @return A ZFont, or NULL if the specified font cannot be loaded + @discussion If the font has not been loaded yet, -loadFontURL: will be called with the given URL first. +*/ +- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize; +/*! + @method + @abstract Returns a CFArrayRef of all loaded CGFont objects + @return A CFArrayRef of all loaded CGFont objects + @description You are responsible for releasing the CFArrayRef +*/ +- (CFArrayRef)copyAllFonts; +@end diff --git a/libs/FontLabel/FontManager.m b/libs/FontLabel/FontManager.m new file mode 100755 index 0000000..12eac2d --- /dev/null +++ b/libs/FontLabel/FontManager.m @@ -0,0 +1,123 @@ +// +// FontManager.m +// FontLabel +// +// Created by Kevin Ballard on 5/5/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "FontManager.h" +#import "ZFont.h" + +static FontManager *sharedFontManager = nil; + +@implementation FontManager ++ (FontManager *)sharedManager { + @synchronized(self) { + if (sharedFontManager == nil) { + sharedFontManager = [[self alloc] init]; + } + } + return sharedFontManager; +} + +- (id)init { + if ((self = [super init])) { + fonts = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + urls = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (BOOL)loadFont:(NSString *)filename { + NSString *fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:@"ttf"]; + if (fontPath == nil) { + fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:nil]; + } + if (fontPath == nil) return NO; + + NSURL *url = [NSURL fileURLWithPath:fontPath]; + if ([self loadFontURL:url]) { + [urls setObject:url forKey:filename]; + return YES; + } + return NO; +} + +- (BOOL)loadFontURL:(NSURL *)url { + CGDataProviderRef fontDataProvider = CGDataProviderCreateWithURL((CFURLRef)url); + if (fontDataProvider == NULL) return NO; + CGFontRef newFont = CGFontCreateWithDataProvider(fontDataProvider); + CGDataProviderRelease(fontDataProvider); + if (newFont == NULL) return NO; + + CFDictionarySetValue(fonts, url, newFont); + CGFontRelease(newFont); + return YES; +} + +- (CGFontRef)fontWithName:(NSString *)filename { + CGFontRef font = NULL; + NSURL *url = [urls objectForKey:filename]; + if (url == nil && [self loadFont:filename]) { + url = [urls objectForKey:filename]; + } + if (url != nil) { + font = (CGFontRef)CFDictionaryGetValue(fonts, url); + } + return font; +} + +- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize { + NSURL *url = [urls objectForKey:filename]; + if (url == nil && [self loadFont:filename]) { + url = [urls objectForKey:filename]; + } + if (url != nil) { + CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url); + if (cgFont != NULL) { + return [ZFont fontWithCGFont:cgFont size:pointSize]; + } + } + return nil; +} + +- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize { + CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url); + if (cgFont == NULL && [self loadFontURL:url]) { + cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url); + } + if (cgFont != NULL) { + return [ZFont fontWithCGFont:cgFont size:pointSize]; + } + return nil; +} + +- (CFArrayRef)copyAllFonts { + CFIndex count = CFDictionaryGetCount(fonts); + CGFontRef *values = (CGFontRef *)malloc(sizeof(CGFontRef) * count); + CFDictionaryGetKeysAndValues(fonts, NULL, (const void **)values); + CFArrayRef array = CFArrayCreate(NULL, (const void **)values, count, &kCFTypeArrayCallBacks); + free(values); + return array; +} + +- (void)dealloc { + CFRelease(fonts); + [urls release]; + [super dealloc]; +} +@end diff --git a/libs/FontLabel/ZAttributedString.h b/libs/FontLabel/ZAttributedString.h new file mode 100755 index 0000000..e194c81 --- /dev/null +++ b/libs/FontLabel/ZAttributedString.h @@ -0,0 +1,77 @@ +// +// ZAttributedString.h +// FontLabel +// +// Created by Kevin Ballard on 9/22/09. +// Copyright 2009 Zynga Game Networks. All rights reserved. +// + +#import + +#if NS_BLOCKS_AVAILABLE +#define Z_BLOCKS 1 +#else +// set this to 1 if you are using PLBlocks +#define Z_BLOCKS 0 +#endif + +#if Z_BLOCKS +enum { + ZAttributedStringEnumerationReverse = (1UL << 1), + ZAttributedStringEnumerationLongestEffectiveRangeNotRequired = (1UL << 20) +}; +typedef NSUInteger ZAttributedStringEnumerationOptions; +#endif + +@interface ZAttributedString : NSObject { + NSMutableString *_buffer; + NSMutableArray *_attributes; +} +@property (nonatomic, readonly) NSUInteger length; +@property (nonatomic, readonly) NSString *string; +- (id)initWithAttributedString:(ZAttributedString *)attr; +- (id)initWithString:(NSString *)str; +- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes; +- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange; +- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit; +- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange; +- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange; +- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit; +#if Z_BLOCKS +- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts + usingBlock:(void (^)(id value, NSRange range, BOOL *stop))block; +- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts + usingBlock:(void (^)(NSDictionary *attrs, NSRange range, BOOL *stop))block; +#endif +- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString; +@end + +@interface ZMutableAttributedString : ZAttributedString { +} +- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range; +- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range; +- (void)appendAttributedString:(ZAttributedString *)str; +- (void)deleteCharactersInRange:(NSRange)range; +- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx; +- (void)removeAttribute:(NSString *)name range:(NSRange)range; +- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str; +- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str; +- (void)setAttributedString:(ZAttributedString *)str; +- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range; +@end + +extern NSString * const ZFontAttributeName; +extern NSString * const ZForegroundColorAttributeName; +extern NSString * const ZBackgroundColorAttributeName; +extern NSString * const ZUnderlineStyleAttributeName; + +enum { + ZUnderlineStyleNone = 0x00, + ZUnderlineStyleSingle = 0x01 +}; +#define ZUnderlineStyleMask 0x00FF + +enum { + ZUnderlinePatternSolid = 0x0000 +}; +#define ZUnderlinePatternMask 0xFF00 diff --git a/libs/FontLabel/ZAttributedString.m b/libs/FontLabel/ZAttributedString.m new file mode 100755 index 0000000..a4163bc --- /dev/null +++ b/libs/FontLabel/ZAttributedString.m @@ -0,0 +1,597 @@ +// +// ZAttributedString.m +// FontLabel +// +// Created by Kevin Ballard on 9/22/09. +// Copyright 2009 Zynga Game Networks. All rights reserved. +// + +#import "ZAttributedString.h" +#import "ZAttributedStringPrivate.h" + +@interface ZAttributedString () +- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index; +- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName; +- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange + inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName; +@end + +@interface ZAttributedString () +@property (nonatomic, readonly) NSArray *attributes; +@end + +@implementation ZAttributedString +@synthesize string = _buffer; +@synthesize attributes = _attributes; + +- (id)initWithAttributedString:(ZAttributedString *)attr { + NSParameterAssert(attr != nil); + if ((self = [super init])) { + _buffer = [attr->_buffer mutableCopy]; + _attributes = [[NSMutableArray alloc] initWithArray:attr->_attributes copyItems:YES]; + } + return self; +} + +- (id)initWithString:(NSString *)str { + return [self initWithString:str attributes:nil]; +} + +- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes { + if ((self = [super init])) { + _buffer = [str mutableCopy]; + _attributes = [[NSMutableArray alloc] initWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil]; + } + return self; +} + +- (id)init { + return [self initWithString:@"" attributes:nil]; +} + +- (id)initWithCoder:(NSCoder *)decoder { + if ((self = [super init])) { + _buffer = [[decoder decodeObjectForKey:@"buffer"] mutableCopy]; + _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_buffer forKey:@"buffer"]; + [aCoder encodeObject:_attributes forKey:@"attributes"]; +} + +- (id)copyWithZone:(NSZone *)zone { + return [self retain]; +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + return [(ZMutableAttributedString *)[ZMutableAttributedString allocWithZone:zone] initWithAttributedString:self]; +} + +- (NSUInteger)length { + return [_buffer length]; +} + +- (NSString *)description { + NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]*2]; + NSRange range = NSMakeRange(0, 0); + for (NSUInteger i = 0; i <= [_attributes count]; i++) { + range.location = NSMaxRange(range); + ZAttributeRun *run; + if (i < [_attributes count]) { + run = [_attributes objectAtIndex:i]; + range.length = run.index - range.location; + } else { + run = nil; + range.length = [_buffer length] - range.location; + } + if (range.length > 0) { + [components addObject:[NSString stringWithFormat:@"\"%@\"", [_buffer substringWithRange:range]]]; + } + if (run != nil) { + NSMutableArray *attrDesc = [NSMutableArray arrayWithCapacity:[run.attributes count]]; + for (id key in run.attributes) { + [attrDesc addObject:[NSString stringWithFormat:@"%@: %@", key, [run.attributes objectForKey:key]]]; + } + [components addObject:[NSString stringWithFormat:@"{%@}", [attrDesc componentsJoinedByString:@", "]]]; + } + } + return [NSString stringWithFormat:@"%@", [components componentsJoinedByString:@" "]]; +} + +- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange { + NSParameterAssert(attributeName != nil); + return [[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:attributeName] objectForKey:attributeName]; +} + +- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit { + NSParameterAssert(attributeName != nil); + return [[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:attributeName] objectForKey:attributeName]; +} + +- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange { + if (NSMaxRange(aRange) > [_buffer length]) { + @throw [NSException exceptionWithName:NSRangeException reason:@"range was outisde of the attributed string" userInfo:nil]; + } + ZMutableAttributedString *newStr = [self mutableCopy]; + if (aRange.location > 0) { + [newStr deleteCharactersInRange:NSMakeRange(0, aRange.location)]; + } + if (NSMaxRange(aRange) < [_buffer length]) { + [newStr deleteCharactersInRange:NSMakeRange(aRange.length, [_buffer length] - NSMaxRange(aRange))]; + } + return [newStr autorelease]; +} + +- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange { + return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:nil]]; +} + +- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit { + return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:nil]]; +} + +#if Z_BLOCKS +// Warning: this code has not been tested. The only guarantee is that it compiles. +- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts + usingBlock:(void (^)(id, NSRange, BOOL*))block { + if (opts & ZAttributedStringEnumerationLongestEffectiveRangeNotRequired) { + [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { + id value = [attrs objectForKey:attrName]; + if (value != nil) { + block(value, range, stop); + } + }]; + } else { + __block id oldValue = nil; + __block NSRange effectiveRange = NSMakeRange(0, 0); + [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { + id value = [attrs objectForKey:attrName]; + if (oldValue == nil) { + oldValue = value; + effectiveRange = range; + } else if (value != nil && [oldValue isEqual:value]) { + // combine the attributes + effectiveRange = NSUnionRange(effectiveRange, range); + } else { + BOOL innerStop = NO; + block(oldValue, effectiveRange, &innerStop); + if (innerStop) { + *stop = YES; + oldValue = nil; + } else { + oldValue = value; + } + } + }]; + if (oldValue != nil) { + BOOL innerStop = NO; // necessary for the block, but unused + block(oldValue, effectiveRange, &innerStop); + } + } +} + +- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts + usingBlock:(void (^)(NSDictionary*, NSRange, BOOL*))block { + // copy the attributes so we can mutate the string if necessary during enumeration + // also clip the array during copy to only the subarray of attributes that cover the requested range + NSArray *attrs; + if (NSEqualRanges(enumerationRange, NSMakeRange(0, 0))) { + attrs = [NSArray arrayWithArray:_attributes]; + } else { + // in this binary search, last is the first run after the range + NSUInteger first = 0, last = [_attributes count]; + while (last > first+1) { + NSUInteger pivot = (last + first) / 2; + ZAttributeRun *run = [_attributes objectAtIndex:pivot]; + if (run.index < enumerationRange.location) { + first = pivot; + } else if (run.index >= NSMaxRange(enumerationRange)) { + last = pivot; + } + } + attrs = [_attributes subarrayWithRange:NSMakeRange(first, last-first)]; + } + if (opts & ZAttributedStringEnumerationReverse) { + NSUInteger end = [_buffer length]; + for (ZAttributeRun *run in [attrs reverseObjectEnumerator]) { + BOOL stop = NO; + NSUInteger start = run.index; + // clip to enumerationRange + start = MAX(start, enumerationRange.location); + end = MIN(end, NSMaxRange(enumerationRange)); + block(run.attributes, NSMakeRange(start, end - start), &stop); + if (stop) break; + end = run.index; + } + } else { + NSUInteger start = 0; + ZAttributeRun *run = [attrs objectAtIndex:0]; + NSInteger offset = 0; + NSInteger oldLength = [_buffer length]; + for (NSUInteger i = 1;;i++) { + NSUInteger end; + if (i >= [attrs count]) { + end = oldLength; + } else { + end = [[attrs objectAtIndex:i] index]; + } + BOOL stop = NO; + NSUInteger clippedStart = MAX(start, enumerationRange.location); + NSUInteger clippedEnd = MIN(end, NSMaxRange(enumerationRange)); + block(run.attributes, NSMakeRange(clippedStart + offset, clippedEnd - start), &stop); + if (stop || i >= [attrs count]) break; + start = end; + NSUInteger newLength = [_buffer length]; + offset += (newLength - oldLength); + oldLength = newLength; + } + } +} +#endif + +- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString { + return ([_buffer isEqualToString:otherString->_buffer] && [_attributes isEqualToArray:otherString->_attributes]); +} + +- (BOOL)isEqual:(id)object { + return [object isKindOfClass:[ZAttributedString class]] && [self isEqualToAttributedString:(ZAttributedString *)object]; +} + +#pragma mark - + +- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index { + NSUInteger first = 0, last = [_attributes count]; + while (last > first + 1) { + NSUInteger pivot = (last + first) / 2; + ZAttributeRun *run = [_attributes objectAtIndex:pivot]; + if (run.index > index) { + last = pivot; + } else if (run.index < index) { + first = pivot; + } else { + first = pivot; + break; + } + } + return first; +} + +- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName { + if (index >= [_buffer length]) { + @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil]; + } + NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index]; + ZAttributeRun *run = [_attributes objectAtIndex:runIndex]; + if (aRange != NULL) { + aRange->location = run.index; + runIndex++; + if (runIndex < [_attributes count]) { + aRange->length = [[_attributes objectAtIndex:runIndex] index] - aRange->location; + } else { + aRange->length = [_buffer length] - aRange->location; + } + } + return run.attributes; +} +- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange + inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName { + if (index >= [_buffer length]) { + @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil]; + } else if (NSMaxRange(rangeLimit) > [_buffer length]) { + @throw [NSException exceptionWithName:NSRangeException reason:@"rangeLimit beyond range of attributed string" userInfo:nil]; + } + NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index]; + ZAttributeRun *run = [_attributes objectAtIndex:runIndex]; + if (aRange != NULL) { + if (attributeName != nil) { + id value = [run.attributes objectForKey:attributeName]; + NSUInteger endRunIndex = runIndex+1; + runIndex--; + // search backwards + while (1) { + if (run.index <= rangeLimit.location) { + break; + } + ZAttributeRun *prevRun = [_attributes objectAtIndex:runIndex]; + id prevValue = [prevRun.attributes objectForKey:attributeName]; + if (prevValue == value || (value != nil && [prevValue isEqual:value])) { + runIndex--; + run = prevRun; + } else { + break; + } + } + // search forwards + ZAttributeRun *endRun = nil; + while (endRunIndex < [_attributes count]) { + ZAttributeRun *nextRun = [_attributes objectAtIndex:endRunIndex]; + if (nextRun.index >= NSMaxRange(rangeLimit)) { + endRun = nextRun; + break; + } + id nextValue = [nextRun.attributes objectForKey:attributeName]; + if (nextValue == value || (value != nil && [nextValue isEqual:value])) { + endRunIndex++; + } else { + endRun = nextRun; + break; + } + } + aRange->location = MAX(run.index, rangeLimit.location); + aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location; + } else { + // with no attribute name, we don't need to do any real searching, + // as we already guarantee each run has unique attributes. + // just make sure to clip the range to the rangeLimit + aRange->location = MAX(run.index, rangeLimit.location); + ZAttributeRun *endRun = (runIndex+1 < [_attributes count] ? [_attributes objectAtIndex:runIndex+1] : nil); + aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location; + } + } + return run.attributes; +} + +- (void)dealloc { + [_buffer release]; + [_attributes release]; + [super dealloc]; +} +@end + +@interface ZMutableAttributedString () +- (void)cleanupAttributesInRange:(NSRange)range; +- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range; +- (void)offsetRunsInRange:(NSRange )range byOffset:(NSInteger)offset; +@end + +@implementation ZMutableAttributedString +- (id)copyWithZone:(NSZone *)zone { + return [(ZAttributedString *)[ZAttributedString allocWithZone:zone] initWithAttributedString:self]; +} + +- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range { + range = [self rangeOfAttributeRunsForRange:range]; + for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) { + [run.attributes setObject:value forKey:name]; + } + [self cleanupAttributesInRange:range]; +} + +- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range { + range = [self rangeOfAttributeRunsForRange:range]; + for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) { + [run.attributes addEntriesFromDictionary:attributes]; + } + [self cleanupAttributesInRange:range]; +} + +- (void)appendAttributedString:(ZAttributedString *)str { + [self insertAttributedString:str atIndex:[_buffer length]]; +} + +- (void)deleteCharactersInRange:(NSRange)range { + NSRange runRange = [self rangeOfAttributeRunsForRange:range]; + [_buffer replaceCharactersInRange:range withString:@""]; + [_attributes removeObjectsInRange:runRange]; + for (NSUInteger i = runRange.location; i < [_attributes count]; i++) { + ZAttributeRun *run = [_attributes objectAtIndex:i]; + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:(run.index - range.length) attributes:run.attributes]; + [_attributes replaceObjectAtIndex:i withObject:newRun]; + [newRun release]; + } + [self cleanupAttributesInRange:NSMakeRange(runRange.location, 0)]; +} + +- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx { + [self replaceCharactersInRange:NSMakeRange(idx, 0) withAttributedString:str]; +} + +- (void)removeAttribute:(NSString *)name range:(NSRange)range { + range = [self rangeOfAttributeRunsForRange:range]; + for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) { + [run.attributes removeObjectForKey:name]; + } + [self cleanupAttributesInRange:range]; +} + +- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str { + NSRange replaceRange = [self rangeOfAttributeRunsForRange:range]; + NSInteger offset = [str->_buffer length] - range.length; + [_buffer replaceCharactersInRange:range withString:str->_buffer]; + [_attributes replaceObjectsInRange:replaceRange withObjectsFromArray:str->_attributes]; + NSRange newRange = NSMakeRange(replaceRange.location, [str->_attributes count]); + [self offsetRunsInRange:newRange byOffset:range.location]; + [self offsetRunsInRange:NSMakeRange(NSMaxRange(newRange), [_attributes count] - NSMaxRange(newRange)) byOffset:offset]; + [self cleanupAttributesInRange:NSMakeRange(newRange.location, 0)]; + [self cleanupAttributesInRange:NSMakeRange(NSMaxRange(newRange), 0)]; +} + +- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str { + [self replaceCharactersInRange:range withAttributedString:[[[ZAttributedString alloc] initWithString:str] autorelease]]; +} + +- (void)setAttributedString:(ZAttributedString *)str { + [_buffer release], _buffer = [str->_buffer mutableCopy]; + [_attributes release], _attributes = [str->_attributes mutableCopy]; +} + +- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range { + range = [self rangeOfAttributeRunsForRange:range]; + for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) { + [run.attributes setDictionary:attributes]; + } + [self cleanupAttributesInRange:range]; +} + +#pragma mark - + +// splits the existing runs to provide one or more new runs for the given range +- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range { + NSParameterAssert(NSMaxRange(range) <= [_buffer length]); + + // find (or create) the first run + NSUInteger first = 0; + ZAttributeRun *lastRun = nil; + for (;;first++) { + if (first >= [_attributes count]) { + // we didn't find a run + first = [_attributes count]; + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes]; + [_attributes addObject:newRun]; + [newRun release]; + break; + } + ZAttributeRun *run = [_attributes objectAtIndex:first]; + if (run.index == range.location) { + break; + } else if (run.index > range.location) { + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes]; + [_attributes insertObject:newRun atIndex:first]; + [newRun release]; + break; + } + lastRun = run; + } + + if (((ZAttributeRun *)[_attributes lastObject]).index < NSMaxRange(range)) { + NSRange subrange = NSMakeRange(first, [_attributes count] - first); + if (NSMaxRange(range) < [_buffer length]) { + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range) + attributes:(NSDictionary*)[(ZAttributeRun *)[_attributes lastObject] attributes]]; + [_attributes addObject:newRun]; + [newRun release]; + } + return subrange; + } else { + // find the last run within and the first run after the range + NSUInteger lastIn = first, firstAfter = [_attributes count]-1; + while (firstAfter > lastIn + 1) { + NSUInteger idx = (firstAfter + lastIn) / 2; + ZAttributeRun *run = [_attributes objectAtIndex:idx]; + if (run.index < range.location) { + lastIn = idx; + } else if (run.index > range.location) { + firstAfter = idx; + } else { + // this is definitively the first run after the range + firstAfter = idx; + break; + } + } + if ([[_attributes objectAtIndex:firstAfter] index] > NSMaxRange(range)) { + // the first after is too far after, insert another run! + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range) + attributes:[(ZAttributeRun *)[_attributes objectAtIndex:firstAfter-1] attributes]]; + [_attributes insertObject:newRun atIndex:firstAfter]; + [newRun release]; + } + return NSMakeRange(lastIn, firstAfter - lastIn); + } +} + +- (void)cleanupAttributesInRange:(NSRange)range { + // expand the range to include one surrounding attribute on each side + if (range.location > 0) { + range.location -= 1; + range.length += 1; + } + if (NSMaxRange(range) < [_attributes count]) { + range.length += 1; + } else { + // make sure the range is capped to the attributes count + range.length = [_attributes count] - range.location; + } + if (range.length == 0) return; + ZAttributeRun *lastRun = [_attributes objectAtIndex:range.location]; + for (NSUInteger i = range.location+1; i < NSMaxRange(range);) { + ZAttributeRun *run = [_attributes objectAtIndex:i]; + if ([lastRun.attributes isEqualToDictionary:run.attributes]) { + [_attributes removeObjectAtIndex:i]; + range.length -= 1; + } else { + lastRun = run; + i++; + } + } +} + +- (void)offsetRunsInRange:(NSRange)range byOffset:(NSInteger)offset { + for (NSUInteger i = range.location; i < NSMaxRange(range); i++) { + ZAttributeRun *run = [_attributes objectAtIndex:i]; + ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:run.index + offset attributes:run.attributes]; + [_attributes replaceObjectAtIndex:i withObject:newRun]; + [newRun release]; + } +} +@end + +@implementation ZAttributeRun +@synthesize index = _index; +@synthesize attributes = _attributes; + ++ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs { + return [[[self alloc] initWithIndex:idx attributes:attrs] autorelease]; +} + +- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs { + NSParameterAssert(idx >= 0); + if ((self = [super init])) { + _index = idx; + if (attrs == nil) { + _attributes = [[NSMutableDictionary alloc] init]; + } else { + _attributes = [attrs mutableCopy]; + } + } + return self; +} + +- (id)initWithCoder:(NSCoder *)decoder { + if ((self = [super init])) { + _index = [[decoder decodeObjectForKey:@"index"] unsignedIntegerValue]; + _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy]; + } + return self; +} + +- (id)init { + return [self initWithIndex:0 attributes:[NSDictionary dictionary]]; +} + +- (id)copyWithZone:(NSZone *)zone { + return [[ZAttributeRun allocWithZone:zone] initWithIndex:_index attributes:_attributes]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:_index] forKey:@"index"]; + [aCoder encodeObject:_attributes forKey:@"attributes"]; +} + +- (NSString *)description { + NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]]; + for (id key in _attributes) { + [components addObject:[NSString stringWithFormat:@"%@=%@", key, [_attributes objectForKey:key]]]; + } + return [NSString stringWithFormat:@"<%@: %p index=%lu attributes={%@}>", + NSStringFromClass([self class]), self, (unsigned long)_index, [components componentsJoinedByString:@" "]]; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ZAttributeRun class]]) return NO; + ZAttributeRun *other = (ZAttributeRun *)object; + return _index == other->_index && [_attributes isEqualToDictionary:other->_attributes]; +} + +- (void)dealloc { + [_attributes release]; + [super dealloc]; +} +@end + +NSString * const ZFontAttributeName = @"ZFontAttributeName"; +NSString * const ZForegroundColorAttributeName = @"ZForegroundColorAttributeName"; +NSString * const ZBackgroundColorAttributeName = @"ZBackgroundColorAttributeName"; +NSString * const ZUnderlineStyleAttributeName = @"ZUnderlineStyleAttributeName"; diff --git a/libs/FontLabel/ZAttributedStringPrivate.h b/libs/FontLabel/ZAttributedStringPrivate.h new file mode 100755 index 0000000..1021d7b --- /dev/null +++ b/libs/FontLabel/ZAttributedStringPrivate.h @@ -0,0 +1,24 @@ +// +// ZAttributedStringPrivate.h +// FontLabel +// +// Created by Kevin Ballard on 9/23/09. +// Copyright 2009 Zynga Game Networks. All rights reserved. +// + +#import +#import "ZAttributedString.h" + +@interface ZAttributeRun : NSObject { + NSUInteger _index; + NSMutableDictionary *_attributes; +} +@property (nonatomic, readonly) NSUInteger index; +@property (nonatomic, readonly) NSMutableDictionary *attributes; ++ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs; +- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs; +@end + +@interface ZAttributedString (ZAttributedStringPrivate) +@property (nonatomic, readonly) NSArray *attributes; +@end diff --git a/libs/FontLabel/ZFont.h b/libs/FontLabel/ZFont.h new file mode 100755 index 0000000..05ae823 --- /dev/null +++ b/libs/FontLabel/ZFont.h @@ -0,0 +1,47 @@ +// +// ZFont.h +// FontLabel +// +// Created by Kevin Ballard on 7/2/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +@interface ZFont : NSObject { + CGFontRef _cgFont; + CGFloat _pointSize; + CGFloat _ratio; + NSString *_familyName; + NSString *_fontName; + NSString *_postScriptName; +} +@property (nonatomic, readonly) CGFontRef cgFont; +@property (nonatomic, readonly) CGFloat pointSize; +@property (nonatomic, readonly) CGFloat ascender; +@property (nonatomic, readonly) CGFloat descender; +@property (nonatomic, readonly) CGFloat leading; +@property (nonatomic, readonly) CGFloat xHeight; +@property (nonatomic, readonly) CGFloat capHeight; +@property (nonatomic, readonly) NSString *familyName; +@property (nonatomic, readonly) NSString *fontName; +@property (nonatomic, readonly) NSString *postScriptName; ++ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize; ++ (ZFont *)fontWithUIFont:(UIFont *)uiFont; +- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize; +- (ZFont *)fontWithSize:(CGFloat)fontSize; +@end diff --git a/libs/FontLabel/ZFont.m b/libs/FontLabel/ZFont.m new file mode 100755 index 0000000..793b13a --- /dev/null +++ b/libs/FontLabel/ZFont.m @@ -0,0 +1,170 @@ +// +// ZFont.m +// FontLabel +// +// Created by Kevin Ballard on 7/2/09. +// Copyright © 2009 Zynga Game Networks +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ZFont.h" + +@interface ZFont () +@property (nonatomic, readonly) CGFloat ratio; +- (NSString *)copyNameTableEntryForID:(UInt16)nameID; +@end + +@implementation ZFont +@synthesize cgFont=_cgFont, pointSize=_pointSize, ratio=_ratio; + ++ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize { + return [[[self alloc] initWithCGFont:cgFont size:fontSize] autorelease]; +} + ++ (ZFont *)fontWithUIFont:(UIFont *)uiFont { + NSParameterAssert(uiFont != nil); + CGFontRef cgFont = CGFontCreateWithFontName((CFStringRef)uiFont.fontName); + ZFont *zFont = [[self alloc] initWithCGFont:cgFont size:uiFont.pointSize]; + CGFontRelease(cgFont); + return [zFont autorelease]; +} + +- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize { + if ((self = [super init])) { + _cgFont = CGFontRetain(cgFont); + _pointSize = fontSize; + _ratio = fontSize/CGFontGetUnitsPerEm(cgFont); + } + return self; +} + +- (id)init { + NSAssert(NO, @"-init is not valid for ZFont"); + return nil; +} + +- (CGFloat)ascender { + return ceilf(self.ratio * CGFontGetAscent(self.cgFont)); +} + +- (CGFloat)descender { + return floorf(self.ratio * CGFontGetDescent(self.cgFont)); +} + +- (CGFloat)leading { + return (self.ascender - self.descender); +} + +- (CGFloat)capHeight { + return ceilf(self.ratio * CGFontGetCapHeight(self.cgFont)); +} + +- (CGFloat)xHeight { + return ceilf(self.ratio * CGFontGetXHeight(self.cgFont)); +} + +- (NSString *)familyName { + if (_familyName == nil) { + _familyName = [self copyNameTableEntryForID:1]; + } + return _familyName; +} + +- (NSString *)fontName { + if (_fontName == nil) { + _fontName = [self copyNameTableEntryForID:4]; + } + return _fontName; +} + +- (NSString *)postScriptName { + if (_postScriptName == nil) { + _postScriptName = [self copyNameTableEntryForID:6]; + } + return _postScriptName; +} + +- (ZFont *)fontWithSize:(CGFloat)fontSize { + if (fontSize == self.pointSize) return self; + NSParameterAssert(fontSize > 0.0); + return [[[ZFont alloc] initWithCGFont:self.cgFont size:fontSize] autorelease]; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ZFont class]]) return NO; + ZFont *font = (ZFont *)object; + return (font.cgFont == self.cgFont && font.pointSize == self.pointSize); +} + +- (NSString *)copyNameTableEntryForID:(UInt16)aNameID { + CFDataRef nameTable = CGFontCopyTableForTag(self.cgFont, 'name'); + NSAssert1(nameTable != NULL, @"CGFontCopyTableForTag returned NULL for 'name' tag in font %@", + [(id)CFCopyDescription(self.cgFont) autorelease]); + const UInt8 * const bytes = CFDataGetBytePtr(nameTable); + NSAssert1(OSReadBigInt16(bytes, 0) == 0, @"name table for font %@ has bad version number", + [(id)CFCopyDescription(self.cgFont) autorelease]); + const UInt16 count = OSReadBigInt16(bytes, 2); + const UInt16 stringOffset = OSReadBigInt16(bytes, 4); + const UInt8 * const nameRecords = &bytes[6]; + UInt16 nameLength = 0; + UInt16 nameOffset = 0; + NSStringEncoding encoding = 0; + for (UInt16 idx = 0; idx < count; idx++) { + const uintptr_t recordOffset = 12 * idx; + const UInt16 nameID = OSReadBigInt16(nameRecords, recordOffset + 6); + if (nameID != aNameID) continue; + const UInt16 platformID = OSReadBigInt16(nameRecords, recordOffset + 0); + const UInt16 platformSpecificID = OSReadBigInt16(nameRecords, recordOffset + 2); + encoding = 0; + // for now, we only support a subset of encodings + switch (platformID) { + case 0: // Unicode + encoding = NSUTF16StringEncoding; + break; + case 1: // Macintosh + switch (platformSpecificID) { + case 0: + encoding = NSMacOSRomanStringEncoding; + break; + } + case 3: // Microsoft + switch (platformSpecificID) { + case 1: + encoding = NSUTF16StringEncoding; + break; + } + } + if (encoding == 0) continue; + nameLength = OSReadBigInt16(nameRecords, recordOffset + 8); + nameOffset = OSReadBigInt16(nameRecords, recordOffset + 10); + break; + } + NSString *result = nil; + if (nameOffset > 0) { + const UInt8 *nameBytes = &bytes[stringOffset + nameOffset]; + result = [[NSString alloc] initWithBytes:nameBytes length:nameLength encoding:encoding]; + } + CFRelease(nameTable); + return result; +} + +- (void)dealloc { + CGFontRelease(_cgFont); + [_familyName release]; + [_fontName release]; + [_postScriptName release]; + [super dealloc]; +} +@end -- cgit 1.4.1