Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epoxy
GitHub Repository: epoxy/proj11
Path: blob/master/SLICK_HOME/src/org/newdawn/slick/AngelCodeFont.java
1456 views
1
package org.newdawn.slick;
2
3
import java.io.BufferedReader;
4
import java.io.IOException;
5
import java.io.InputStream;
6
import java.io.InputStreamReader;
7
import java.util.ArrayList;
8
import java.util.HashMap;
9
import java.util.Iterator;
10
import java.util.LinkedHashMap;
11
import java.util.List;
12
import java.util.Map;
13
import java.util.StringTokenizer;
14
import java.util.Map.Entry;
15
16
import org.newdawn.slick.opengl.renderer.Renderer;
17
import org.newdawn.slick.opengl.renderer.SGL;
18
import org.newdawn.slick.util.Log;
19
import org.newdawn.slick.util.ResourceLoader;
20
21
/**
22
* A font implementation that will parse BMFont format font files. The font files can be output
23
* by Hiero, which is included with Slick, and also the AngelCode font tool available at:
24
*
25
* <a
26
* href="http://www.angelcode.com/products/bmfont/">http://www.angelcode.com/products/bmfont/</a>
27
*
28
* This implementation copes with both the font display and kerning information
29
* allowing nicer looking paragraphs of text. Note that this utility only
30
* supports the text BMFont format definition file.
31
*
32
* @author kevin
33
* @author Nathan Sweet <[email protected]>
34
*/
35
public class AngelCodeFont implements Font {
36
/** The renderer to use for all GL operations */
37
private static SGL GL = Renderer.get();
38
39
/**
40
* The line cache size, this is how many lines we can render before starting
41
* to regenerate lists
42
*/
43
private static final int DISPLAY_LIST_CACHE_SIZE = 200;
44
45
/** The highest character that AngelCodeFont will support. */
46
private static final int MAX_CHAR = 255;
47
48
/** True if this font should use display list caching */
49
private boolean displayListCaching = true;
50
51
/** The image containing the bitmap font */
52
private Image fontImage;
53
/** The characters building up the font */
54
private CharDef[] chars;
55
/** The height of a line */
56
private int lineHeight;
57
/** The first display list ID */
58
private int baseDisplayListID = -1;
59
/** The eldest display list ID */
60
private int eldestDisplayListID;
61
/** The eldest display list */
62
private DisplayList eldestDisplayList;
63
64
/** The display list cache for rendered lines */
65
private final LinkedHashMap displayLists = new LinkedHashMap(DISPLAY_LIST_CACHE_SIZE, 1, true) {
66
protected boolean removeEldestEntry(Entry eldest) {
67
eldestDisplayList = (DisplayList)eldest.getValue();
68
eldestDisplayListID = eldestDisplayList.id;
69
70
return false;
71
}
72
};
73
74
75
/**
76
* Create a new font based on a font definition from AngelCode's tool and
77
* the font image generated from the tool.
78
*
79
* @param fntFile
80
* The location of the font defnition file
81
* @param image
82
* The image to use for the font
83
* @throws SlickException
84
* Indicates a failure to load either file
85
*/
86
public AngelCodeFont(String fntFile, Image image) throws SlickException {
87
fontImage = image;
88
89
parseFnt(ResourceLoader.getResourceAsStream(fntFile));
90
}
91
92
/**
93
* Create a new font based on a font definition from AngelCode's tool and
94
* the font image generated from the tool.
95
*
96
* @param fntFile
97
* The location of the font defnition file
98
* @param imgFile
99
* The location of the font image
100
* @throws SlickException
101
* Indicates a failure to load either file
102
*/
103
public AngelCodeFont(String fntFile, String imgFile) throws SlickException {
104
fontImage = new Image(imgFile);
105
106
parseFnt(ResourceLoader.getResourceAsStream(fntFile));
107
}
108
109
/**
110
* Create a new font based on a font definition from AngelCode's tool and
111
* the font image generated from the tool.
112
*
113
* @param fntFile
114
* The location of the font defnition file
115
* @param image
116
* The image to use for the font
117
* @param caching
118
* True if this font should use display list caching
119
* @throws SlickException
120
* Indicates a failure to load either file
121
*/
122
public AngelCodeFont(String fntFile, Image image, boolean caching)
123
throws SlickException {
124
fontImage = image;
125
displayListCaching = caching;
126
parseFnt(ResourceLoader.getResourceAsStream(fntFile));
127
}
128
129
/**
130
* Create a new font based on a font definition from AngelCode's tool and
131
* the font image generated from the tool.
132
*
133
* @param fntFile
134
* The location of the font defnition file
135
* @param imgFile
136
* The location of the font image
137
* @param caching
138
* True if this font should use display list caching
139
* @throws SlickException
140
* Indicates a failure to load either file
141
*/
142
public AngelCodeFont(String fntFile, String imgFile, boolean caching)
143
throws SlickException {
144
fontImage = new Image(imgFile);
145
displayListCaching = caching;
146
parseFnt(ResourceLoader.getResourceAsStream(fntFile));
147
}
148
149
/**
150
* Create a new font based on a font definition from AngelCode's tool and
151
* the font image generated from the tool.
152
*
153
* @param name
154
* The name to assign to the font image in the image store
155
* @param fntFile
156
* The stream of the font defnition file
157
* @param imgFile
158
* The stream of the font image
159
* @throws SlickException
160
* Indicates a failure to load either file
161
*/
162
public AngelCodeFont(String name, InputStream fntFile, InputStream imgFile)
163
throws SlickException {
164
fontImage = new Image(imgFile, name, false);
165
166
parseFnt(fntFile);
167
}
168
169
/**
170
* Create a new font based on a font definition from AngelCode's tool and
171
* the font image generated from the tool.
172
*
173
* @param name
174
* The name to assign to the font image in the image store
175
* @param fntFile
176
* The stream of the font defnition file
177
* @param imgFile
178
* The stream of the font image
179
* @param caching
180
* True if this font should use display list caching
181
* @throws SlickException
182
* Indicates a failure to load either file
183
*/
184
public AngelCodeFont(String name, InputStream fntFile, InputStream imgFile,
185
boolean caching) throws SlickException {
186
fontImage = new Image(imgFile, name, false);
187
188
displayListCaching = caching;
189
parseFnt(fntFile);
190
}
191
192
/**
193
* Parse the font definition file
194
*
195
* @param fntFile
196
* The stream from which the font file can be read
197
* @throws SlickException
198
*/
199
private void parseFnt(InputStream fntFile) throws SlickException {
200
if (displayListCaching) {
201
baseDisplayListID = GL.glGenLists(DISPLAY_LIST_CACHE_SIZE);
202
if (baseDisplayListID == 0) displayListCaching = false;
203
}
204
205
try {
206
// now parse the font file
207
BufferedReader in = new BufferedReader(new InputStreamReader(
208
fntFile));
209
String info = in.readLine();
210
String common = in.readLine();
211
String page = in.readLine();
212
213
Map kerning = new HashMap(64);
214
List charDefs = new ArrayList(MAX_CHAR);
215
int maxChar = 0;
216
boolean done = false;
217
while (!done) {
218
String line = in.readLine();
219
if (line == null) {
220
done = true;
221
} else {
222
if (line.startsWith("chars c")) {
223
// ignore
224
} else if (line.startsWith("char")) {
225
CharDef def = parseChar(line);
226
if (def != null) {
227
maxChar = Math.max(maxChar, def.id);
228
charDefs.add(def);
229
}
230
}
231
if (line.startsWith("kernings c")) {
232
// ignore
233
} else if (line.startsWith("kerning")) {
234
StringTokenizer tokens = new StringTokenizer(line, " =");
235
tokens.nextToken(); // kerning
236
tokens.nextToken(); // first
237
short first = Short.parseShort(tokens.nextToken()); // first value
238
tokens.nextToken(); // second
239
int second = Integer.parseInt(tokens.nextToken()); // second value
240
tokens.nextToken(); // offset
241
int offset = Integer.parseInt(tokens.nextToken()); // offset value
242
List values = (List)kerning.get(new Short(first));
243
if (values == null) {
244
values = new ArrayList();
245
kerning.put(new Short(first), values);
246
}
247
// Pack the character and kerning offset into a short.
248
values.add(new Short((short)((offset << 8) | second)));
249
}
250
}
251
}
252
253
chars = new CharDef[maxChar + 1];
254
for (Iterator iter = charDefs.iterator(); iter.hasNext();) {
255
CharDef def = (CharDef)iter.next();
256
chars[def.id] = def;
257
}
258
259
// Turn each list of kerning values into a short[] and set on the chardef.
260
for (Iterator iter = kerning.entrySet().iterator(); iter.hasNext(); ) {
261
Entry entry = (Entry)iter.next();
262
short first = ((Short)entry.getKey()).shortValue();
263
List valueList = (List)entry.getValue();
264
short[] valueArray = new short[valueList.size()];
265
int i = 0;
266
for (Iterator valueIter = valueList.iterator(); valueIter.hasNext(); i++)
267
valueArray[i] = ((Short)valueIter.next()).shortValue();
268
chars[first].kerning = valueArray;
269
}
270
} catch (IOException e) {
271
Log.error(e);
272
throw new SlickException("Failed to parse font file: " + fntFile);
273
}
274
}
275
276
/**
277
* Parse a single character line from the definition
278
*
279
* @param line
280
* The line to be parsed
281
* @return The character definition from the line
282
* @throws SlickException Indicates a given character is not valid in an angel code font
283
*/
284
private CharDef parseChar(String line) throws SlickException {
285
CharDef def = new CharDef();
286
StringTokenizer tokens = new StringTokenizer(line, " =");
287
288
tokens.nextToken(); // char
289
tokens.nextToken(); // id
290
def.id = Short.parseShort(tokens.nextToken()); // id value
291
if (def.id < 0) {
292
return null;
293
}
294
if (def.id > MAX_CHAR) {
295
throw new SlickException("Invalid character '" + def.id
296
+ "': AngelCodeFont does not support characters above " + MAX_CHAR);
297
}
298
299
tokens.nextToken(); // x
300
def.x = Short.parseShort(tokens.nextToken()); // x value
301
tokens.nextToken(); // y
302
def.y = Short.parseShort(tokens.nextToken()); // y value
303
tokens.nextToken(); // width
304
def.width = Short.parseShort(tokens.nextToken()); // width value
305
tokens.nextToken(); // height
306
def.height = Short.parseShort(tokens.nextToken()); // height value
307
tokens.nextToken(); // x offset
308
def.xoffset = Short.parseShort(tokens.nextToken()); // xoffset value
309
tokens.nextToken(); // y offset
310
def.yoffset = Short.parseShort(tokens.nextToken()); // yoffset value
311
tokens.nextToken(); // xadvance
312
def.xadvance = Short.parseShort(tokens.nextToken()); // xadvance
313
314
def.init();
315
316
if (def.id != ' ') {
317
lineHeight = Math.max(def.height + def.yoffset, lineHeight);
318
}
319
320
return def;
321
}
322
323
/**
324
* @see org.newdawn.slick.Font#drawString(float, float, java.lang.String)
325
*/
326
public void drawString(float x, float y, String text) {
327
drawString(x, y, text, Color.white);
328
}
329
330
/**
331
* @see org.newdawn.slick.Font#drawString(float, float, java.lang.String,
332
* org.newdawn.slick.Color)
333
*/
334
public void drawString(float x, float y, String text, Color col) {
335
drawString(x, y, text, col, 0, text.length() - 1);
336
}
337
338
/**
339
* @see Font#drawString(float, float, String, Color, int, int)
340
*/
341
public void drawString(float x, float y, String text, Color col,
342
int startIndex, int endIndex) {
343
fontImage.bind();
344
col.bind();
345
346
GL.glTranslatef(x, y, 0);
347
if (displayListCaching && startIndex == 0 && endIndex == text.length() - 1) {
348
DisplayList displayList = (DisplayList)displayLists.get(text);
349
if (displayList != null) {
350
GL.glCallList(displayList.id);
351
} else {
352
// Compile a new display list.
353
displayList = new DisplayList();
354
displayList.text = text;
355
int displayListCount = displayLists.size();
356
if (displayListCount < DISPLAY_LIST_CACHE_SIZE) {
357
displayList.id = baseDisplayListID + displayListCount;
358
} else {
359
displayList.id = eldestDisplayListID;
360
displayLists.remove(eldestDisplayList.text);
361
}
362
363
displayLists.put(text, displayList);
364
365
GL.glNewList(displayList.id, SGL.GL_COMPILE_AND_EXECUTE);
366
render(text, startIndex, endIndex);
367
GL.glEndList();
368
}
369
} else {
370
render(text, startIndex, endIndex);
371
}
372
GL.glTranslatef(-x, -y, 0);
373
}
374
375
/**
376
* Render based on immediate rendering
377
*
378
* @param text The text to be rendered
379
* @param start The index of the first character in the string to render
380
* @param end The index of the last character in the string to render
381
*/
382
private void render(String text, int start, int end) {
383
GL.glBegin(SGL.GL_QUADS);
384
385
int x = 0, y = 0;
386
CharDef lastCharDef = null;
387
char[] data = text.toCharArray();
388
for (int i = 0; i < data.length; i++) {
389
int id = data[i];
390
if (id == '\n') {
391
x = 0;
392
y += getLineHeight();
393
continue;
394
}
395
if (id >= chars.length) {
396
continue;
397
}
398
CharDef charDef = chars[id];
399
if (charDef == null) {
400
continue;
401
}
402
403
if (lastCharDef != null) x += lastCharDef.getKerning(id);
404
lastCharDef = charDef;
405
406
if ((i >= start) && (i <= end)) {
407
charDef.draw(x, y);
408
}
409
410
x += charDef.xadvance;
411
}
412
GL.glEnd();
413
}
414
415
/**
416
* Returns the distance from the y drawing location to the top most pixel of the specified text.
417
*
418
* @param text
419
* The text that is to be tested
420
* @return The yoffset from the y draw location at which text will start
421
*/
422
public int getYOffset(String text) {
423
DisplayList displayList = null;
424
if (displayListCaching) {
425
displayList = (DisplayList)displayLists.get(text);
426
if (displayList != null && displayList.yOffset != null) return displayList.yOffset.intValue();
427
}
428
429
int stopIndex = text.indexOf('\n');
430
if (stopIndex == -1) stopIndex = text.length();
431
432
int minYOffset = 10000;
433
for (int i = 0; i < stopIndex; i++) {
434
int id = text.charAt(i);
435
CharDef charDef = chars[id];
436
if (charDef == null) {
437
continue;
438
}
439
minYOffset = Math.min(charDef.yoffset, minYOffset);
440
}
441
442
if (displayList != null) displayList.yOffset = new Short((short)minYOffset);
443
444
return minYOffset;
445
}
446
447
/**
448
* @see org.newdawn.slick.Font#getHeight(java.lang.String)
449
*/
450
public int getHeight(String text) {
451
DisplayList displayList = null;
452
if (displayListCaching) {
453
displayList = (DisplayList)displayLists.get(text);
454
if (displayList != null && displayList.height != null) return displayList.height.intValue();
455
}
456
457
int lines = 0;
458
int maxHeight = 0;
459
for (int i = 0; i < text.length(); i++) {
460
int id = text.charAt(i);
461
if (id == '\n') {
462
lines++;
463
maxHeight = 0;
464
continue;
465
}
466
// ignore space, it doesn't contribute to height
467
if (id == ' ') {
468
continue;
469
}
470
CharDef charDef = chars[id];
471
if (charDef == null) {
472
continue;
473
}
474
475
maxHeight = Math.max(charDef.height + charDef.yoffset,
476
maxHeight);
477
}
478
479
maxHeight += lines * getLineHeight();
480
481
if (displayList != null) displayList.height = new Short((short)maxHeight);
482
483
return maxHeight;
484
}
485
486
/**
487
* @see org.newdawn.slick.Font#getWidth(java.lang.String)
488
*/
489
public int getWidth(String text) {
490
DisplayList displayList = null;
491
if (displayListCaching) {
492
displayList = (DisplayList)displayLists.get(text);
493
if (displayList != null && displayList.width != null) return displayList.width.intValue();
494
}
495
496
int maxWidth = 0;
497
int width = 0;
498
CharDef lastCharDef = null;
499
for (int i = 0, n = text.length(); i < n; i++) {
500
int id = text.charAt(i);
501
if (id == '\n') {
502
width = 0;
503
continue;
504
}
505
if (id >= chars.length) {
506
continue;
507
}
508
CharDef charDef = chars[id];
509
if (charDef == null) {
510
continue;
511
}
512
513
if (lastCharDef != null) width += lastCharDef.getKerning(id);
514
lastCharDef = charDef;
515
516
if (i < n - 1) {
517
width += charDef.xadvance;
518
} else {
519
width += charDef.width;
520
}
521
maxWidth = Math.max(maxWidth, width);
522
}
523
524
if (displayList != null) displayList.width = new Short((short)maxWidth);
525
526
return maxWidth;
527
}
528
529
/**
530
* The definition of a single character as defined in the AngelCode file
531
* format
532
*
533
* @author kevin
534
*/
535
private class CharDef {
536
/** The id of the character */
537
public short id;
538
/** The x location on the sprite sheet */
539
public short x;
540
/** The y location on the sprite sheet */
541
public short y;
542
/** The width of the character image */
543
public short width;
544
/** The height of the character image */
545
public short height;
546
/** The amount the x position should be offset when drawing the image */
547
public short xoffset;
548
/** The amount the y position should be offset when drawing the image */
549
public short yoffset;
550
551
/** The amount to move the current position after drawing the character */
552
public short xadvance;
553
/** The image containing the character */
554
public Image image;
555
/** The display list index for this character */
556
public short dlIndex;
557
/** The kerning info for this character */
558
public short[] kerning;
559
560
/**
561
* Initialise the image by cutting the right section from the map
562
* produced by the AngelCode tool.
563
*/
564
public void init() {
565
image = fontImage.getSubImage(x, y, width, height);
566
}
567
568
/**
569
* @see java.lang.Object#toString()
570
*/
571
public String toString() {
572
return "[CharDef id=" + id + " x=" + x + " y=" + y + "]";
573
}
574
575
/**
576
* Draw this character embedded in a image draw
577
*
578
* @param x
579
* The x position at which to draw the text
580
* @param y
581
* The y position at which to draw the text
582
*/
583
public void draw(float x, float y) {
584
image.drawEmbedded(x + xoffset, y + yoffset, width, height);
585
}
586
587
/**
588
* Get the kerning offset between this character and the specified character.
589
* @param otherCodePoint The other code point
590
* @return the kerning offset
591
*/
592
public int getKerning (int otherCodePoint) {
593
if (kerning == null) return 0;
594
int low = 0;
595
int high = kerning.length - 1;
596
while (low <= high) {
597
int midIndex = (low + high) >>> 1;
598
int value = kerning[midIndex];
599
int foundCodePoint = value & 0xff;
600
if (foundCodePoint < otherCodePoint)
601
low = midIndex + 1;
602
else if (foundCodePoint > otherCodePoint)
603
high = midIndex - 1;
604
else
605
return value >> 8;
606
}
607
return 0;
608
}
609
}
610
611
/**
612
* @see org.newdawn.slick.Font#getLineHeight()
613
*/
614
public int getLineHeight() {
615
return lineHeight;
616
}
617
618
/**
619
* A descriptor for a single display list
620
*
621
* @author Nathan Sweet <[email protected]>
622
*/
623
static private class DisplayList {
624
/** The if of the distance list */
625
int id;
626
/** The offset of the line rendered */
627
Short yOffset;
628
/** The width of the line rendered */
629
Short width;
630
/** The height of the line rendered */
631
Short height;
632
/** The text that the display list holds */
633
String text;
634
}
635
}
636
637