summary refs log tree commit diff stats
path: root/libs/FontLabel
diff options
context:
space:
mode:
authorStarla Insigna <starla4444@gmail.com>2011-07-30 11:19:14 -0400
committerStarla Insigna <starla4444@gmail.com>2011-07-30 11:19:14 -0400
commit9cd57b731ab1c666d4a1cb725538fdc137763d12 (patch)
tree5bac45ae5157a1cb10c6e45500cbf72789917980 /libs/FontLabel
downloadcartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.tar.gz
cartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.tar.bz2
cartcollect-9cd57b731ab1c666d4a1cb725538fdc137763d12.zip
Initial commit (version 0.2.1)
Diffstat (limited to 'libs/FontLabel')
-rwxr-xr-xlibs/FontLabel/FontLabel.h44
-rwxr-xr-xlibs/FontLabel/FontLabel.m195
-rwxr-xr-xlibs/FontLabel/FontLabelStringDrawing.h69
-rwxr-xr-xlibs/FontLabel/FontLabelStringDrawing.m892
-rwxr-xr-xlibs/FontLabel/FontManager.h85
-rwxr-xr-xlibs/FontLabel/FontManager.m123
-rwxr-xr-xlibs/FontLabel/ZAttributedString.h77
-rwxr-xr-xlibs/FontLabel/ZAttributedString.m597
-rwxr-xr-xlibs/FontLabel/ZAttributedStringPrivate.h24
-rwxr-xr-xlibs/FontLabel/ZFont.h47
-rwxr-xr-xlibs/FontLabel/ZFont.m170
11 files changed, 2323 insertions, 0 deletions
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 @@
1//
2// FontLabel.h
3// FontLabel
4//
5// Created by Kevin Ballard on 5/8/09.
6// Copyright © 2009 Zynga Game Networks
7//
8//
9// Licensed under the Apache License, Version 2.0 (the "License");
10// you may not use this file except in compliance with the License.
11// You may obtain a copy of the License at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20//
21
22#import <Foundation/Foundation.h>
23#import <UIKit/UIKit.h>
24
25@class ZFont;
26@class ZAttributedString;
27
28@interface FontLabel : UILabel {
29 void *reserved; // works around a bug in UILabel
30 ZFont *zFont;
31 ZAttributedString *zAttributedText;
32}
33@property (nonatomic, setter=setCGFont:) CGFontRef cgFont __AVAILABILITY_INTERNAL_DEPRECATED;
34@property (nonatomic, assign) CGFloat pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
35@property (nonatomic, retain, setter=setZFont:) ZFont *zFont;
36// if attributedText is nil, fall back on using the inherited UILabel properties
37// if attributedText is non-nil, the font/text/textColor
38// in addition, adjustsFontSizeToFitWidth does not work with attributed text
39@property (nonatomic, copy) ZAttributedString *zAttributedText;
40// -initWithFrame:fontName:pointSize: uses FontManager to look up the font name
41- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize;
42- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font;
43- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
44@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 @@
1//
2// FontLabel.m
3// FontLabel
4//
5// Created by Kevin Ballard on 5/8/09.
6// Copyright © 2009 Zynga Game Networks
7//
8//
9// Licensed under the Apache License, Version 2.0 (the "License");
10// you may not use this file except in compliance with the License.
11// You may obtain a copy of the License at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20//
21
22#import "FontLabel.h"
23#import "FontManager.h"
24#import "FontLabelStringDrawing.h"
25#import "ZFont.h"
26
27@interface ZFont (ZFontPrivate)
28@property (nonatomic, readonly) CGFloat ratio;
29@end
30
31@implementation FontLabel
32@synthesize zFont;
33@synthesize zAttributedText;
34
35- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize {
36 return [self initWithFrame:frame zFont:[[FontManager sharedManager] zFontWithName:fontName pointSize:pointSize]];
37}
38
39- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font {
40 if ((self = [super initWithFrame:frame])) {
41 zFont = [font retain];
42 }
43 return self;
44}
45
46- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize {
47 return [self initWithFrame:frame zFont:[ZFont fontWithCGFont:font size:pointSize]];
48}
49
50- (CGFontRef)cgFont {
51 return self.zFont.cgFont;
52}
53
54- (void)setCGFont:(CGFontRef)font {
55 if (self.zFont.cgFont != font) {
56 self.zFont = [ZFont fontWithCGFont:font size:self.zFont.pointSize];
57 }
58}
59
60- (CGFloat)pointSize {
61 return self.zFont.pointSize;
62}
63
64- (void)setPointSize:(CGFloat)pointSize {
65 if (self.zFont.pointSize != pointSize) {
66 self.zFont = [ZFont fontWithCGFont:self.zFont.cgFont size:pointSize];
67 }
68}
69
70- (void)setZAttributedText:(ZAttributedString *)attStr {
71 if (zAttributedText != attStr) {
72 [zAttributedText release];
73 zAttributedText = [attStr copy];
74 [self setNeedsDisplay];
75 }
76}
77
78- (void)drawTextInRect:(CGRect)rect {
79 if (self.zFont == NULL && self.zAttributedText == nil) {
80 [super drawTextInRect:rect];
81 return;
82 }
83
84 if (self.zAttributedText == nil) {
85 // this method is documented as setting the text color for us, but that doesn't appear to be the case
86 if (self.highlighted) {
87 [(self.highlightedTextColor ?: [UIColor whiteColor]) setFill];
88 } else {
89 [(self.textColor ?: [UIColor blackColor]) setFill];
90 }
91
92 ZFont *actualFont = self.zFont;
93 CGSize origSize = rect.size;
94 if (self.numberOfLines == 1) {
95 origSize.height = actualFont.leading;
96 CGPoint point = CGPointMake(rect.origin.x,
97 rect.origin.y + roundf(((rect.size.height - actualFont.leading) / 2.0f)));
98 CGSize size = [self.text sizeWithZFont:actualFont];
99 if (self.adjustsFontSizeToFitWidth && self.minimumFontSize < actualFont.pointSize) {
100 if (size.width > origSize.width) {
101 CGFloat desiredRatio = (origSize.width * actualFont.ratio) / size.width;
102 CGFloat desiredPointSize = desiredRatio * actualFont.pointSize / actualFont.ratio;
103 actualFont = [actualFont fontWithSize:MAX(MAX(desiredPointSize, self.minimumFontSize), 1.0f)];
104 size = [self.text sizeWithZFont:actualFont];
105 }
106 if (!CGSizeEqualToSize(origSize, size)) {
107 switch (self.baselineAdjustment) {
108 case UIBaselineAdjustmentAlignCenters:
109 point.y += roundf((origSize.height - size.height) / 2.0f);
110 break;
111 case UIBaselineAdjustmentAlignBaselines:
112 point.y += (self.zFont.ascender - actualFont.ascender);
113 break;
114 case UIBaselineAdjustmentNone:
115 break;
116 }
117 }
118 }
119 size.width = MIN(size.width, origSize.width);
120 // adjust the point for alignment
121 switch (self.textAlignment) {
122 case UITextAlignmentLeft:
123 break;
124 case UITextAlignmentCenter:
125 point.x += (origSize.width - size.width) / 2.0f;
126 break;
127 case UITextAlignmentRight:
128 point.x += origSize.width - size.width;
129 break;
130 }
131 [self.text drawAtPoint:point forWidth:size.width withZFont:actualFont lineBreakMode:self.lineBreakMode];
132 } else {
133 CGSize size = [self.text sizeWithZFont:actualFont constrainedToSize:origSize lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines];
134 CGPoint point = rect.origin;
135 point.y += roundf((rect.size.height - size.height) / 2.0f);
136 rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)};
137 [self.text drawInRect:rect withZFont:actualFont lineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines];
138 }
139 } else {
140 ZAttributedString *attStr = self.zAttributedText;
141 if (self.highlighted) {
142 // modify the string to change the base color
143 ZMutableAttributedString *mutStr = [[attStr mutableCopy] autorelease];
144 NSRange activeRange = NSMakeRange(0, attStr.length);
145 while (activeRange.length > 0) {
146 NSRange effective;
147 UIColor *color = [attStr attribute:ZForegroundColorAttributeName atIndex:activeRange.location
148 longestEffectiveRange:&effective inRange:activeRange];
149 if (color == nil) {
150 [mutStr addAttribute:ZForegroundColorAttributeName value:[UIColor whiteColor] range:effective];
151 }
152 activeRange.location += effective.length, activeRange.length -= effective.length;
153 }
154 attStr = mutStr;
155 }
156 CGSize size = [attStr sizeConstrainedToSize:rect.size lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines];
157 CGPoint point = rect.origin;
158 point.y += roundf((rect.size.height - size.height) / 2.0f);
159 rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)};
160 [attStr drawInRect:rect withLineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines];
161 }
162}
163
164- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines {
165 if (self.zFont == NULL && self.zAttributedText == nil) {
166 return [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
167 }
168
169 if (numberOfLines == 1) {
170 // if numberOfLines == 1 we need to use the version that converts spaces
171 CGSize size;
172 if (self.zAttributedText == nil) {
173 size = [self.text sizeWithZFont:self.zFont];
174 } else {
175 size = [self.zAttributedText size];
176 }
177 bounds.size.width = MIN(bounds.size.width, size.width);
178 bounds.size.height = MIN(bounds.size.height, size.height);
179 } else {
180 if (numberOfLines > 0) bounds.size.height = MIN(bounds.size.height, self.zFont.leading * numberOfLines);
181 if (self.zAttributedText == nil) {
182 bounds.size = [self.text sizeWithZFont:self.zFont constrainedToSize:bounds.size lineBreakMode:self.lineBreakMode];
183 } else {
184 bounds.size = [self.zAttributedText sizeConstrainedToSize:bounds.size lineBreakMode:self.lineBreakMode];
185 }
186 }
187 return bounds;
188}
189
190- (void)dealloc {
191 [zFont release];
192 [zAttributedText release];
193 [super dealloc];
194}
195@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 @@
1//
2// FontLabelStringDrawing.h
3// FontLabel
4//
5// Created by Kevin Ballard on 5/5/09.
6// Copyright © 2009 Zynga Game Networks
7//
8//
9// Licensed under the Apache License, Version 2.0 (the "License");
10// you may not use this file except in compliance with the License.
11// You may obtain a copy of the License at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20//
21
22#import <UIKit/UIKit.h>
23#import "ZAttributedString.h"
24
25@class ZFont;
26
27@interface NSString (FontLabelStringDrawing)
28// CGFontRef-based methods
29- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
30- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size __AVAILABILITY_INTERNAL_DEPRECATED;
31- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size
32 lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED;
33- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
34- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
35- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
36 lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED;
37- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
38 lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment __AVAILABILITY_INTERNAL_DEPRECATED;
39
40// ZFont-based methods
41- (CGSize)sizeWithZFont:(ZFont *)font;
42- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size;
43- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode;
44- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
45 numberOfLines:(NSUInteger)numberOfLines;
46- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font;
47- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode;
48- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font;
49- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode;
50- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
51 alignment:(UITextAlignment)alignment;
52- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
53 alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines;
54@end
55
56@interface ZAttributedString (ZAttributedStringDrawing)
57- (CGSize)size;
58- (CGSize)sizeConstrainedToSize:(CGSize)size;
59- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode;
60- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
61 numberOfLines:(NSUInteger)numberOfLines;
62- (CGSize)drawAtPoint:(CGPoint)point;
63- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode;
64- (CGSize)drawInRect:(CGRect)rect;
65- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode;
66- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment;
67- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment
68 numberOfLines:(NSUInteger)numberOfLines;
69@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 @@
1//
2// FontLabelStringDrawing.m
3// FontLabel
4//
5// Created by Kevin Ballard on 5/5/09.
6// Copyright © 2009 Zynga Game Networks
7//
8//
9// Licensed under the Apache License, Version 2.0 (the "License");
10// you may not use this file except in compliance with the License.
11// You may obtain a copy of the License at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20//
21
22#import "FontLabelStringDrawing.h"
23#import "ZFont.h"
24#import "ZAttributedStringPrivate.h"
25
26@interface ZFont (ZFontPrivate)
27@property (nonatomic, readonly) CGFloat ratio;
28@end
29
30#define kUnicodeHighSurrogateStart 0xD800
31#define kUnicodeHighSurrogateEnd 0xDBFF
32#define kUnicodeHighSurrogateMask kUnicodeHighSurrogateStart
33#define kUnicodeLowSurrogateStart 0xDC00
34#define kUnicodeLowSurrogateEnd 0xDFFF
35#define kUnicodeLowSurrogateMask kUnicodeLowSurrogateStart
36#define kUnicodeSurrogateTypeMask 0xFC00
37#define UnicharIsHighSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeHighSurrogateMask)
38#define UnicharIsLowSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeLowSurrogateMask)
39#define ConvertSurrogatePairToUTF32(high, low) ((UInt32)((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000))
40
41typedef enum {
42 kFontTableFormat4 = 4,
43 kFontTableFormat12 = 12,
44} FontTableFormat;
45
46typedef struct fontTable {
47 NSUInteger retainCount;
48 CFDataRef cmapTable;
49 FontTableFormat format;
50 union {
51 struct {
52 UInt16 segCountX2;
53 UInt16 *endCodes;
54 UInt16 *startCodes;
55 UInt16 *idDeltas;
56 UInt16 *idRangeOffsets;
57 } format4;
58 struct {
59 UInt32 nGroups;
60 struct {
61 UInt32 startCharCode;
62 UInt32 endCharCode;
63 UInt32 startGlyphCode;
64 } *groups;
65 } format12;
66 } cmap;
67} fontTable;
68
69static FontTableFormat supportedFormats[] = { kFontTableFormat4, kFontTableFormat12 };
70static size_t supportedFormatsCount = sizeof(supportedFormats) / sizeof(FontTableFormat);
71
72static fontTable *newFontTable(CFDataRef cmapTable, FontTableFormat format) {
73 fontTable *table = (struct fontTable *)malloc(sizeof(struct fontTable));
74 table->retainCount = 1;
75 table->cmapTable = CFRetain(cmapTable);
76 table->format = format;
77 return table;
78}
79
80static fontTable *retainFontTable(fontTable *table) {
81 if (table != NULL) {
82 table->retainCount++;
83 }
84 return table;
85}
86
87static void releaseFontTable(fontTable *table) {
88 if (table != NULL) {
89 if (table->retainCount <= 1) {
90 CFRelease(table->cmapTable);
91 free(table);
92 } else {
93 table->retainCount--;
94 }
95 }
96}
97
98static const void *fontTableRetainCallback(CFAllocatorRef allocator, const void *value) {
99 return retainFontTable((fontTable *)value);
100}
101
102static void fontTableReleaseCallback(CFAllocatorRef allocator, const void *value) {
103 releaseFontTable((fontTable *)value);
104}
105
106static const CFDictionaryValueCallBacks kFontTableDictionaryValueCallBacks = {
107 .version = 0,
108 .retain = &fontTableRetainCallback,
109 .release = &fontTableReleaseCallback,
110 .copyDescription = NULL,
111 .equal = NULL
112};
113
114// read the cmap table from the font
115// we only know how to understand some of the table formats at the moment
116static fontTable *readFontTableFromCGFont(CGFontRef font) {
117 CFDataRef cmapTable = CGFontCopyTableForTag(font, 'cmap');
118 NSCAssert1(cmapTable != NULL, @"CGFontCopyTableForTag returned NULL for 'cmap' tag in font %@",
119 (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)"));
120 const UInt8 * const bytes = CFDataGetBytePtr(cmapTable);
121 NSCAssert1(OSReadBigInt16(bytes, 0) == 0, @"cmap table for font %@ has bad version number",
122 (font ? [(id)CFCopyDescription(font) autorelease] : @"(null)"));
123 UInt16 numberOfSubtables = OSReadBigInt16(bytes, 2);
124 const UInt8 *unicodeSubtable = NULL;
125 //UInt16 unicodeSubtablePlatformID;
126 UInt16 unicodeSubtablePlatformSpecificID;
127 FontTableFormat unicodeSubtableFormat;
128 const UInt8 * const encodingSubtables = &bytes[4];
129 for (UInt16 i = 0; i < numberOfSubtables; i++) {
130 const UInt8 * const encodingSubtable = &encodingSubtables[8 * i];
131 UInt16 platformID = OSReadBigInt16(encodingSubtable, 0);
132 UInt16 platformSpecificID = OSReadBigInt16(encodingSubtable, 2);
133 // find the best subtable
134 // best is defined by a combination of encoding and format
135 // At the moment we only support format 4, so ignore all other format tables
136 // We prefer platformID == 0, but we will also accept Microsoft's unicode format
137 if (platformID == 0 || (platformID == 3 && platformSpecificID == 1)) {
138 BOOL preferred = NO;
139 if (unicodeSubtable == NULL) {
140 preferred = YES;
141 } else if (platformID == 0 && platformSpecificID > unicodeSubtablePlatformSpecificID) {
142 preferred = YES;
143 }
144 if (preferred) {
145 UInt32 offset = OSReadBigInt32(encodingSubtable, 4);
146 const UInt8 *subtable = &bytes[offset];
147 UInt16 format = OSReadBigInt16(subtable, 0);
148 for (size_t i = 0; i < supportedFormatsCount; i++) {
149 if (format == supportedFormats[i]) {
150 if (format >= 8) {
151 // the version is a fixed-point
152 UInt16 formatFrac = OSReadBigInt16(subtable, 2);
153 if (formatFrac != 0) {
154 // all the current formats with a Fixed version are always *.0
155 continue;
156 }
157 }
158 unicodeSubtable = subtable;
159 //unicodeSubtablePlatformID = platformID;
160 unicodeSubtablePlatformSpecificID = platformSpecificID;
161 unicodeSubtableFormat = format;
162 break;
163 }
164 }
165 }
166 }
167 }
168 fontTable *table = NULL;
169 if (unicodeSubtable != NULL) {
170 table = newFontTable(cmapTable, unicodeSubtableFormat);
171 switch (unicodeSubtableFormat) {
172 case kFontTableFormat4:
173 // subtable format 4
174 //UInt16 length = OSReadBigInt16(unicodeSubtable, 2);
175 //UInt16 language = OSReadBigInt16(unicodeSubtable, 4);
176 table->cmap.format4.segCountX2 = OSReadBigInt16(unicodeSubtable, 6);
177 //UInt16 searchRange = OSReadBigInt16(unicodeSubtable, 8);
178 //UInt16 entrySelector = OSReadBigInt16(unicodeSubtable, 10);
179 //UInt16 rangeShift = OSReadBigInt16(unicodeSubtable, 12);
180 table->cmap.format4.endCodes = (UInt16*)&unicodeSubtable[14];
181 table->cmap.format4.startCodes = (UInt16*)&((UInt8*)table->cmap.format4.endCodes)[table->cmap.format4.segCountX2+2];
182 table->cmap.format4.idDeltas = (UInt16*)&((UInt8*)table->cmap.format4.startCodes)[table->cmap.format4.segCountX2];
183 table->cmap.format4.idRangeOffsets = (UInt16*)&((UInt8*)table->cmap.format4.idDeltas)[table->cmap.format4.segCountX2];
184 //UInt16 *glyphIndexArray = &idRangeOffsets[segCountX2];
185 break;
186 case kFontTableFormat12:
187 table->cmap.format12.nGroups = OSReadBigInt32(unicodeSubtable, 12);
188 table->cmap.format12.groups = (void *)&unicodeSubtable[16];
189 break;
190 default:
191 releaseFontTable(table);
192 table = NULL;
193 }
194 }
195 CFRelease(cmapTable);
196 return table;
197}
198
199// outGlyphs must be at least size n
200static void mapCharactersToGlyphsInFont(const fontTable *table, unichar characters[], size_t charLen, CGGlyph outGlyphs[], size_t *outGlyphLen) {
201 if (table != NULL) {
202 NSUInteger j = 0;
203 switch (table->format) {
204 case kFontTableFormat4: {
205 for (NSUInteger i = 0; i < charLen; i++, j++) {
206 unichar c = characters[i];
207 UInt16 segOffset;
208 BOOL foundSegment = NO;
209 for (segOffset = 0; segOffset < table->cmap.format4.segCountX2; segOffset += 2) {
210 UInt16 endCode = OSReadBigInt16(table->cmap.format4.endCodes, segOffset);
211 if (endCode >= c) {
212 foundSegment = YES;
213 break;
214 }
215 }
216 if (!foundSegment) {
217 // no segment
218 // this is an invalid font
219 outGlyphs[j] = 0;
220 } else {
221 UInt16 startCode = OSReadBigInt16(table->cmap.format4.startCodes, segOffset);
222 if (!(startCode <= c)) {
223 // the code falls in a hole between segments
224 outGlyphs[j] = 0;
225 } else {
226 UInt16 idRangeOffset = OSReadBigInt16(table->cmap.format4.idRangeOffsets, segOffset);
227 if (idRangeOffset == 0) {
228 UInt16 idDelta = OSReadBigInt16(table->cmap.format4.idDeltas, segOffset);
229 outGlyphs[j] = (c + idDelta) % 65536;
230 } else {
231 // use the glyphIndexArray
232 UInt16 glyphOffset = idRangeOffset + 2 * (c - startCode);
233 outGlyphs[j] = OSReadBigInt16(&((UInt8*)table->cmap.format4.idRangeOffsets)[segOffset], glyphOffset);
234 }
235 }
236 }
237 }
238 break;
239 }
240 case kFontTableFormat12: {
241 UInt32 lastSegment = UINT32_MAX;
242 for (NSUInteger i = 0; i < charLen; i++, j++) {
243 unichar c = characters[i];
244 UInt32 c32 = c;
245 if (UnicharIsHighSurrogate(c)) {
246 if (i+1 < charLen) { // do we have another character after this one?
247 unichar cc = characters[i+1];
248 if (UnicharIsLowSurrogate(cc)) {
249 c32 = ConvertSurrogatePairToUTF32(c, cc);
250 i++;
251 }
252 }
253 }
254 // Start the heuristic search
255 // If this is an ASCII char, just do a linear search
256 // Otherwise do a hinted, modified binary search
257 // Start the first pivot at the last range found
258 // And when moving the pivot, limit the movement by increasing
259 // powers of two. This should help with locality
260 __typeof__(table->cmap.format12.groups[0]) *foundGroup = NULL;
261 if (c32 <= 0x7F) {
262 // ASCII
263 for (UInt32 idx = 0; idx < table->cmap.format12.nGroups; idx++) {
264 __typeof__(table->cmap.format12.groups[idx]) *group = &table->cmap.format12.groups[idx];
265 if (c32 < OSSwapBigToHostInt32(group->startCharCode)) {
266 // we've fallen into a hole
267 break;
268 } else if (c32 <= OSSwapBigToHostInt32(group->endCharCode)) {
269 // this is the range
270 foundGroup = group;
271 break;
272 }
273 }
274 } else {
275 // heuristic search
276 UInt32 maxJump = (lastSegment == UINT32_MAX ? UINT32_MAX / 2 : 8);
277 UInt32 lowIdx = 0, highIdx = table->cmap.format12.nGroups; // highIdx is the first invalid idx
278 UInt32 pivot = (lastSegment == UINT32_MAX ? lowIdx + (highIdx - lowIdx) / 2 : lastSegment);
279 while (highIdx > lowIdx) {
280 __typeof__(table->cmap.format12.groups[pivot]) *group = &table->cmap.format12.groups[pivot];
281 if (c32 < OSSwapBigToHostInt32(group->startCharCode)) {
282 highIdx = pivot;
283 } else if (c32 > OSSwapBigToHostInt32(group->endCharCode)) {
284 lowIdx = pivot + 1;
285 } else {
286 // we've hit the range
287 foundGroup = group;
288 break;
289 }
290 if (highIdx - lowIdx > maxJump * 2) {
291 if (highIdx == pivot) {
292 pivot -= maxJump;
293 } else {
294 pivot += maxJump;
295 }
296 maxJump *= 2;
297 } else {
298 pivot = lowIdx + (highIdx - lowIdx) / 2;
299 }
300 }
301 if (foundGroup != NULL) lastSegment = pivot;
302 }
303 if (foundGroup == NULL) {
304 outGlyphs[j] = 0;
305 } else {
306 outGlyphs[j] = (CGGlyph)(OSSwapBigToHostInt32(foundGroup->startGlyphCode) +
307 (c32 - OSSwapBigToHostInt32(foundGroup->startCharCode)));
308 }
309 }
310 break;
311 }
312 }
313 if (outGlyphLen != NULL) *outGlyphLen = j;
314 } else {
315 // we have no table, so just null out the glyphs
316 bzero(outGlyphs, charLen*sizeof(CGGlyph));
317 if (outGlyphLen != NULL) *outGlyphLen = 0;
318 }
319}
320
321static BOOL mapGlyphsToAdvancesInFont(ZFont *font, size_t n, CGGlyph glyphs[], CGFloat outAdvances[]) {
322 int advances[n];
323 if (CGFontGetGlyphAdvances(font.cgFont, glyphs, n, advances)) {
324 CGFloat ratio = font.ratio;
325
326 for (size_t i = 0; i < n; i++) {
327 outAdvances[i] = advances[i]*ratio;
328 }
329 return YES;
330 } else {
331 bzero(outAdvances, n*sizeof(CGFloat));
332 }
333 return NO;
334}
335
336static id getValueOrDefaultForRun(ZAttributeRun *run, NSString *key) {
337 id value = [run.attributes objectForKey:key];
338 if (value == nil) {
339 static NSDictionary *defaultValues = nil;
340 if (defaultValues == nil) {
341 defaultValues = [[NSDictionary alloc] initWithObjectsAndKeys:
342 [ZFont fontWithUIFont:[UIFont systemFontOfSize:12]], ZFontAttributeName,
343 [UIColor blackColor], ZForegroundColorAttributeName,
344 [UIColor clearColor], ZBackgroundColorAttributeName,
345 [NSNumber numberWithInt:ZUnderlineStyleNone], ZUnderlineStyleAttributeName,
346 nil];
347 }
348 value = [defaultValues objectForKey:key];
349 }
350 return value;
351}
352
353static void readRunInformation(NSArray *attributes, NSUInteger len, CFMutableDictionaryRef fontTableMap,
354 NSUInteger index, ZAttributeRun **currentRun, NSUInteger *nextRunStart,
355 ZFont **currentFont, fontTable **currentTable) {
356 *currentRun = [attributes objectAtIndex:index];
357 *nextRunStart = ([attributes count] > index+1 ? [[attributes objectAtIndex:index+1] index] : len);
358 *currentFont = getValueOrDefaultForRun(*currentRun, ZFontAttributeName);
359 if (!CFDictionaryGetValueIfPresent(fontTableMap, (*currentFont).cgFont, (const void **)currentTable)) {
360 *currentTable = readFontTableFromCGFont((*currentFont).cgFont);
361 CFDictionarySetValue(fontTableMap, (*currentFont).cgFont, *currentTable);
362 releaseFontTable(*currentTable);
363 }
364}
365
366static CGSize drawOrSizeTextConstrainedToSize(BOOL performDraw, NSString *string, NSArray *attributes, CGSize constrainedSize, NSUInteger maxLines,
367 UILineBreakMode lineBreakMode, UITextAlignment alignment, BOOL ignoreColor) {
368 NSUInteger len = [string length];
369 NSUInteger idx = 0;
370 CGPoint drawPoint = CGPointZero;
371 CGSize retValue = CGSizeZero;
372 CGContextRef ctx = (performDraw ? UIGraphicsGetCurrentContext() : NULL);
373
374 BOOL convertNewlines = (maxLines == 1);
375
376 // Extract the characters from the string
377 // Convert newlines to spaces if necessary
378 unichar *characters = (unichar *)malloc(sizeof(unichar) * len);
379 if (convertNewlines) {
380 NSCharacterSet *charset = [NSCharacterSet newlineCharacterSet];
381 NSRange range = NSMakeRange(0, len);
382 size_t cIdx = 0;
383 while (range.length > 0) {
384 NSRange newlineRange = [string rangeOfCharacterFromSet:charset options:0 range:range];
385 if (newlineRange.location == NSNotFound) {
386 [string getCharacters:&characters[cIdx] range:range];
387 cIdx += range.length;
388 break;
389 } else {
390 NSUInteger delta = newlineRange.location - range.location;
391 if (newlineRange.location > range.location) {
392 [string getCharacters:&characters[cIdx] range:NSMakeRange(range.location, delta)];
393 }
394 cIdx += delta;
395 characters[cIdx] = (unichar)' ';
396 cIdx++;
397 delta += newlineRange.length;
398 range.location += delta, range.length -= delta;
399 if (newlineRange.length == 1 && range.length >= 1 &&
400 [string characterAtIndex:newlineRange.location] == (unichar)'\r' &&
401 [string characterAtIndex:range.location] == (unichar)'\n') {
402 // CRLF sequence, skip the LF
403 range.location += 1, range.length -= 1;
404 }
405 }
406 }
407 len = cIdx;
408 } else {
409 [string getCharacters:characters range:NSMakeRange(0, len)];
410 }
411
412 // Create storage for glyphs and advances
413 CGGlyph *glyphs;
414 CGFloat *advances;
415 {
416 NSUInteger maxRunLength = 0;
417 ZAttributeRun *a = [attributes objectAtIndex:0];
418 for (NSUInteger i = 1; i < [attributes count]; i++) {
419 ZAttributeRun *b = [attributes objectAtIndex:i];
420 maxRunLength = MAX(maxRunLength, b.index - a.index);
421 a = b;
422 }
423 maxRunLength = MAX(maxRunLength, len - a.index);
424 maxRunLength++; // for a potential ellipsis
425 glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * maxRunLength);
426 advances = (CGFloat *)malloc(sizeof(CGFloat) * maxRunLength);
427 }
428
429 // Use this table to cache all fontTable objects
430 CFMutableDictionaryRef fontTableMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
431 &kFontTableDictionaryValueCallBacks);
432
433 // Fetch initial style values
434 NSUInteger currentRunIdx = 0;
435 ZAttributeRun *currentRun;
436 NSUInteger nextRunStart;
437 ZFont *currentFont;
438 fontTable *currentTable;
439
440#define READ_RUN() readRunInformation(attributes, len, fontTableMap, \
441 currentRunIdx, &currentRun, &nextRunStart, \
442 &currentFont, &currentTable)
443
444 READ_RUN();
445
446 // fetch the glyphs for the first run
447 size_t glyphCount;
448 NSUInteger glyphIdx;
449
450#define READ_GLYPHS() do { \
451 mapCharactersToGlyphsInFont(currentTable, &characters[currentRun.index], (nextRunStart - currentRun.index), glyphs, &glyphCount); \
452 mapGlyphsToAdvancesInFont(currentFont, (nextRunStart - currentRun.index), glyphs, advances); \
453 glyphIdx = 0; \
454 } while (0)
455
456 READ_GLYPHS();
457
458 NSMutableCharacterSet *alphaCharset = [NSMutableCharacterSet alphanumericCharacterSet];
459 [alphaCharset addCharactersInString:@"([{'\"\u2019\u02BC"];
460
461 // scan left-to-right looking for newlines or until we hit the width constraint
462 // When we hit a wrapping point, calculate truncation as follows:
463 // If we have room to draw at least one more character on the next line, no truncation
464 // Otherwise apply the truncation algorithm to the current line.
465 // After calculating any truncation, draw.
466 // Each time we hit the end of an attribute run, calculate the new font and make sure
467 // it fits (vertically) within the size constraint. If not, truncate this line.
468 // When we draw, iterate over the attribute runs for this line and draw each run separately
469 BOOL lastLine = NO; // used to indicate truncation and to stop the iterating
470 NSUInteger lineCount = 1;
471 while (idx < len && !lastLine) {
472 if (maxLines > 0 && lineCount == maxLines) {
473 lastLine = YES;
474 }
475 // scan left-to-right
476 struct {
477 NSUInteger index;
478 NSUInteger glyphIndex;
479 NSUInteger currentRunIdx;
480 } indexCache = { idx, glyphIdx, currentRunIdx };
481 CGSize lineSize = CGSizeMake(0, currentFont.leading);
482 CGFloat lineAscender = currentFont.ascender;
483 struct {
484 NSUInteger index;
485 NSUInteger glyphIndex;
486 NSUInteger currentRunIdx;
487 CGSize lineSize;
488 } lastWrapCache = {0, 0, 0, CGSizeZero};
489 BOOL inAlpha = NO; // used for calculating wrap points
490
491 BOOL finishLine = NO;
492 for (;idx <= len && !finishLine;) {
493 NSUInteger skipCount = 0;
494 if (idx == len) {
495 finishLine = YES;
496 lastLine = YES;
497 } else {
498 if (idx >= nextRunStart) {
499 // cycle the font and table and grab the next set of glyphs
500 do {
501 currentRunIdx++;
502 READ_RUN();
503 } while (idx >= nextRunStart);
504 READ_GLYPHS();
505 // re-scan the characters to synchronize the glyph index
506 for (NSUInteger j = currentRun.index; j < idx; j++) {
507 if (UnicharIsHighSurrogate(characters[j]) && j+1<len && UnicharIsLowSurrogate(characters[j+1])) {
508 j++;
509 }
510 glyphIdx++;
511 }
512 if (currentFont.leading > lineSize.height) {
513 lineSize.height = currentFont.leading;
514 if (retValue.height + currentFont.ascender > constrainedSize.height) {
515 lastLine = YES;
516 finishLine = YES;
517 }
518 }
519 lineAscender = MAX(lineAscender, currentFont.ascender);
520 }
521 unichar c = characters[idx];
522 // Mark a wrap point before spaces and after any stretch of non-alpha characters
523 BOOL markWrap = NO;
524 if (c == (unichar)' ') {
525 markWrap = YES;
526 } else if ([alphaCharset characterIsMember:c]) {
527 if (!inAlpha) {
528 markWrap = YES;
529 inAlpha = YES;
530 }
531 } else {
532 inAlpha = NO;
533 }
534 if (markWrap) {
535 lastWrapCache = (__typeof__(lastWrapCache)){
536 .index = idx,
537 .glyphIndex = glyphIdx,
538 .currentRunIdx = currentRunIdx,
539 .lineSize = lineSize
540 };
541 }
542 // process the line
543 if (c == (unichar)'\n' || c == 0x0085) { // U+0085 is the NEXT_LINE unicode character
544 finishLine = YES;
545 skipCount = 1;
546 } else if (c == (unichar)'\r') {
547 finishLine = YES;
548 // check for CRLF
549 if (idx+1 < len && characters[idx+1] == (unichar)'\n') {
550 skipCount = 2;
551 } else {
552 skipCount = 1;
553 }
554 } else if (lineSize.width + advances[glyphIdx] > constrainedSize.width) {
555 finishLine = YES;
556 if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) {
557 lastLine = YES;
558 }
559 // walk backwards if wrapping is necessary
560 if (lastWrapCache.index > indexCache.index && lineBreakMode != UILineBreakModeCharacterWrap &&
561 (!lastLine || lineBreakMode != UILineBreakModeClip)) {
562 // we're doing some sort of word wrapping
563 idx = lastWrapCache.index;
564 lineSize = lastWrapCache.lineSize;
565 if (!lastLine) {
566 // re-check if this is the last line
567 if (lastWrapCache.currentRunIdx != currentRunIdx) {
568 currentRunIdx = lastWrapCache.currentRunIdx;
569 READ_RUN();
570 READ_GLYPHS();
571 }
572 if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) {
573 lastLine = YES;
574 }
575 }
576 glyphIdx = lastWrapCache.glyphIndex;
577 // skip any spaces
578 for (NSUInteger j = idx; j < len && characters[j] == (unichar)' '; j++) {
579 skipCount++;
580 }
581 }
582 }
583 }
584 if (finishLine) {
585 // TODO: support head/middle truncation
586 if (lastLine && idx < len && lineBreakMode == UILineBreakModeTailTruncation) {
587 // truncate
588 unichar ellipsis = 0x2026; // ellipsis (…)
589 CGGlyph ellipsisGlyph;
590 mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL);
591 CGFloat ellipsisWidth;
592 mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth);
593 while ((idx - indexCache.index) > 1 && lineSize.width + ellipsisWidth > constrainedSize.width) {
594 // we have more than 1 character and we're too wide, so back up
595 idx--;
596 if (UnicharIsHighSurrogate(characters[idx]) && UnicharIsLowSurrogate(characters[idx+1])) {
597 idx--;
598 }
599 if (idx < currentRun.index) {
600 ZFont *oldFont = currentFont;
601 do {
602 currentRunIdx--;
603 READ_RUN();
604 } while (idx < currentRun.index);
605 READ_GLYPHS();
606 glyphIdx = glyphCount-1;
607 if (oldFont != currentFont) {
608 mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL);
609 mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth);
610 }
611 } else {
612 glyphIdx--;
613 }
614 lineSize.width -= advances[glyphIdx];
615 }
616 // skip any spaces before truncating
617 while ((idx - indexCache.index) > 1 && characters[idx-1] == (unichar)' ') {
618 idx--;
619 if (idx < currentRun.index) {
620 currentRunIdx--;
621 READ_RUN();
622 READ_GLYPHS();
623 glyphIdx = glyphCount-1;
624 } else {
625 glyphIdx--;
626 }
627 lineSize.width -= advances[glyphIdx];
628 }
629 lineSize.width += ellipsisWidth;
630 glyphs[glyphIdx] = ellipsisGlyph;
631 idx++;
632 glyphIdx++;
633 }
634 retValue.width = MAX(retValue.width, lineSize.width);
635 retValue.height += lineSize.height;
636
637 // draw
638 if (performDraw) {
639 switch (alignment) {
640 case UITextAlignmentLeft:
641 drawPoint.x = 0;
642 break;
643 case UITextAlignmentCenter:
644 drawPoint.x = (constrainedSize.width - lineSize.width) / 2.0f;
645 break;
646 case UITextAlignmentRight:
647 drawPoint.x = constrainedSize.width - lineSize.width;
648 break;
649 }
650 NSUInteger stopGlyphIdx = glyphIdx;
651 NSUInteger lastRunIdx = currentRunIdx;
652 NSUInteger stopCharIdx = idx;
653 idx = indexCache.index;
654 if (currentRunIdx != indexCache.currentRunIdx) {
655 currentRunIdx = indexCache.currentRunIdx;
656 READ_RUN();
657 READ_GLYPHS();
658 }
659 glyphIdx = indexCache.glyphIndex;
660 for (NSUInteger drawIdx = currentRunIdx; drawIdx <= lastRunIdx; drawIdx++) {
661 if (drawIdx != currentRunIdx) {
662 currentRunIdx = drawIdx;
663 READ_RUN();
664 READ_GLYPHS();
665 }
666 NSUInteger numGlyphs;
667 if (drawIdx == lastRunIdx) {
668 numGlyphs = stopGlyphIdx - glyphIdx;
669 idx = stopCharIdx;
670 } else {
671 numGlyphs = glyphCount - glyphIdx;
672 idx = nextRunStart;
673 }
674 CGContextSetFont(ctx, currentFont.cgFont);
675 CGContextSetFontSize(ctx, currentFont.pointSize);
676 // calculate the fragment size
677 CGFloat fragmentWidth = 0;
678 for (NSUInteger g = 0; g < numGlyphs; g++) {
679 fragmentWidth += advances[glyphIdx + g];
680 }
681
682 if (!ignoreColor) {
683 UIColor *foregroundColor = getValueOrDefaultForRun(currentRun, ZForegroundColorAttributeName);
684 UIColor *backgroundColor = getValueOrDefaultForRun(currentRun, ZBackgroundColorAttributeName);
685 if (backgroundColor != nil && ![backgroundColor isEqual:[UIColor clearColor]]) {
686 [backgroundColor setFill];
687 UIRectFillUsingBlendMode((CGRect){ drawPoint, { fragmentWidth, lineSize.height } }, kCGBlendModeNormal);
688 }
689 [foregroundColor setFill];
690 }
691
692 CGContextShowGlyphsAtPoint(ctx, drawPoint.x, drawPoint.y + lineAscender, &glyphs[glyphIdx], numGlyphs);
693 NSNumber *underlineStyle = getValueOrDefaultForRun(currentRun, ZUnderlineStyleAttributeName);
694 if ([underlineStyle integerValue] & ZUnderlineStyleMask) {
695 // we only support single for the time being
696 UIRectFill(CGRectMake(drawPoint.x, drawPoint.y + lineAscender, fragmentWidth, 1));
697 }
698 drawPoint.x += fragmentWidth;
699 glyphIdx += numGlyphs;
700 }
701 drawPoint.y += lineSize.height;
702 }
703 idx += skipCount;
704 glyphIdx += skipCount;
705 lineCount++;
706 } else {
707 lineSize.width += advances[glyphIdx];
708 glyphIdx++;
709 idx++;
710 if (idx < len && UnicharIsHighSurrogate(characters[idx-1]) && UnicharIsLowSurrogate(characters[idx])) {
711 // skip the second half of the surrogate pair
712 idx++;
713 }
714 }
715 }
716 }
717 CFRelease(fontTableMap);
718 free(glyphs);
719 free(advances);
720 free(characters);
721
722#undef READ_GLYPHS
723#undef READ_RUN
724
725 return retValue;
726}
727
728static NSArray *attributeRunForFont(ZFont *font) {
729 return [NSArray arrayWithObject:[ZAttributeRun attributeRunWithIndex:0
730 attributes:[NSDictionary dictionaryWithObject:font
731 forKey:ZFontAttributeName]]];
732}
733
734static CGSize drawTextInRect(CGRect rect, NSString *text, NSArray *attributes, UILineBreakMode lineBreakMode,
735 UITextAlignment alignment, NSUInteger numberOfLines, BOOL ignoreColor) {
736 CGContextRef ctx = UIGraphicsGetCurrentContext();
737
738 CGContextSaveGState(ctx);
739
740 // flip it upside-down because our 0,0 is upper-left, whereas ttfs are for screens where 0,0 is lower-left
741 CGAffineTransform textTransform = CGAffineTransformMake(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
742 CGContextSetTextMatrix(ctx, textTransform);
743
744 CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y);
745
746 CGContextSetTextDrawingMode(ctx, kCGTextFill);
747 CGSize size = drawOrSizeTextConstrainedToSize(YES, text, attributes, rect.size, numberOfLines, lineBreakMode, alignment, ignoreColor);
748
749 CGContextRestoreGState(ctx);
750
751 return size;
752}
753
754@implementation NSString (FontLabelStringDrawing)
755// CGFontRef-based methods
756- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
757 return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize]];
758}
759
760- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size {
761 return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size];
762}
763
764- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size
765 lineBreakMode:(UILineBreakMode)lineBreakMode {
766 return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size lineBreakMode:lineBreakMode];
767}
768
769- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
770 return [self drawAtPoint:point withZFont:[ZFont fontWithCGFont:font size:pointSize]];
771}
772
773- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
774 return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize]];
775}
776
777- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize lineBreakMode:(UILineBreakMode)lineBreakMode {
778 return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode];
779}
780
781- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
782 lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment {
783 return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode alignment:alignment];
784}
785
786// ZFont-based methods
787- (CGSize)sizeWithZFont:(ZFont *)font {
788 CGSize size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1,
789 UILineBreakModeClip, UITextAlignmentLeft, YES);
790 return CGSizeMake(ceilf(size.width), ceilf(size.height));
791}
792
793- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size {
794 return [self sizeWithZFont:font constrainedToSize:size lineBreakMode:UILineBreakModeWordWrap];
795}
796
797/*
798 According to experimentation with UIStringDrawing, this can actually return a CGSize whose height is greater
799 than the one passed in. The two cases are as follows:
800 1. If the given size parameter's height is smaller than a single line, the returned value will
801 be the height of one line.
802 2. If the given size parameter's height falls between multiples of a line height, and the wrapped string
803 actually extends past the size.height, and the difference between size.height and the previous multiple
804 of a line height is >= the font's ascender, then the returned size's height is extended to the next line.
805 To put it simply, if the baseline point of a given line falls in the given size, the entire line will
806 be present in the output size.
807 */
808- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode {
809 size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, 0, lineBreakMode, UITextAlignmentLeft, YES);
810 return CGSizeMake(ceilf(size.width), ceilf(size.height));
811}
812
813- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
814 numberOfLines:(NSUInteger)numberOfLines {
815 size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, numberOfLines, lineBreakMode, UITextAlignmentLeft, YES);
816 return CGSizeMake(ceilf(size.width), ceilf(size.height));
817}
818
819- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font {
820 return [self drawAtPoint:point forWidth:CGFLOAT_MAX withZFont:font lineBreakMode:UILineBreakModeClip];
821}
822
823- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode {
824 return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self, attributeRunForFont(font), lineBreakMode, UITextAlignmentLeft, 1, YES);
825}
826
827- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font {
828 return [self drawInRect:rect withZFont:font lineBreakMode:UILineBreakModeWordWrap];
829}
830
831- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode {
832 return [self drawInRect:rect withZFont:font lineBreakMode:lineBreakMode alignment:UITextAlignmentLeft];
833}
834
835- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
836 alignment:(UITextAlignment)alignment {
837 return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, 0, YES);
838}
839
840- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
841 alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines {
842 return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, numberOfLines, YES);
843}
844@end
845
846@implementation ZAttributedString (ZAttributedStringDrawing)
847- (CGSize)size {
848 CGSize size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1,
849 UILineBreakModeClip, UITextAlignmentLeft, NO);
850 return CGSizeMake(ceilf(size.width), ceilf(size.height));
851}
852
853- (CGSize)sizeConstrainedToSize:(CGSize)size {
854 return [self sizeConstrainedToSize:size lineBreakMode:UILineBreakModeWordWrap];
855}
856
857- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode {
858 size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, 0, lineBreakMode, UITextAlignmentLeft, NO);
859 return CGSizeMake(ceilf(size.width), ceilf(size.height));
860}
861
862- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
863 numberOfLines:(NSUInteger)numberOfLines {
864 size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, numberOfLines, lineBreakMode, UITextAlignmentLeft, NO);
865 return CGSizeMake(ceilf(size.width), ceilf(size.height));
866}
867
868- (CGSize)drawAtPoint:(CGPoint)point {
869 return [self drawAtPoint:point forWidth:CGFLOAT_MAX lineBreakMode:UILineBreakModeClip];
870}
871
872- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode {
873 return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self.string, self.attributes, lineBreakMode, UITextAlignmentLeft, 1, NO);
874}
875
876- (CGSize)drawInRect:(CGRect)rect {
877 return [self drawInRect:rect withLineBreakMode:UILineBreakModeWordWrap];
878}
879
880- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode {
881 return [self drawInRect:rect withLineBreakMode:lineBreakMode alignment:UITextAlignmentLeft];
882}
883
884- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment {
885 return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, 0, NO);
886}
887
888- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment
889 numberOfLines:(NSUInteger)numberOfLines {
890 return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, numberOfLines, NO);
891}
892@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 @@
1//
2// FontManager.h
3// FontLabel
4//
5// Created by Kevin Ballard on 5/5/09.
6// Copyright © 2009 Zynga Game Networks
7//
8//
9// Licensed under the Apache License, Version 2.0 (the "License");
10// you may not use this file except in compliance with the License.
11// You may obtain a copy of the License at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20//
21
22#import <Foundation/Foundation.h>
23#import <CoreGraphics/CoreGraphics.h>
24
25@class ZFont;
26
27@interface FontManager : NSObject {
28 CFMutableDictionaryRef fonts;
29 NSMutableDictionary *urls;
30}
31+ (FontManager *)sharedManager;
32/*!
33 @method
34 @abstract Loads a TTF font from the main bundle
35 @param filename The name of the font file to load (with or without extension).
36 @return YES if the font was loaded, NO if an error occurred
37 @discussion If the font has already been loaded, this method does nothing and returns YES.
38 This method first attempts to load the font by appending .ttf to the filename.
39 If that file does not exist, it tries the filename exactly as given.
40*/
41- (BOOL)loadFont:(NSString *)filename;
42/*!
43 @method
44 @abstract Loads a font from the given file URL
45 @param url A file URL that points to a font file
46 @return YES if the font was loaded, NO if an error occurred
47 @discussion If the font has already been loaded, this method does nothing and returns YES.
48*/
49- (BOOL)loadFontURL:(NSURL *)url;
50/*!
51 @method
52 @abstract Returns the loaded font with the given filename
53 @param filename The name of the font file that was given to -loadFont:
54 @return A CGFontRef, or NULL if the specified font cannot be found
55 @discussion If the font has not been loaded yet, -loadFont: will be
56 called with the given name first.
57*/
58- (CGFontRef)fontWithName:(NSString *)filename __AVAILABILITY_INTERNAL_DEPRECATED;
59/*!
60 @method
61 @abstract Returns a ZFont object corresponding to the loaded font with the given filename and point size
62 @param filename The name of the font file that was given to -loadFont:
63 @param pointSize The point size of the font
64 @return A ZFont, or NULL if the specified font cannot be found
65 @discussion If the font has not been loaded yet, -loadFont: will be
66 called with the given name first.
67*/
68- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize;
69/*!
70 @method
71 @abstract Returns a ZFont object corresponding to the loaded font with the given file URL and point size
72 @param url A file URL that points to a font file
73 @param pointSize The point size of the font
74 @return A ZFont, or NULL if the specified font cannot be loaded
75 @discussion If the font has not been loaded yet, -loadFontURL: will be called with the given URL first.
76*/
77- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize;
78/*!
79 @method
80 @abstract Returns a CFArrayRef of all loaded CGFont objects
81 @return A CFArrayRef of all loaded CGFont objects
82 @description You are responsible for releasing the CFArrayRef
83*/
84- (CFArrayRef)copyAllFonts;
85@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 @@
1//
2// FontManager.m
3// FontLabel
4//
5// Created by Kevin Ballard on 5/5/09.
6// Copyright © 2009 Zynga Game Networks
7//
8//
9// Licensed under the Apache License, Version 2.0 (the "License");
10// you may not use this file except in compliance with the License.
11// You may obtain a copy of the License at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20//
21
22#import "FontManager.h"
23#import "ZFont.h"
24
25static FontManager *sharedFontManager = nil;
26
27@implementation FontManager
28+ (FontManager *)sharedManager {
29 @synchronized(self) {
30 if (sharedFontManager == nil) {
31 sharedFontManager = [[self alloc] init];
32 }
33 }
34 return sharedFontManager;
35}
36
37- (id)init {
38 if ((self = [super init])) {
39 fonts = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
40 urls = [[NSMutableDictionary alloc] init];
41 }
42 return self;
43}
44
45- (BOOL)loadFont:(NSString *)filename {
46 NSString *fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:@"ttf"];
47 if (fontPath == nil) {
48 fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
49 }
50 if (fontPath == nil) return NO;
51
52 NSURL *url = [NSURL fileURLWithPath:fontPath];
53 if ([self loadFontURL:url]) {
54 [urls setObject:url forKey:filename];
55 return YES;
56 }
57 return NO;
58}
59
60- (BOOL)loadFontURL:(NSURL *)url {
61 CGDataProviderRef fontDataProvider = CGDataProviderCreateWithURL((CFURLRef)url);
62 if (fontDataProvider == NULL) return NO;
63 CGFontRef newFont = CGFontCreateWithDataProvider(fontDataProvider);
64 CGDataProviderRelease(fontDataProvider);
65 if (newFont == NULL) return NO;
66
67 CFDictionarySetValue(fonts, url, newFont);
68 CGFontRelease(newFont);
69 return YES;
70}
71
72- (CGFontRef)fontWithName:(NSString *)filename {
73 CGFontRef font = NULL;
74 NSURL *url = [urls objectForKey:filename];
75 if (url == nil && [self loadFont:filename]) {
76 url = [urls objectForKey:filename];
77 }
78 if (url != nil) {
79 font = (CGFontRef)CFDictionaryGetValue(fonts, url);
80 }
81 return font;
82}
83
84- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize {
85 NSURL *url = [urls objectForKey:filename];
86 if (url == nil && [self loadFont:filename]) {
87 url = [urls objectForKey:filename];
88 }
89 if (url != nil) {
90 CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
91 if (cgFont != NULL) {
92 return [ZFont fontWithCGFont:cgFont size:pointSize];
93 }
94 }
95 return nil;
96}
97
98- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize {
99 CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
100 if (cgFont == NULL && [self loadFontURL:url]) {
101 cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
102 }
103 if (cgFont != NULL) {
104 return [ZFont fontWithCGFont:cgFont size:pointSize];
105 }
106 return nil;
107}
108
109- (CFArrayRef)copyAllFonts {
110 CFIndex count = CFDictionaryGetCount(fonts);
111 CGFontRef *values = (CGFontRef *)malloc(sizeof(CGFontRef) * count);
112 CFDictionaryGetKeysAndValues(fonts, NULL, (const void **)values);
113 CFArrayRef array = CFArrayCreate(NULL, (const void **)values, count, &kCFTypeArrayCallBacks);
114 free(values);
115 return array;
116}
117
118- (void)dealloc {
119 CFRelease(fonts);
120 [urls release];
121 [super dealloc];
122}
123@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 @@
1//
2// ZAttributedString.h
3// FontLabel
4//
5// Created by Kevin Ballard on 9/22/09.
6// Copyright 2009 Zynga Game Networks. All rights reserved.
7//
8
9#import <Foundation/Foundation.h>
10
11#if NS_BLOCKS_AVAILABLE
12#define Z_BLOCKS 1
13#else
14// set this to 1 if you are using PLBlocks
15#define Z_BLOCKS 0
16#endif
17
18#if Z_BLOCKS
19enum {
20 ZAttributedStringEnumerationReverse = (1UL << 1),
21 ZAttributedStringEnumerationLongestEffectiveRangeNotRequired = (1UL << 20)
22};
23typedef NSUInteger ZAttributedStringEnumerationOptions;
24#endif
25
26@interface ZAttributedString : NSObject <NSCoding, NSCopying, NSMutableCopying> {
27 NSMutableString *_buffer;
28 NSMutableArray *_attributes;
29}
30@property (nonatomic, readonly) NSUInteger length;
31@property (nonatomic, readonly) NSString *string;
32- (id)initWithAttributedString:(ZAttributedString *)attr;
33- (id)initWithString:(NSString *)str;
34- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes;
35- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange;
36- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit;
37- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange;
38- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange;
39- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit;
40#if Z_BLOCKS
41- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
42 usingBlock:(void (^)(id value, NSRange range, BOOL *stop))block;
43- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
44 usingBlock:(void (^)(NSDictionary *attrs, NSRange range, BOOL *stop))block;
45#endif
46- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString;
47@end
48
49@interface ZMutableAttributedString : ZAttributedString {
50}
51- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range;
52- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range;
53- (void)appendAttributedString:(ZAttributedString *)str;
54- (void)deleteCharactersInRange:(NSRange)range;
55- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx;
56- (void)removeAttribute:(NSString *)name range:(NSRange)range;
57- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str;
58- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
59- (void)setAttributedString:(ZAttributedString *)str;
60- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range;
61@end
62
63extern NSString * const ZFontAttributeName;
64extern NSString * const ZForegroundColorAttributeName;
65extern NSString * const ZBackgroundColorAttributeName;
66extern NSString * const ZUnderlineStyleAttributeName;
67
68enum {
69 ZUnderlineStyleNone = 0x00,
70 ZUnderlineStyleSingle = 0x01
71};
72#define ZUnderlineStyleMask 0x00FF
73
74enum {
75 ZUnderlinePatternSolid = 0x0000
76};
77#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 @@
1//
2// ZAttributedString.m
3// FontLabel
4//
5// Created by Kevin Ballard on 9/22/09.
6// Copyright 2009 Zynga Game Networks. All rights reserved.
7//
8
9#import "ZAttributedString.h"
10#import "ZAttributedStringPrivate.h"
11
12@interface ZAttributedString ()
13- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index;
14- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName;
15- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange
16 inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName;
17@end
18
19@interface ZAttributedString ()
20@property (nonatomic, readonly) NSArray *attributes;
21@end
22
23@implementation ZAttributedString
24@synthesize string = _buffer;
25@synthesize attributes = _attributes;
26
27- (id)initWithAttributedString:(ZAttributedString *)attr {
28 NSParameterAssert(attr != nil);
29 if ((self = [super init])) {
30 _buffer = [attr->_buffer mutableCopy];
31 _attributes = [[NSMutableArray alloc] initWithArray:attr->_attributes copyItems:YES];
32 }
33 return self;
34}
35
36- (id)initWithString:(NSString *)str {
37 return [self initWithString:str attributes:nil];
38}
39
40- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes {
41 if ((self = [super init])) {
42 _buffer = [str mutableCopy];
43 _attributes = [[NSMutableArray alloc] initWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil];
44 }
45 return self;
46}
47
48- (id)init {
49 return [self initWithString:@"" attributes:nil];
50}
51
52- (id)initWithCoder:(NSCoder *)decoder {
53 if ((self = [super init])) {
54 _buffer = [[decoder decodeObjectForKey:@"buffer"] mutableCopy];
55 _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy];
56 }
57 return self;
58}
59
60- (void)encodeWithCoder:(NSCoder *)aCoder {
61 [aCoder encodeObject:_buffer forKey:@"buffer"];
62 [aCoder encodeObject:_attributes forKey:@"attributes"];
63}
64
65- (id)copyWithZone:(NSZone *)zone {
66 return [self retain];
67}
68
69- (id)mutableCopyWithZone:(NSZone *)zone {
70 return [(ZMutableAttributedString *)[ZMutableAttributedString allocWithZone:zone] initWithAttributedString:self];
71}
72
73- (NSUInteger)length {
74 return [_buffer length];
75}
76
77- (NSString *)description {
78 NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]*2];
79 NSRange range = NSMakeRange(0, 0);
80 for (NSUInteger i = 0; i <= [_attributes count]; i++) {
81 range.location = NSMaxRange(range);
82 ZAttributeRun *run;
83 if (i < [_attributes count]) {
84 run = [_attributes objectAtIndex:i];
85 range.length = run.index - range.location;
86 } else {
87 run = nil;
88 range.length = [_buffer length] - range.location;
89 }
90 if (range.length > 0) {
91 [components addObject:[NSString stringWithFormat:@"\"%@\"", [_buffer substringWithRange:range]]];
92 }
93 if (run != nil) {
94 NSMutableArray *attrDesc = [NSMutableArray arrayWithCapacity:[run.attributes count]];
95 for (id key in run.attributes) {
96 [attrDesc addObject:[NSString stringWithFormat:@"%@: %@", key, [run.attributes objectForKey:key]]];
97 }
98 [components addObject:[NSString stringWithFormat:@"{%@}", [attrDesc componentsJoinedByString:@", "]]];
99 }
100 }
101 return [NSString stringWithFormat:@"%@", [components componentsJoinedByString:@" "]];
102}
103
104- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange {
105 NSParameterAssert(attributeName != nil);
106 return [[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:attributeName] objectForKey:attributeName];
107}
108
109- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit {
110 NSParameterAssert(attributeName != nil);
111 return [[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:attributeName] objectForKey:attributeName];
112}
113
114- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange {
115 if (NSMaxRange(aRange) > [_buffer length]) {
116 @throw [NSException exceptionWithName:NSRangeException reason:@"range was outisde of the attributed string" userInfo:nil];
117 }
118 ZMutableAttributedString *newStr = [self mutableCopy];
119 if (aRange.location > 0) {
120 [newStr deleteCharactersInRange:NSMakeRange(0, aRange.location)];
121 }
122 if (NSMaxRange(aRange) < [_buffer length]) {
123 [newStr deleteCharactersInRange:NSMakeRange(aRange.length, [_buffer length] - NSMaxRange(aRange))];
124 }
125 return [newStr autorelease];
126}
127
128- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange {
129 return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:nil]];
130}
131
132- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit {
133 return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:nil]];
134}
135
136#if Z_BLOCKS
137// Warning: this code has not been tested. The only guarantee is that it compiles.
138- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
139 usingBlock:(void (^)(id, NSRange, BOOL*))block {
140 if (opts & ZAttributedStringEnumerationLongestEffectiveRangeNotRequired) {
141 [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
142 id value = [attrs objectForKey:attrName];
143 if (value != nil) {
144 block(value, range, stop);
145 }
146 }];
147 } else {
148 __block id oldValue = nil;
149 __block NSRange effectiveRange = NSMakeRange(0, 0);
150 [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
151 id value = [attrs objectForKey:attrName];
152 if (oldValue == nil) {
153 oldValue = value;
154 effectiveRange = range;
155 } else if (value != nil && [oldValue isEqual:value]) {
156 // combine the attributes
157 effectiveRange = NSUnionRange(effectiveRange, range);
158 } else {
159 BOOL innerStop = NO;
160 block(oldValue, effectiveRange, &innerStop);
161 if (innerStop) {
162 *stop = YES;
163 oldValue = nil;
164 } else {
165 oldValue = value;
166 }
167 }
168 }];
169 if (oldValue != nil) {
170 BOOL innerStop = NO; // necessary for the block, but unused
171 block(oldValue, effectiveRange, &innerStop);
172 }
173 }
174}
175
176- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
177 usingBlock:(void (^)(NSDictionary*, NSRange, BOOL*))block {
178 // copy the attributes so we can mutate the string if necessary during enumeration
179 // also clip the array during copy to only the subarray of attributes that cover the requested range
180 NSArray *attrs;
181 if (NSEqualRanges(enumerationRange, NSMakeRange(0, 0))) {
182 attrs = [NSArray arrayWithArray:_attributes];
183 } else {
184 // in this binary search, last is the first run after the range
185 NSUInteger first = 0, last = [_attributes count];
186 while (last > first+1) {
187 NSUInteger pivot = (last + first) / 2;
188 ZAttributeRun *run = [_attributes objectAtIndex:pivot];
189 if (run.index < enumerationRange.location) {
190 first = pivot;
191 } else if (run.index >= NSMaxRange(enumerationRange)) {
192 last = pivot;
193 }
194 }
195 attrs = [_attributes subarrayWithRange:NSMakeRange(first, last-first)];
196 }
197 if (opts & ZAttributedStringEnumerationReverse) {
198 NSUInteger end = [_buffer length];
199 for (ZAttributeRun *run in [attrs reverseObjectEnumerator]) {
200 BOOL stop = NO;
201 NSUInteger start = run.index;
202 // clip to enumerationRange
203 start = MAX(start, enumerationRange.location);
204 end = MIN(end, NSMaxRange(enumerationRange));
205 block(run.attributes, NSMakeRange(start, end - start), &stop);
206 if (stop) break;
207 end = run.index;
208 }
209 } else {
210 NSUInteger start = 0;
211 ZAttributeRun *run = [attrs objectAtIndex:0];
212 NSInteger offset = 0;
213 NSInteger oldLength = [_buffer length];
214 for (NSUInteger i = 1;;i++) {
215 NSUInteger end;
216 if (i >= [attrs count]) {
217 end = oldLength;
218 } else {
219 end = [[attrs objectAtIndex:i] index];
220 }
221 BOOL stop = NO;
222 NSUInteger clippedStart = MAX(start, enumerationRange.location);
223 NSUInteger clippedEnd = MIN(end, NSMaxRange(enumerationRange));
224 block(run.attributes, NSMakeRange(clippedStart + offset, clippedEnd - start), &stop);
225 if (stop || i >= [attrs count]) break;
226 start = end;
227 NSUInteger newLength = [_buffer length];
228 offset += (newLength - oldLength);
229 oldLength = newLength;
230 }
231 }
232}
233#endif
234
235- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString {
236 return ([_buffer isEqualToString:otherString->_buffer] && [_attributes isEqualToArray:otherString->_attributes]);
237}
238
239- (BOOL)isEqual:(id)object {
240 return [object isKindOfClass:[ZAttributedString class]] && [self isEqualToAttributedString:(ZAttributedString *)object];
241}
242
243#pragma mark -
244
245- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index {
246 NSUInteger first = 0, last = [_attributes count];
247 while (last > first + 1) {
248 NSUInteger pivot = (last + first) / 2;
249 ZAttributeRun *run = [_attributes objectAtIndex:pivot];
250 if (run.index > index) {
251 last = pivot;
252 } else if (run.index < index) {
253 first = pivot;
254 } else {
255 first = pivot;
256 break;
257 }
258 }
259 return first;
260}
261
262- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName {
263 if (index >= [_buffer length]) {
264 @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil];
265 }
266 NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index];
267 ZAttributeRun *run = [_attributes objectAtIndex:runIndex];
268 if (aRange != NULL) {
269 aRange->location = run.index;
270 runIndex++;
271 if (runIndex < [_attributes count]) {
272 aRange->length = [[_attributes objectAtIndex:runIndex] index] - aRange->location;
273 } else {
274 aRange->length = [_buffer length] - aRange->location;
275 }
276 }
277 return run.attributes;
278}
279- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange
280 inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName {
281 if (index >= [_buffer length]) {
282 @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil];
283 } else if (NSMaxRange(rangeLimit) > [_buffer length]) {
284 @throw [NSException exceptionWithName:NSRangeException reason:@"rangeLimit beyond range of attributed string" userInfo:nil];
285 }
286 NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index];
287 ZAttributeRun *run = [_attributes objectAtIndex:runIndex];
288 if (aRange != NULL) {
289 if (attributeName != nil) {
290 id value = [run.attributes objectForKey:attributeName];
291 NSUInteger endRunIndex = runIndex+1;
292 runIndex--;
293 // search backwards
294 while (1) {
295 if (run.index <= rangeLimit.location) {
296 break;
297 }
298 ZAttributeRun *prevRun = [_attributes objectAtIndex:runIndex];
299 id prevValue = [prevRun.attributes objectForKey:attributeName];
300 if (prevValue == value || (value != nil && [prevValue isEqual:value])) {
301 runIndex--;
302 run = prevRun;
303 } else {
304 break;
305 }
306 }
307 // search forwards
308 ZAttributeRun *endRun = nil;
309 while (endRunIndex < [_attributes count]) {
310 ZAttributeRun *nextRun = [_attributes objectAtIndex:endRunIndex];
311 if (nextRun.index >= NSMaxRange(rangeLimit)) {
312 endRun = nextRun;
313 break;
314 }
315 id nextValue = [nextRun.attributes objectForKey:attributeName];
316 if (nextValue == value || (value != nil && [nextValue isEqual:value])) {
317 endRunIndex++;
318 } else {
319 endRun = nextRun;
320 break;
321 }
322 }
323 aRange->location = MAX(run.index, rangeLimit.location);
324 aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location;
325 } else {
326 // with no attribute name, we don't need to do any real searching,
327 // as we already guarantee each run has unique attributes.
328 // just make sure to clip the range to the rangeLimit
329 aRange->location = MAX(run.index, rangeLimit.location);
330 ZAttributeRun *endRun = (runIndex+1 < [_attributes count] ? [_attributes objectAtIndex:runIndex+1] : nil);
331 aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location;
332 }
333 }
334 return run.attributes;
335}
336
337- (void)dealloc {
338 [_buffer release];
339 [_attributes release];
340 [super dealloc];
341}
342@end
343
344@interface ZMutableAttributedString ()
345- (void)cleanupAttributesInRange:(NSRange)range;
346- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range;
347- (void)offsetRunsInRange:(NSRange )range byOffset:(NSInteger)offset;
348@end
349
350@implementation ZMutableAttributedString
351- (id)copyWithZone:(NSZone *)zone {
352 return [(ZAttributedString *)[ZAttributedString allocWithZone:zone] initWithAttributedString:self];
353}
354
355- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range {
356 range = [self rangeOfAttributeRunsForRange:range];
357 for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
358 [run.attributes setObject:value forKey:name];
359 }
360 [self cleanupAttributesInRange:range];
361}
362
363- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range {
364 range = [self rangeOfAttributeRunsForRange:range];
365 for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
366 [run.attributes addEntriesFromDictionary:attributes];
367 }
368 [self cleanupAttributesInRange:range];
369}
370
371- (void)appendAttributedString:(ZAttributedString *)str {
372 [self insertAttributedString:str atIndex:[_buffer length]];
373}
374
375- (void)deleteCharactersInRange:(NSRange)range {
376 NSRange runRange = [self rangeOfAttributeRunsForRange:range];
377 [_buffer replaceCharactersInRange:range withString:@""];
378 [_attributes removeObjectsInRange:runRange];
379 for (NSUInteger i = runRange.location; i < [_attributes count]; i++) {
380 ZAttributeRun *run = [_attributes objectAtIndex:i];
381 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:(run.index - range.length) attributes:run.attributes];
382 [_attributes replaceObjectAtIndex:i withObject:newRun];
383 [newRun release];
384 }
385 [self cleanupAttributesInRange:NSMakeRange(runRange.location, 0)];
386}
387
388- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx {
389 [self replaceCharactersInRange:NSMakeRange(idx, 0) withAttributedString:str];
390}
391
392- (void)removeAttribute:(NSString *)name range:(NSRange)range {
393 range = [self rangeOfAttributeRunsForRange:range];
394 for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
395 [run.attributes removeObjectForKey:name];
396 }
397 [self cleanupAttributesInRange:range];
398}
399
400- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str {
401 NSRange replaceRange = [self rangeOfAttributeRunsForRange:range];
402 NSInteger offset = [str->_buffer length] - range.length;
403 [_buffer replaceCharactersInRange:range withString:str->_buffer];
404 [_attributes replaceObjectsInRange:replaceRange withObjectsFromArray:str->_attributes];
405 NSRange newRange = NSMakeRange(replaceRange.location, [str->_attributes count]);
406 [self offsetRunsInRange:newRange byOffset:range.location];
407 [self offsetRunsInRange:NSMakeRange(NSMaxRange(newRange), [_attributes count] - NSMaxRange(newRange)) byOffset:offset];
408 [self cleanupAttributesInRange:NSMakeRange(newRange.location, 0)];
409 [self cleanupAttributesInRange:NSMakeRange(NSMaxRange(newRange), 0)];
410}
411
412- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str {
413 [self replaceCharactersInRange:range withAttributedString:[[[ZAttributedString alloc] initWithString:str] autorelease]];
414}
415
416- (void)setAttributedString:(ZAttributedString *)str {
417 [_buffer release], _buffer = [str->_buffer mutableCopy];
418 [_attributes release], _attributes = [str->_attributes mutableCopy];
419}
420
421- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range {
422 range = [self rangeOfAttributeRunsForRange:range];
423 for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
424 [run.attributes setDictionary:attributes];
425 }
426 [self cleanupAttributesInRange:range];
427}
428
429#pragma mark -
430
431// splits the existing runs to provide one or more new runs for the given range
432- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range {
433 NSParameterAssert(NSMaxRange(range) <= [_buffer length]);
434
435 // find (or create) the first run
436 NSUInteger first = 0;
437 ZAttributeRun *lastRun = nil;
438 for (;;first++) {
439 if (first >= [_attributes count]) {
440 // we didn't find a run
441 first = [_attributes count];
442 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes];
443 [_attributes addObject:newRun];
444 [newRun release];
445 break;
446 }
447 ZAttributeRun *run = [_attributes objectAtIndex:first];
448 if (run.index == range.location) {
449 break;
450 } else if (run.index > range.location) {
451 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes];
452 [_attributes insertObject:newRun atIndex:first];
453 [newRun release];
454 break;
455 }
456 lastRun = run;
457 }
458
459 if (((ZAttributeRun *)[_attributes lastObject]).index < NSMaxRange(range)) {
460 NSRange subrange = NSMakeRange(first, [_attributes count] - first);
461 if (NSMaxRange(range) < [_buffer length]) {
462 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range)
463 attributes:(NSDictionary*)[(ZAttributeRun *)[_attributes lastObject] attributes]];
464 [_attributes addObject:newRun];
465 [newRun release];
466 }
467 return subrange;
468 } else {
469 // find the last run within and the first run after the range
470 NSUInteger lastIn = first, firstAfter = [_attributes count]-1;
471 while (firstAfter > lastIn + 1) {
472 NSUInteger idx = (firstAfter + lastIn) / 2;
473 ZAttributeRun *run = [_attributes objectAtIndex:idx];
474 if (run.index < range.location) {
475 lastIn = idx;
476 } else if (run.index > range.location) {
477 firstAfter = idx;
478 } else {
479 // this is definitively the first run after the range
480 firstAfter = idx;
481 break;
482 }
483 }
484 if ([[_attributes objectAtIndex:firstAfter] index] > NSMaxRange(range)) {
485 // the first after is too far after, insert another run!
486 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range)
487 attributes:[(ZAttributeRun *)[_attributes objectAtIndex:firstAfter-1] attributes]];
488 [_attributes insertObject:newRun atIndex:firstAfter];
489 [newRun release];
490 }
491 return NSMakeRange(lastIn, firstAfter - lastIn);
492 }
493}
494
495- (void)cleanupAttributesInRange:(NSRange)range {
496 // expand the range to include one surrounding attribute on each side
497 if (range.location > 0) {
498 range.location -= 1;
499 range.length += 1;
500 }
501 if (NSMaxRange(range) < [_attributes count]) {
502 range.length += 1;
503 } else {
504 // make sure the range is capped to the attributes count
505 range.length = [_attributes count] - range.location;
506 }
507 if (range.length == 0) return;
508 ZAttributeRun *lastRun = [_attributes objectAtIndex:range.location];
509 for (NSUInteger i = range.location+1; i < NSMaxRange(range);) {
510 ZAttributeRun *run = [_attributes objectAtIndex:i];
511 if ([lastRun.attributes isEqualToDictionary:run.attributes]) {
512 [_attributes removeObjectAtIndex:i];
513 range.length -= 1;
514 } else {
515 lastRun = run;
516 i++;
517 }
518 }
519}
520
521- (void)offsetRunsInRange:(NSRange)range byOffset:(NSInteger)offset {
522 for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
523 ZAttributeRun *run = [_attributes objectAtIndex:i];
524 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:run.index + offset attributes:run.attributes];
525 [_attributes replaceObjectAtIndex:i withObject:newRun];
526 [newRun release];
527 }
528}
529@end
530
531@implementation ZAttributeRun
532@synthesize index = _index;
533@synthesize attributes = _attributes;
534
535+ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs {
536 return [[[self alloc] initWithIndex:idx attributes:attrs] autorelease];
537}
538
539- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs {
540 NSParameterAssert(idx >= 0);
541 if ((self = [super init])) {
542 _index = idx;
543 if (attrs == nil) {
544 _attributes = [[NSMutableDictionary alloc] init];
545 } else {
546 _attributes = [attrs mutableCopy];
547 }
548 }
549 return self;
550}
551
552- (id)initWithCoder:(NSCoder *)decoder {
553 if ((self = [super init])) {
554 _index = [[decoder decodeObjectForKey:@"index"] unsignedIntegerValue];
555 _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy];
556 }
557 return self;
558}
559
560- (id)init {
561 return [self initWithIndex:0 attributes:[NSDictionary dictionary]];
562}
563
564- (id)copyWithZone:(NSZone *)zone {
565 return [[ZAttributeRun allocWithZone:zone] initWithIndex:_index attributes:_attributes];
566}
567
568- (void)encodeWithCoder:(NSCoder *)aCoder {
569 [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:_index] forKey:@"index"];
570 [aCoder encodeObject:_attributes forKey:@"attributes"];
571}
572
573- (NSString *)description {
574 NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]];
575 for (id key in _attributes) {
576 [components addObject:[NSString stringWithFormat:@"%@=%@", key, [_attributes objectForKey:key]]];
577 }
578 return [NSString stringWithFormat:@"<%@: %p index=%lu attributes={%@}>",
579 NSStringFromClass([self class]), self, (unsigned long)_index, [components componentsJoinedByString:@" "]];
580}
581
582- (BOOL)isEqual:(id)object {
583 if (![object isKindOfClass:[ZAttributeRun class]]) return NO;
584 ZAttributeRun *other = (ZAttributeRun *)object;
585 return _index == other->_index && [_attributes isEqualToDictionary:other->_attributes];
586}
587
588- (void)dealloc {
589 [_attributes release];
590 [super dealloc];
591}
592@end
593
594NSString * const ZFontAttributeName = @"ZFontAttributeName";
595NSString * const ZForegroundColorAttributeName = @"ZForegroundColorAttributeName";
596NSString * const ZBackgroundColorAttributeName = @"ZBackgroundColorAttributeName";
597NSString * 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 @@
1//
2// ZAttributedStringPrivate.h
3// FontLabel
4//
5// Created by Kevin Ballard on 9/23/09.
6// Copyright 2009 Zynga Game Networks. All rights reserved.
7//
8
9#import <Foundation/Foundation.h>
10#import "ZAttributedString.h"
11
12@interface ZAttributeRun : NSObject <NSCopying, NSCoding> {
13 NSUInteger _index;
14 NSMutableDictionary *_attributes;
15}
16@property (nonatomic, readonly) NSUInteger index;
17@property (nonatomic, readonly) NSMutableDictionary *attributes;
18+ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs;
19- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs;
20@end
21
22@interface ZAttributedString (ZAttributedStringPrivate)
23@property (nonatomic, readonly) NSArray *attributes;
24@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 @@
1//
2// ZFont.h
3// FontLabel
4//
5// Created by Kevin Ballard on 7/2/09.
6// Copyright © 2009 Zynga Game Networks
7//
8//
9// Licensed under the Apache License, Version 2.0 (the "License");
10// you may not use this file except in compliance with the License.
11// You may obtain a copy of the License at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20//
21
22#import <Foundation/Foundation.h>
23#import <UIKit/UIKit.h>
24
25@interface ZFont : NSObject {
26 CGFontRef _cgFont;
27 CGFloat _pointSize;
28 CGFloat _ratio;
29 NSString *_familyName;
30 NSString *_fontName;
31 NSString *_postScriptName;
32}
33@property (nonatomic, readonly) CGFontRef cgFont;
34@property (nonatomic, readonly) CGFloat pointSize;
35@property (nonatomic, readonly) CGFloat ascender;
36@property (nonatomic, readonly) CGFloat descender;
37@property (nonatomic, readonly) CGFloat leading;
38@property (nonatomic, readonly) CGFloat xHeight;
39@property (nonatomic, readonly) CGFloat capHeight;
40@property (nonatomic, readonly) NSString *familyName;
41@property (nonatomic, readonly) NSString *fontName;
42@property (nonatomic, readonly) NSString *postScriptName;
43+ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize;
44+ (ZFont *)fontWithUIFont:(UIFont *)uiFont;
45- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize;
46- (ZFont *)fontWithSize:(CGFloat)fontSize;
47@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 @@
1//
2// ZFont.m
3// FontLabel
4//
5// Created by Kevin Ballard on 7/2/09.
6// Copyright © 2009 Zynga Game Networks
7//
8//
9// Licensed under the Apache License, Version 2.0 (the "License");
10// you may not use this file except in compliance with the License.
11// You may obtain a copy of the License at
12//
13// http://www.apache.org/licenses/LICENSE-2.0
14//
15// Unless required by applicable law or agreed to in writing, software
16// distributed under the License is distributed on an "AS IS" BASIS,
17// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18// See the License for the specific language governing permissions and
19// limitations under the License.
20//
21
22#import "ZFont.h"
23
24@interface ZFont ()
25@property (nonatomic, readonly) CGFloat ratio;
26- (NSString *)copyNameTableEntryForID:(UInt16)nameID;
27@end
28
29@implementation ZFont
30@synthesize cgFont=_cgFont, pointSize=_pointSize, ratio=_ratio;
31
32+ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize {
33 return [[[self alloc] initWithCGFont:cgFont size:fontSize] autorelease];
34}
35
36+ (ZFont *)fontWithUIFont:(UIFont *)uiFont {
37 NSParameterAssert(uiFont != nil);
38 CGFontRef cgFont = CGFontCreateWithFontName((CFStringRef)uiFont.fontName);
39 ZFont *zFont = [[self alloc] initWithCGFont:cgFont size:uiFont.pointSize];
40 CGFontRelease(cgFont);
41 return [zFont autorelease];
42}
43
44- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize {
45 if ((self = [super init])) {
46 _cgFont = CGFontRetain(cgFont);
47 _pointSize = fontSize;
48 _ratio = fontSize/CGFontGetUnitsPerEm(cgFont);
49 }
50 return self;
51}
52
53- (id)init {
54 NSAssert(NO, @"-init is not valid for ZFont");
55 return nil;
56}
57
58- (CGFloat)ascender {
59 return ceilf(self.ratio * CGFontGetAscent(self.cgFont));
60}
61
62- (CGFloat)descender {
63 return floorf(self.ratio * CGFontGetDescent(self.cgFont));
64}
65
66- (CGFloat)leading {
67 return (self.ascender - self.descender);
68}
69
70- (CGFloat)capHeight {
71 return ceilf(self.ratio * CGFontGetCapHeight(self.cgFont));
72}
73
74- (CGFloat)xHeight {
75 return ceilf(self.ratio * CGFontGetXHeight(self.cgFont));
76}
77
78- (NSString *)familyName {
79 if (_familyName == nil) {
80 _familyName = [self copyNameTableEntryForID:1];
81 }
82 return _familyName;
83}
84
85- (NSString *)fontName {
86 if (_fontName == nil) {
87 _fontName = [self copyNameTableEntryForID:4];
88 }
89 return _fontName;
90}
91
92- (NSString *)postScriptName {
93 if (_postScriptName == nil) {
94 _postScriptName = [self copyNameTableEntryForID:6];
95 }
96 return _postScriptName;
97}
98
99- (ZFont *)fontWithSize:(CGFloat)fontSize {
100 if (fontSize == self.pointSize) return self;
101 NSParameterAssert(fontSize > 0.0);
102 return [[[ZFont alloc] initWithCGFont:self.cgFont size:fontSize] autorelease];
103}
104
105- (BOOL)isEqual:(id)object {
106 if (![object isKindOfClass:[ZFont class]]) return NO;
107 ZFont *font = (ZFont *)object;
108 return (font.cgFont == self.cgFont && font.pointSize == self.pointSize);
109}
110
111- (NSString *)copyNameTableEntryForID:(UInt16)aNameID {
112 CFDataRef nameTable = CGFontCopyTableForTag(self.cgFont, 'name');
113 NSAssert1(nameTable != NULL, @"CGFontCopyTableForTag returned NULL for 'name' tag in font %@",
114 [(id)CFCopyDescription(self.cgFont) autorelease]);
115 const UInt8 * const bytes = CFDataGetBytePtr(nameTable);
116 NSAssert1(OSReadBigInt16(bytes, 0) == 0, @"name table for font %@ has bad version number",
117 [(id)CFCopyDescription(self.cgFont) autorelease]);
118 const UInt16 count = OSReadBigInt16(bytes, 2);
119 const UInt16 stringOffset = OSReadBigInt16(bytes, 4);
120 const UInt8 * const nameRecords = &bytes[6];
121 UInt16 nameLength = 0;
122 UInt16 nameOffset = 0;
123 NSStringEncoding encoding = 0;
124 for (UInt16 idx = 0; idx < count; idx++) {
125 const uintptr_t recordOffset = 12 * idx;
126 const UInt16 nameID = OSReadBigInt16(nameRecords, recordOffset + 6);
127 if (nameID != aNameID) continue;
128 const UInt16 platformID = OSReadBigInt16(nameRecords, recordOffset + 0);
129 const UInt16 platformSpecificID = OSReadBigInt16(nameRecords, recordOffset + 2);
130 encoding = 0;
131 // for now, we only support a subset of encodings
132 switch (platformID) {
133 case 0: // Unicode
134 encoding = NSUTF16StringEncoding;
135 break;
136 case 1: // Macintosh
137 switch (platformSpecificID) {
138 case 0:
139 encoding = NSMacOSRomanStringEncoding;
140 break;
141 }
142 case 3: // Microsoft
143 switch (platformSpecificID) {
144 case 1:
145 encoding = NSUTF16StringEncoding;
146 break;
147 }
148 }
149 if (encoding == 0) continue;
150 nameLength = OSReadBigInt16(nameRecords, recordOffset + 8);
151 nameOffset = OSReadBigInt16(nameRecords, recordOffset + 10);
152 break;
153 }
154 NSString *result = nil;
155 if (nameOffset > 0) {
156 const UInt8 *nameBytes = &bytes[stringOffset + nameOffset];
157 result = [[NSString alloc] initWithBytes:nameBytes length:nameLength encoding:encoding];
158 }
159 CFRelease(nameTable);
160 return result;
161}
162
163- (void)dealloc {
164 CGFontRelease(_cgFont);
165 [_familyName release];
166 [_fontName release];
167 [_postScriptName release];
168 [super dealloc];
169}
170@end