/
BitmapFont.m
295 lines (231 loc) · 11.7 KB
/
BitmapFont.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
//
// BitMapFont.m
#import "BitmapFont.h"
#import "Image.h"
#pragma mark -
#pragma mark Private interface
@interface BitmapFont (Private)
// Takes the control file and parses the information within it
- (void)parseFont:(NSString*)controlFile;
// Parses the common configuration line within the control file. This contains information for
// the common height of the font which is used when calculating the position of the font characters
// on the screen.
- (void)parseCommon:(NSString*)line;
// Parses the individual character definitions which are identified by the parseFont method.
// This method also assiciates an image with each character
- (void)parseCharacterDefinition:(NSString*)line;
@end
#pragma mark -
#pragma mark Public implementation
@implementation BitmapFont
@synthesize image;
@synthesize fontColor;
- (void)dealloc {
// Free the objects which are being held within the charsArray
if (charsArray) {
for(int i=0; i < kMaxCharsInFont; i++) {
if (charsArray[i].image)
[charsArray[i].image release];
}
free(charsArray);
}
if (image)
[image release];
[super dealloc];
}
- (id)initWithFontImageNamed:(NSString*)aFileName controlFile:(NSString*)aControlFile scale:(Scale2f)aScale filter:(GLenum)aFilter {
self = [self init];
if (self != nil) {
// Reference the font image which has been supplied and which contains the character bitmaps
image = [[Image alloc] initWithImageNamed:aFileName filter:aFilter];
image.scale = aScale;
// Set up the initial color
fontColor = Color4fMake(1.0f, 1.0f, 1.0f, 1.0f);
// Initialize the array which is going to hold the bitmapfontchar structures
charsArray = calloc(kMaxCharsInFont, sizeof(BitmapFontChar));
// Parse the control file and populate charsArray which the character definitions
[self parseFont:aControlFile];
}
return self;
}
- (id)initWithImage:(Image*)aImage controlFile:(NSString*)aControlFile scale:(Scale2f)aScale filter:(GLenum)aFilter {
self = [self init];
if (self != nil) {
// Reference the font image which has been supplied and which contains the character bitmaps
self.image = aImage;
self.image.scale = aScale;
// Set up the initial color
fontColor = Color4fMake(1.0f, 1.0f, 1.0f, 1.0f);
// Initialize the array which is going to hold the bitmapfontchar structures
charsArray = calloc(kMaxCharsInFont, sizeof(BitmapFontChar));
// Parse the control file and populate charsArray which the character definitions
[self parseFont:aControlFile];
}
return self;
}
- (void)renderStringAt:(CGPoint)aPoint text:(NSString*)aText {
// Grab the scale that we will be using
float xScale = image.scale.x;
float yScale = image.scale.y;
// Loop through all the characters in the text to be rendered
for(int i=0; i<[aText length]; i++) {
// Grab the character value of the current character. We take off 32 as the first
// 32 characters of the fonts are not used
unichar charID = [aText characterAtIndex:i] - 32;
// Using the current x and y, calculate the correct position of the character using the x and y offsets for each character.
// This will cause the characters to all sit on the line correctly with tails below the line. The commonHeight which has
// been taken from the fonts control file is used within the calculation.
int y = aPoint.y + (lineHeight * yScale) - (charsArray[charID].height + charsArray[charID].yOffset) * yScale;
int x = aPoint.x + charsArray[charID].xOffset;
CGPoint renderPoint = CGPointMake(x, y);
// Set the color of this character based on the fontColor
charsArray[charID].image.color = fontColor;
// Render the current character at the renderPoint
[charsArray[charID].image renderAtPoint:renderPoint];
// Move x based on the amount to advance for the current char
aPoint.x += charsArray[charID].xAdvance * xScale;
}
}
- (void)renderStringJustifiedInFrame:(CGRect)aRect justification:(int)aJustification text:(NSString*)aText {
CGPoint point = CGPointMake(0.0, 0.0);
// Calculate the width and height in pixels of the text
int textWidth = [self getWidthForString:aText];
int textHeight = [self getHeightForString:aText];
// Based on the justification enum calculate the position of the text
switch (aJustification) {
case BitmapFontJustification_TopLeft:
point.x = aRect.origin.x;
point.y = aRect.origin.y + (aRect.size.height - textHeight) - (lineHeight - textHeight);
break;
case BitmapFontJustification_MiddleLeft:
point.x = aRect.origin.x;
point.y = aRect.origin.y + ((aRect.size.height - textHeight) / 2) - (lineHeight - textHeight);
break;
case BitmapFontJustification_BottomLeft:
point.x = aRect.origin.x;
point.y = aRect.origin.y - (lineHeight - textHeight);
break;
case BitmapFontJustification_TopCentered:
point.x = aRect.origin.x + ((aRect.size.width - textWidth) / 2);
point.y = aRect.origin.y + (aRect.size.height - textHeight) - (lineHeight - textHeight);
break;
case BitmapFontJustification_MiddleCentered:
point.x = aRect.origin.x + ((aRect.size.width - textWidth) / 2);
point.y = aRect.origin.y + ((aRect.size.height - textHeight) / 2) - (lineHeight - textHeight);
break;
case BitmapFontJustification_BottomCentered:
point.x = aRect.origin.x + ((aRect.size.width - textWidth) / 2);
point.y = aRect.origin.y - (lineHeight - textHeight);
break;
case BitmapFontJustification_TopRight:
point.x = aRect.origin.x + (aRect.size.width - textWidth);
point.y = aRect.origin.y + (aRect.size.height - textHeight) - (lineHeight - textHeight);
break;
case BitmapFontJustification_MiddleRight:
point.x = aRect.origin.x + (aRect.size.width - textWidth);
point.y = aRect.origin.y + ((aRect.size.height - textHeight) / 2) - (lineHeight - textHeight);
break;
case BitmapFontJustification_BottomRight:
point.x = aRect.origin.x + (aRect.size.width - textWidth);
point.y = aRect.origin.y - (lineHeight - textHeight);
break;
default:
break;
}
// Now we have calcualted the point to render the text, use the standard render method to actually render the text
// at the point calculated
[self renderStringAt:point text:aText];
}
- (int)getWidthForString:(NSString*)string {
// Set up stringWidth
int stringWidth = 0;
// Loop through the characters in the text and sum the xAdvance for each one
// xAdvance holds how far x should be advanced for each character so that the correct
// space is left after each character.
for(int index=0; index<[string length]; index++) {
unichar charID = [string characterAtIndex:index] - 32;
// Add the xAdvance value of the current character to stringWidth scaling as necessary
stringWidth += charsArray[charID].xAdvance * image.scale.x;
}
// Return the total width calculated
return stringWidth;
}
- (int)getHeightForString:(NSString*)string {
// Set up stringHeight
int stringHeight = 0;
// Loop through the characters in the text and sum the height. The sum will take into
// account the offset of the character as some characters sit below the line etc
for(int i=0; i<[string length]; i++) {
unichar charID = [string characterAtIndex:i] - 32;
// Don't bother checking if the character is a space as they have no height
if(charID == ' ')
continue;
// Check to see if the height of the current character is greater than the current max height
// If so then replace the current stringHeight with the height of the current character
stringHeight = MAX((charsArray[charID].height * image.scale.y) + (charsArray[charID].yOffset * image.scale.y), stringHeight);
}
// Return the total height calculated
return stringHeight;
}
@end
#pragma mark -
#pragma mark Private implementation
@implementation BitmapFont (Private)
- (void)parseFont:(NSString*)controlFile {
// Read the contents of the file into a string
NSString *contents = [NSString stringWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:controlFile ofType:@"fnt"] encoding:NSASCIIStringEncoding error:nil];
// Move all lines in the string, which are denoted by \n, into an array
NSArray *lines = [[NSArray alloc] initWithArray:[contents componentsSeparatedByString:@"\n"]];
// Create an enumerator which we can use to move through the lines read from the control file
NSEnumerator *nse = [lines objectEnumerator];
// Create a holder for each line we are going to work with
NSString *line;
// Loop through all the lines in the lines array processing each one
while((line = [nse nextObject])) {
// Check to see if the start of the line is something we are interested in
if([line hasPrefix:@"common"]) {
[self parseCommon:line];
} else if([line hasPrefix:@"char id"]) {
// Parse the current line and create a new CharDef
[self parseCharacterDefinition:line];
}
}
// Finished with lines so release it
[lines release];
}
- (void)parseCommon:(NSString*)line {
// Set up the variables that are going to hold the information for the common line
int scaleW;
int scaleH;
int pages;
// Grab information from the common line
sscanf([line UTF8String], "common lineHeight=%i base=%*i scaleW=%i scaleH=%i pages=%i", &lineHeight, &scaleW, &scaleH, &pages);
// scaleW is the width of the texture atlas for this font.
NSAssert(scaleW <= 1024, @"ERROR - BitmapFont: Texture atlas cannot be larger than 1024x1024");
// scaleH is the height of the texture atlas for this font.
NSAssert(scaleH <= 1024, @"ERROR - BitmapFont: Texture atlas cannot be larger than 1024x1024");
// pages are the number of different texture atlas files being used for this font
NSAssert(pages == 1, @"ERROR - BitmapFont: Only supports fonts with a single texture atlas.");
}
- (void)parseCharacterDefinition:(NSString*)line {
// Set up the variable to store the character ID for the character being processed
int charID;
// Grab the character ID from line
sscanf([line UTF8String], "char id=%i", &charID);
// Take 32 from the charID as we are not using the first 32 characters of the font
charID -= 32;
// Grab the remaining data from line that defines the character information
sscanf([line UTF8String], "char id=%*i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i",
&charsArray[charID].x, &charsArray[charID].y, &charsArray[charID].width,
&charsArray[charID].height, &charsArray[charID].xOffset, &charsArray[charID].yOffset, &charsArray[charID].xAdvance);
// Populate image with a new image instance defined as the sub image region for this
// character.
charsArray[charID].image = [[image subImageInRect:CGRectMake(charsArray[charID].x,
charsArray[charID].y,
charsArray[charID].width,
charsArray[charID].height)] retain];
// Set the scale of this characters image to match the scale of the texture atlas for this font
charsArray[charID].image.scale = image.scale;
}
@end