Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epoxy
GitHub Repository: epoxy/proj11
Path: blob/master/SLICK_HOME/src/org/newdawn/slick/UnicodeFont.java
1456 views
1
2
package org.newdawn.slick;
3
4
import java.awt.Font;
5
import java.awt.FontFormatException;
6
import java.awt.FontMetrics;
7
import java.awt.Rectangle;
8
import java.awt.font.GlyphVector;
9
import java.awt.font.TextAttribute;
10
import java.io.IOException;
11
import java.lang.reflect.Field;
12
import java.util.ArrayList;
13
import java.util.Collections;
14
import java.util.Comparator;
15
import java.util.Iterator;
16
import java.util.LinkedHashMap;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Map.Entry;
20
21
import org.newdawn.slick.font.Glyph;
22
import org.newdawn.slick.font.GlyphPage;
23
import org.newdawn.slick.font.HieroSettings;
24
import org.newdawn.slick.opengl.Texture;
25
import org.newdawn.slick.opengl.TextureImpl;
26
import org.newdawn.slick.opengl.renderer.Renderer;
27
import org.newdawn.slick.opengl.renderer.SGL;
28
import org.newdawn.slick.util.ResourceLoader;
29
30
/**
31
* A Slick bitmap font that can display unicode glyphs from a TrueTypeFont.
32
*
33
* For efficiency, glyphs are packed on to textures. Glyphs can be loaded to the textures on the fly, when they are first needed
34
* for display. However, it is best to load the glyphs that are known to be needed at startup.
35
* @author Nathan Sweet <[email protected]>
36
*/
37
public class UnicodeFont implements org.newdawn.slick.Font {
38
/** The number of display lists that will be cached for strings from this font */
39
private static final int DISPLAY_LIST_CACHE_SIZE = 200;
40
/** The highest glyph code allowed */
41
static private final int MAX_GLYPH_CODE = 0x10FFFF;
42
/** The number of glyphs on a page */
43
private static final int PAGE_SIZE = 512;
44
/** The number of pages */
45
private static final int PAGES = MAX_GLYPH_CODE / PAGE_SIZE;
46
/** Interface to OpenGL */
47
private static final SGL GL = Renderer.get();
48
/** A dummy display list used as a place holder */
49
private static final DisplayList EMPTY_DISPLAY_LIST = new DisplayList();
50
51
/**
52
* Utility to create a Java font for a TTF file reference
53
*
54
* @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
55
* @return The font created
56
* @throws SlickException Indicates a failure to locate or load the font into Java's font
57
* system.
58
*/
59
private static Font createFont (String ttfFileRef) throws SlickException {
60
try {
61
return Font.createFont(Font.TRUETYPE_FONT, ResourceLoader.getResourceAsStream(ttfFileRef));
62
} catch (FontFormatException ex) {
63
throw new SlickException("Invalid font: " + ttfFileRef, ex);
64
} catch (IOException ex) {
65
throw new SlickException("Error reading font: " + ttfFileRef, ex);
66
}
67
}
68
69
/**
70
* Sorts glyphs by height, tallest first.
71
*/
72
private static final Comparator heightComparator = new Comparator() {
73
public int compare (Object o1, Object o2) {
74
return ((Glyph)o1).getHeight() - ((Glyph)o2).getHeight();
75
}
76
};
77
78
/** The AWT font that is being rendered */
79
private Font font;
80
/** The reference to the True Type Font file that has kerning information */
81
private String ttfFileRef;
82
/** The ascent of the font */
83
private int ascent;
84
/** The decent of the font */
85
private int descent;
86
/** The leading edge of the font */
87
private int leading;
88
/** The width of a space for the font */
89
private int spaceWidth;
90
/** The glyphs that are available in this font */
91
private final Glyph[][] glyphs = new Glyph[PAGES][];
92
/** The pages that have been loaded for this font */
93
private final List glyphPages = new ArrayList();
94
/** The glyphs queued up to be rendered */
95
private final List queuedGlyphs = new ArrayList(256);
96
/** The effects that need to be applied to the font */
97
private final List effects = new ArrayList();
98
99
/** The padding applied in pixels to the top of the glyph rendered area */
100
private int paddingTop;
101
/** The padding applied in pixels to the left of the glyph rendered area */
102
private int paddingLeft;
103
/** The padding applied in pixels to the bottom of the glyph rendered area */
104
private int paddingBottom;
105
/** The padding applied in pixels to the right of the glyph rendered area */
106
private int paddingRight;
107
/** The padding applied in pixels to horizontal advance for each glyph */
108
private int paddingAdvanceX;
109
/** The padding applied in pixels to vertical advance for each glyph */
110
private int paddingAdvanceY;
111
/** The glyph to display for missing glyphs in code points */
112
private Glyph missingGlyph;
113
114
/** The width of the glyph page generated */
115
private int glyphPageWidth = 512;
116
/** The height of the glyph page generated */
117
private int glyphPageHeight = 512;
118
119
/** True if display list caching is turned on */
120
private boolean displayListCaching = true;
121
/** The based display list ID */
122
private int baseDisplayListID = -1;
123
/** The ID of the display list that has been around the longest time */
124
private int eldestDisplayListID;
125
/** The eldest display list */
126
private DisplayList eldestDisplayList;
127
128
/** The map fo the display list generated and cached - modified to allow removal of the oldest entry */
129
private final LinkedHashMap displayLists = new LinkedHashMap(DISPLAY_LIST_CACHE_SIZE, 1, true) {
130
protected boolean removeEldestEntry (Entry eldest) {
131
DisplayList displayList = (DisplayList)eldest.getValue();
132
if (displayList != null) eldestDisplayListID = displayList.id;
133
return size() > DISPLAY_LIST_CACHE_SIZE;
134
}
135
};
136
137
/**
138
* Create a new unicode font based on a TTF file
139
*
140
* @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
141
* @param hieroFileRef The file system or classpath location of the Hiero settings file.
142
* @throws SlickException if the UnicodeFont could not be initialized.
143
*/
144
public UnicodeFont (String ttfFileRef, String hieroFileRef) throws SlickException {
145
this(ttfFileRef, new HieroSettings(hieroFileRef));
146
}
147
148
/**
149
* Create a new unicode font based on a TTF file and a set of heiro configuration
150
*
151
* @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
152
* @param settings The settings configured via the Hiero tool
153
* @throws SlickException if the UnicodeFont could not be initialized.
154
*/
155
public UnicodeFont (String ttfFileRef, HieroSettings settings) throws SlickException {
156
this.ttfFileRef = ttfFileRef;
157
Font font = createFont(ttfFileRef);
158
initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());
159
loadSettings(settings);
160
}
161
162
/**
163
* Create a new unicode font based on a TTF file alone
164
*
165
* @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
166
* @param size The point size of the font to generated
167
* @param bold True if the font should be rendered in bold typeface
168
* @param italic True if the font should be rendered in bold typeface
169
* @throws SlickException if the UnicodeFont could not be initialized.
170
*/
171
public UnicodeFont (String ttfFileRef, int size, boolean bold, boolean italic) throws SlickException {
172
this.ttfFileRef = ttfFileRef;
173
initializeFont(createFont(ttfFileRef), size, bold, italic);
174
}
175
176
/**
177
* Creates a new UnicodeFont.
178
*
179
* @param font The AWT font to render
180
* @param hieroFileRef The file system or classpath location of the Hiero settings file.
181
* @throws SlickException if the UnicodeFont could not be initialized.
182
*/
183
public UnicodeFont (Font font, String hieroFileRef) throws SlickException {
184
this(font, new HieroSettings(hieroFileRef));
185
}
186
187
/**
188
* Creates a new UnicodeFont.
189
*
190
* @param font The AWT font to render
191
* @param settings The settings configured via the Hiero tool
192
*/
193
public UnicodeFont (Font font, HieroSettings settings) {
194
initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());
195
loadSettings(settings);
196
}
197
198
/**
199
* Creates a new UnicodeFont.
200
*
201
* @param font The AWT font to render
202
*/
203
public UnicodeFont (Font font) {
204
initializeFont(font, font.getSize(), font.isBold(), font.isItalic());
205
}
206
207
/**
208
* Creates a new UnicodeFont.
209
*
210
* @param font The AWT font to render
211
* @param size The point size of the font to generated
212
* @param bold True if the font should be rendered in bold typeface
213
* @param italic True if the font should be rendered in bold typeface
214
*/
215
public UnicodeFont (Font font, int size, boolean bold, boolean italic) {
216
initializeFont(font, size, bold, italic);
217
}
218
219
/**
220
* Initialise the font to be used based on configuration
221
*
222
* @param baseFont The AWT font to render
223
* @param size The point size of the font to generated
224
* @param bold True if the font should be rendered in bold typeface
225
* @param italic True if the font should be rendered in bold typeface
226
*/
227
private void initializeFont(Font baseFont, int size, boolean bold, boolean italic) {
228
Map attributes = baseFont.getAttributes();
229
attributes.put(TextAttribute.SIZE, new Float(size));
230
attributes.put(TextAttribute.WEIGHT, bold ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);
231
attributes.put(TextAttribute.POSTURE, italic ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);
232
try {
233
attributes.put(TextAttribute.class.getDeclaredField("KERNING").get(null), TextAttribute.class.getDeclaredField(
234
"KERNING_ON").get(null));
235
} catch (Exception ignored) {
236
}
237
font = baseFont.deriveFont(attributes);
238
239
FontMetrics metrics = GlyphPage.getScratchGraphics().getFontMetrics(font);
240
ascent = metrics.getAscent();
241
descent = metrics.getDescent();
242
leading = metrics.getLeading();
243
244
// Determine width of space glyph (getGlyphPixelBounds gives a width of zero).
245
char[] chars = " ".toCharArray();
246
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
247
spaceWidth = vector.getGlyphLogicalBounds(0).getBounds().width;
248
}
249
250
/**
251
* Load the hiero setting and configure the unicode font's rendering
252
*
253
* @param settings The settings to be applied
254
*/
255
private void loadSettings(HieroSettings settings) {
256
paddingTop = settings.getPaddingTop();
257
paddingLeft = settings.getPaddingLeft();
258
paddingBottom = settings.getPaddingBottom();
259
paddingRight = settings.getPaddingRight();
260
paddingAdvanceX = settings.getPaddingAdvanceX();
261
paddingAdvanceY = settings.getPaddingAdvanceY();
262
glyphPageWidth = settings.getGlyphPageWidth();
263
glyphPageHeight = settings.getGlyphPageHeight();
264
effects.addAll(settings.getEffects());
265
}
266
267
/**
268
* Queues the glyphs in the specified codepoint range (inclusive) to be loaded. Note that the glyphs are not actually loaded
269
* until {@link #loadGlyphs()} is called.
270
*
271
* Some characters like combining marks and non-spacing marks can only be rendered with the context of other glyphs. In this
272
* case, use {@link #addGlyphs(String)}.
273
*
274
* @param startCodePoint The code point of the first glyph to add
275
* @param endCodePoint The code point of the last glyph to add
276
*/
277
public void addGlyphs(int startCodePoint, int endCodePoint) {
278
for (int codePoint = startCodePoint; codePoint <= endCodePoint; codePoint++)
279
addGlyphs(new String(Character.toChars(codePoint)));
280
}
281
282
/**
283
* Queues the glyphs in the specified text to be loaded. Note that the glyphs are not actually loaded until
284
* {@link #loadGlyphs()} is called.
285
*
286
* @param text The text containing the glyphs to be added
287
*/
288
public void addGlyphs(String text) {
289
if (text == null) throw new IllegalArgumentException("text cannot be null.");
290
291
char[] chars = text.toCharArray();
292
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
293
for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
294
int codePoint = text.codePointAt(vector.getGlyphCharIndex(i));
295
Rectangle bounds = getGlyphBounds(vector, i, codePoint);
296
getGlyph(vector.getGlyphCode(i), codePoint, bounds, vector, i);
297
}
298
}
299
300
/**
301
* Queues the glyphs in the ASCII character set (codepoints 32 through 255) to be loaded. Note that the glyphs are not actually
302
* loaded until {@link #loadGlyphs()} is called.
303
*/
304
public void addAsciiGlyphs () {
305
addGlyphs(32, 255);
306
}
307
308
/**
309
* Queues the glyphs in the NEHE character set (codepoints 32 through 128) to be loaded. Note that the glyphs are not actually
310
* loaded until {@link #loadGlyphs()} is called.
311
*/
312
public void addNeheGlyphs () {
313
addGlyphs(32, 32 + 96);
314
}
315
316
/**
317
* Loads all queued glyphs to the backing textures. Glyphs that are typically displayed together should be added and loaded at
318
* the same time so that they are stored on the same backing texture. This reduces the number of backing texture binds required
319
* to draw glyphs.
320
*
321
* @return True if the glyphs were loaded entirely
322
* @throws SlickException if the glyphs could not be loaded.
323
*/
324
public boolean loadGlyphs () throws SlickException {
325
return loadGlyphs(-1);
326
}
327
328
/**
329
* Loads up to the specified number of queued glyphs to the backing textures. This is typically called from the game loop to
330
* load glyphs on the fly that were requested for display but have not yet been loaded.
331
*
332
* @param maxGlyphsToLoad The maximum number of glyphs to be loaded this time
333
* @return True if the glyphs were loaded entirely
334
* @throws SlickException if the glyphs could not be loaded.
335
*/
336
public boolean loadGlyphs (int maxGlyphsToLoad) throws SlickException {
337
if (queuedGlyphs.isEmpty()) return false;
338
339
if (effects.isEmpty())
340
throw new IllegalStateException("The UnicodeFont must have at least one effect before any glyphs can be loaded.");
341
342
for (Iterator iter = queuedGlyphs.iterator(); iter.hasNext();) {
343
Glyph glyph = (Glyph)iter.next();
344
int codePoint = glyph.getCodePoint();
345
346
// Don't load an image for a glyph with nothing to display.
347
if (glyph.getWidth() == 0 || codePoint == ' ') {
348
iter.remove();
349
continue;
350
}
351
352
// Only load the first missing glyph.
353
if (glyph.isMissing()) {
354
if (missingGlyph != null) {
355
if (glyph != missingGlyph) iter.remove();
356
continue;
357
}
358
missingGlyph = glyph;
359
}
360
}
361
362
Collections.sort(queuedGlyphs, heightComparator);
363
364
// Add to existing pages.
365
for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {
366
GlyphPage glyphPage = (GlyphPage)iter.next();
367
maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);
368
if (maxGlyphsToLoad == 0 || queuedGlyphs.isEmpty())
369
return true;
370
}
371
372
// Add to new pages.
373
while (!queuedGlyphs.isEmpty()) {
374
GlyphPage glyphPage = new GlyphPage(this, glyphPageWidth, glyphPageHeight);
375
glyphPages.add(glyphPage);
376
maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);
377
if (maxGlyphsToLoad == 0) return true;
378
}
379
380
return true;
381
}
382
383
/**
384
* Clears all loaded and queued glyphs.
385
*/
386
public void clearGlyphs () {
387
for (int i = 0; i < PAGES; i++)
388
glyphs[i] = null;
389
390
for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {
391
GlyphPage page = (GlyphPage)iter.next();
392
try {
393
page.getImage().destroy();
394
} catch (SlickException ignored) {
395
}
396
}
397
glyphPages.clear();
398
399
if (baseDisplayListID != -1) {
400
GL.glDeleteLists(baseDisplayListID, displayLists.size());
401
baseDisplayListID = -1;
402
}
403
404
queuedGlyphs.clear();
405
missingGlyph = null;
406
}
407
408
/**
409
* Releases all resources used by this UnicodeFont. This method should be called when this UnicodeFont instance is no longer
410
* needed.
411
*/
412
public void destroy () {
413
// The destroy() method is just to provide a consistent API for releasing resources.
414
clearGlyphs();
415
}
416
417
/**
418
* Identical to {@link #drawString(float, float, String, Color, int, int)} but returns a
419
* DisplayList which provides access to the width and height of the text drawn.
420
*
421
* @param text The text to render
422
* @param x The horizontal location to render at
423
* @param y The vertical location to render at
424
* @param color The colour to apply as a filter on the text
425
* @param startIndex The start index into the string to start rendering at
426
* @param endIndex The end index into the string to render to
427
* @return The reference to the display list that was drawn and potentiall ygenerated
428
*/
429
public DisplayList drawDisplayList (float x, float y, String text, Color color, int startIndex, int endIndex) {
430
if (text == null) throw new IllegalArgumentException("text cannot be null.");
431
if (text.length() == 0) return EMPTY_DISPLAY_LIST;
432
if (color == null) throw new IllegalArgumentException("color cannot be null.");
433
434
x -= paddingLeft;
435
y -= paddingTop;
436
437
String displayListKey = text.substring(startIndex, endIndex);
438
439
color.bind();
440
TextureImpl.bindNone();
441
442
DisplayList displayList = null;
443
if (displayListCaching && queuedGlyphs.isEmpty()) {
444
if (baseDisplayListID == -1) {
445
baseDisplayListID = GL.glGenLists(DISPLAY_LIST_CACHE_SIZE);
446
if (baseDisplayListID == 0) {
447
baseDisplayListID = -1;
448
displayListCaching = false;
449
return new DisplayList();
450
}
451
}
452
// Try to use a display list compiled for this text.
453
displayList = (DisplayList)displayLists.get(displayListKey);
454
if (displayList != null) {
455
if (displayList.invalid)
456
displayList.invalid = false;
457
else {
458
GL.glTranslatef(x, y, 0);
459
GL.glCallList(displayList.id);
460
GL.glTranslatef(-x, -y, 0);
461
return displayList;
462
}
463
} else if (displayList == null) {
464
// Compile a new display list.
465
displayList = new DisplayList();
466
int displayListCount = displayLists.size();
467
displayLists.put(displayListKey, displayList);
468
if (displayListCount < DISPLAY_LIST_CACHE_SIZE)
469
displayList.id = baseDisplayListID + displayListCount;
470
else
471
displayList.id = eldestDisplayListID;
472
}
473
displayLists.put(displayListKey, displayList);
474
}
475
476
GL.glTranslatef(x, y, 0);
477
478
if (displayList != null) GL.glNewList(displayList.id, SGL.GL_COMPILE_AND_EXECUTE);
479
480
char[] chars = text.substring(0, endIndex).toCharArray();
481
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
482
483
int maxWidth = 0, totalHeight = 0, lines = 0;
484
int extraX = 0, extraY = ascent;
485
boolean startNewLine = false;
486
Texture lastBind = null;
487
for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
488
int charIndex = vector.getGlyphCharIndex(glyphIndex);
489
if (charIndex < startIndex) continue;
490
if (charIndex > endIndex) break;
491
492
int codePoint = text.codePointAt(charIndex);
493
494
Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);
495
Glyph glyph = getGlyph(vector.getGlyphCode(glyphIndex), codePoint, bounds, vector, glyphIndex);
496
497
if (startNewLine && codePoint != '\n') {
498
extraX = -bounds.x;
499
startNewLine = false;
500
}
501
502
Image image = glyph.getImage();
503
if (image == null && missingGlyph != null && glyph.isMissing()) image = missingGlyph.getImage();
504
if (image != null) {
505
// Draw glyph, only binding a new glyph page texture when necessary.
506
Texture texture = image.getTexture();
507
if (lastBind != null && lastBind != texture) {
508
GL.glEnd();
509
lastBind = null;
510
}
511
if (lastBind == null) {
512
texture.bind();
513
GL.glBegin(SGL.GL_QUADS);
514
lastBind = texture;
515
}
516
image.drawEmbedded(bounds.x + extraX, bounds.y + extraY, image.getWidth(), image.getHeight());
517
}
518
519
if (glyphIndex > 0) extraX += paddingRight + paddingLeft + paddingAdvanceX;
520
maxWidth = Math.max(maxWidth, bounds.x + extraX + bounds.width);
521
totalHeight = Math.max(totalHeight, ascent + bounds.y + bounds.height);
522
523
if (codePoint == '\n') {
524
startNewLine = true; // Mac gives -1 for bounds.x of '\n', so use the bounds.x of the next glyph.
525
extraY += getLineHeight();
526
lines++;
527
totalHeight = 0;
528
}
529
}
530
if (lastBind != null) GL.glEnd();
531
532
if (displayList != null) {
533
GL.glEndList();
534
// Invalidate the display list if it had glyphs that need to be loaded.
535
if (!queuedGlyphs.isEmpty()) displayList.invalid = true;
536
}
537
538
GL.glTranslatef(-x, -y, 0);
539
540
if (displayList == null) displayList = new DisplayList();
541
displayList.width = (short)maxWidth;
542
displayList.height = (short)(lines * getLineHeight() + totalHeight);
543
return displayList;
544
}
545
546
public void drawString (float x, float y, String text, Color color, int startIndex, int endIndex) {
547
drawDisplayList(x, y, text, color, startIndex, endIndex);
548
}
549
550
public void drawString (float x, float y, String text) {
551
drawString(x, y, text, Color.white);
552
}
553
554
public void drawString (float x, float y, String text, Color col) {
555
drawString(x, y, text, col, 0, text.length());
556
}
557
558
/**
559
* Returns the glyph for the specified codePoint. If the glyph does not exist yet,
560
* it is created and queued to be loaded.
561
*
562
* @param glyphCode The code of the glyph to locate
563
* @param codePoint The code point associated with the glyph
564
* @param bounds The bounds of the glyph on the page
565
* @param vector The vector the glyph is part of
566
* @param index The index of the glyph within the vector
567
* @return The glyph requested
568
*/
569
private Glyph getGlyph (int glyphCode, int codePoint, Rectangle bounds, GlyphVector vector, int index) {
570
if (glyphCode < 0 || glyphCode >= MAX_GLYPH_CODE) {
571
// GlyphVector#getGlyphCode sometimes returns negative numbers on OS X.
572
return new Glyph(codePoint, bounds, vector, index, this) {
573
public boolean isMissing () {
574
return true;
575
}
576
};
577
}
578
int pageIndex = glyphCode / PAGE_SIZE;
579
int glyphIndex = glyphCode & (PAGE_SIZE - 1);
580
Glyph glyph = null;
581
Glyph[] page = glyphs[pageIndex];
582
if (page != null) {
583
glyph = page[glyphIndex];
584
if (glyph != null) return glyph;
585
} else
586
page = glyphs[pageIndex] = new Glyph[PAGE_SIZE];
587
// Add glyph so size information is available and queue it so its image can be loaded later.
588
glyph = page[glyphIndex] = new Glyph(codePoint, bounds, vector, index, this);
589
queuedGlyphs.add(glyph);
590
return glyph;
591
}
592
593
/**
594
* Returns the bounds of the specified glyph.\
595
*
596
* @param vector The vector the glyph is part of
597
* @param index The index of the glyph within the vector
598
* @param codePoint The code point associated with the glyph
599
*/
600
private Rectangle getGlyphBounds (GlyphVector vector, int index, int codePoint) {
601
Rectangle bounds = vector.getGlyphPixelBounds(index, GlyphPage.renderContext, 0, 0);
602
if (codePoint == ' ') bounds.width = spaceWidth;
603
return bounds;
604
}
605
606
/**
607
* Returns the width of the space character.
608
*/
609
public int getSpaceWidth () {
610
return spaceWidth;
611
}
612
613
/**
614
* @see org.newdawn.slick.Font#getWidth(java.lang.String)
615
*/
616
public int getWidth (String text) {
617
if (text == null) throw new IllegalArgumentException("text cannot be null.");
618
if (text.length() == 0) return 0;
619
620
if (displayListCaching) {
621
DisplayList displayList = (DisplayList)displayLists.get(text);
622
if (displayList != null) return displayList.width;
623
}
624
625
char[] chars = text.toCharArray();
626
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
627
628
int width = 0;
629
int extraX = 0;
630
boolean startNewLine = false;
631
for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
632
int charIndex = vector.getGlyphCharIndex(glyphIndex);
633
int codePoint = text.codePointAt(charIndex);
634
Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);
635
636
if (startNewLine && codePoint != '\n') extraX = -bounds.x;
637
638
if (glyphIndex > 0) extraX += paddingLeft + paddingRight + paddingAdvanceX;
639
width = Math.max(width, bounds.x + extraX + bounds.width);
640
641
if (codePoint == '\n') startNewLine = true;
642
}
643
644
return width;
645
}
646
647
/**
648
* @see org.newdawn.slick.Font#getHeight(java.lang.String)
649
*/
650
public int getHeight (String text) {
651
if (text == null) throw new IllegalArgumentException("text cannot be null.");
652
if (text.length() == 0) return 0;
653
654
if (displayListCaching) {
655
DisplayList displayList = (DisplayList)displayLists.get(text);
656
if (displayList != null) return displayList.height;
657
}
658
659
char[] chars = text.toCharArray();
660
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
661
662
int lines = 0, height = 0;
663
for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
664
int charIndex = vector.getGlyphCharIndex(i);
665
int codePoint = text.codePointAt(charIndex);
666
if (codePoint == ' ') continue;
667
Rectangle bounds = getGlyphBounds(vector, i, codePoint);
668
669
height = Math.max(height, ascent + bounds.y + bounds.height);
670
671
if (codePoint == '\n') {
672
lines++;
673
height = 0;
674
}
675
}
676
return lines * getLineHeight() + height;
677
}
678
679
/**
680
* Returns the distance from the y drawing location to the top most pixel of the
681
* specified text.
682
*
683
* @param text The text to analyse
684
* @return The distance fro the y drawing location ot the top most pixel of the specified text
685
*/
686
public int getYOffset (String text) {
687
if (text == null) throw new IllegalArgumentException("text cannot be null.");
688
689
DisplayList displayList = null;
690
if (displayListCaching) {
691
displayList = (DisplayList)displayLists.get(text);
692
if (displayList != null && displayList.yOffset != null) return displayList.yOffset.intValue();
693
}
694
695
int index = text.indexOf('\n');
696
if (index != -1) text = text.substring(0, index);
697
char[] chars = text.toCharArray();
698
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
699
int yOffset = ascent + vector.getPixelBounds(null, 0, 0).y;
700
701
if (displayList != null) displayList.yOffset = new Short((short)yOffset);
702
703
return yOffset;
704
}
705
706
/**
707
* Returns the TrueTypeFont for this UnicodeFont.
708
*
709
* @return The AWT Font being rendered
710
*/
711
public Font getFont() {
712
return font;
713
}
714
715
/**
716
* Returns the padding above a glyph on the GlyphPage to allow for effects to be drawn.
717
*
718
* @return The padding at the top of the glyphs when drawn
719
*/
720
public int getPaddingTop() {
721
return paddingTop;
722
}
723
724
/**
725
* Sets the padding above a glyph on the GlyphPage to allow for effects to be drawn.
726
*
727
* @param paddingTop The padding at the top of the glyphs when drawn
728
*/
729
public void setPaddingTop(int paddingTop) {
730
this.paddingTop = paddingTop;
731
}
732
733
/**
734
* Returns the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn.
735
*
736
* @return The padding at the left of the glyphs when drawn
737
*/
738
public int getPaddingLeft() {
739
return paddingLeft;
740
}
741
742
/**
743
* Sets the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn.
744
*
745
* @param paddingLeft The padding at the left of the glyphs when drawn
746
*/
747
public void setPaddingLeft(int paddingLeft) {
748
this.paddingLeft = paddingLeft;
749
}
750
751
/**
752
* Returns the padding below a glyph on the GlyphPage to allow for effects to be drawn.
753
*
754
* @return The padding at the bottom of the glyphs when drawn
755
*/
756
public int getPaddingBottom() {
757
return paddingBottom;
758
}
759
760
/**
761
* Sets the padding below a glyph on the GlyphPage to allow for effects to be drawn.
762
*
763
* @param paddingBottom The padding at the bottom of the glyphs when drawn
764
*/
765
public void setPaddingBottom(int paddingBottom) {
766
this.paddingBottom = paddingBottom;
767
}
768
769
/**
770
* Returns the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn.
771
*
772
* @return The padding at the right of the glyphs when drawn
773
*/
774
public int getPaddingRight () {
775
return paddingRight;
776
}
777
778
/**
779
* Sets the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn.
780
*
781
* @param paddingRight The padding at the right of the glyphs when drawn
782
*/
783
public void setPaddingRight (int paddingRight) {
784
this.paddingRight = paddingRight;
785
}
786
787
/**
788
* Gets the additional amount to offset glyphs on the x axis.
789
*
790
* @return The padding applied for each horizontal advance (i.e. when a glyph is rendered)
791
*/
792
public int getPaddingAdvanceX() {
793
return paddingAdvanceX;
794
}
795
796
/**
797
* Sets the additional amount to offset glyphs on the x axis. This is typically set to a negative number when left or right
798
* padding is used so that glyphs are not spaced too far apart.
799
*
800
* @param paddingAdvanceX The padding applied for each horizontal advance (i.e. when a glyph is rendered)
801
*/
802
public void setPaddingAdvanceX (int paddingAdvanceX) {
803
this.paddingAdvanceX = paddingAdvanceX;
804
}
805
806
/**
807
* Gets the additional amount to offset a line of text on the y axis.
808
*
809
* @return The padding applied for each vertical advance (i.e. when a glyph is rendered)
810
*/
811
public int getPaddingAdvanceY () {
812
return paddingAdvanceY;
813
}
814
815
/**
816
* Sets the additional amount to offset a line of text on the y axis. This is typically set to a negative number when top or
817
* bottom padding is used so that lines of text are not spaced too far apart.
818
*
819
* @param paddingAdvanceY The padding applied for each vertical advance (i.e. when a glyph is rendered)
820
*/
821
public void setPaddingAdvanceY (int paddingAdvanceY) {
822
this.paddingAdvanceY = paddingAdvanceY;
823
}
824
825
/**
826
* Returns the distance from one line of text to the next. This is the sum of the descent, ascent, leading, padding top,
827
* padding bottom, and padding advance y. To change the line height, use {@link #setPaddingAdvanceY(int)}.
828
*/
829
public int getLineHeight() {
830
return descent + ascent + leading + paddingTop + paddingBottom + paddingAdvanceY;
831
}
832
833
/**
834
* Gets the distance from the baseline to the y drawing location.
835
*
836
* @return The ascent of this font
837
*/
838
public int getAscent() {
839
return ascent;
840
}
841
842
/**
843
* Gets the distance from the baseline to the bottom of most alphanumeric characters
844
* with descenders.
845
*
846
* @return The distance from the baseline to the bottom of the font
847
*/
848
public int getDescent () {
849
return descent;
850
}
851
852
/**
853
* Gets the extra distance between the descent of one line of text to the ascent of the next.
854
*
855
* @return The leading edge of the font
856
*/
857
public int getLeading () {
858
return leading;
859
}
860
861
/**
862
* Returns the width of the backing textures.
863
*
864
* @return The width of the glyph pages in this font
865
*/
866
public int getGlyphPageWidth () {
867
return glyphPageWidth;
868
}
869
870
/**
871
* Sets the width of the backing textures. Default is 512.
872
*
873
* @param glyphPageWidth The width of the glyph pages in this font
874
*/
875
public void setGlyphPageWidth(int glyphPageWidth) {
876
this.glyphPageWidth = glyphPageWidth;
877
}
878
879
/**
880
* Returns the height of the backing textures.
881
*
882
* @return The height of the glyph pages in this font
883
*/
884
public int getGlyphPageHeight() {
885
return glyphPageHeight;
886
}
887
888
/**
889
* Sets the height of the backing textures. Default is 512.
890
*
891
* @param glyphPageHeight The width of the glyph pages in this font
892
*/
893
public void setGlyphPageHeight(int glyphPageHeight) {
894
this.glyphPageHeight = glyphPageHeight;
895
}
896
897
/**
898
* Returns the GlyphPages for this UnicodeFont.
899
*
900
* @return The glyph pages that have been loaded into this font
901
*/
902
public List getGlyphPages () {
903
return glyphPages;
904
}
905
906
/**
907
* Returns a list of {@link org.newdawn.slick.font.effects.Effect}s that will be applied
908
* to the glyphs.
909
*
910
* @return The list of effects to be applied to the font
911
*/
912
public List getEffects () {
913
return effects;
914
}
915
916
/**
917
* Returns true if this UnicodeFont caches the glyph drawing instructions to
918
* improve performance.
919
*
920
* @return True if caching is turned on
921
*/
922
public boolean isCaching () {
923
return displayListCaching;
924
}
925
926
/**
927
* Sets if this UnicodeFont caches the glyph drawing instructions to improve performance.
928
* Default is true. Text rendering is very slow without display list caching.
929
*
930
* @param displayListCaching True if caching should be turned on
931
*/
932
public void setDisplayListCaching (boolean displayListCaching) {
933
this.displayListCaching = displayListCaching;
934
}
935
936
/**
937
* Returns the path to the TTF file for this UnicodeFont, or null. If this UnicodeFont was created without specifying the TTF
938
* file, it will try to determine the path using Sun classes. If this fails, null is returned.
939
*
940
* @return The reference to the font file that the kerning was loaded from
941
*/
942
public String getFontFile () {
943
if (ttfFileRef == null) {
944
// Worst case if this UnicodeFont was loaded without a ttfFileRef, try to get the font file from Sun's classes.
945
try {
946
Object font2D = Class.forName("sun.font.FontManager").getDeclaredMethod("getFont2D", new Class[] {Font.class})
947
.invoke(null, new Object[] {font});
948
Field platNameField = Class.forName("sun.font.PhysicalFont").getDeclaredField("platName");
949
platNameField.setAccessible(true);
950
ttfFileRef = (String)platNameField.get(font2D);
951
} catch (Throwable ignored) {
952
}
953
if (ttfFileRef == null) ttfFileRef = "";
954
}
955
if (ttfFileRef.length() == 0) return null;
956
return ttfFileRef;
957
}
958
959
/**
960
* A simple descriptor for display lists cached within this font
961
*/
962
public static class DisplayList {
963
/** True if this display list has been invalidated */
964
boolean invalid;
965
/** The ID of the display list this descriptor represents */
966
int id;
967
/** The vertical offset to the top of this display list */
968
Short yOffset;
969
970
/** The width of rendered text in the list */
971
public short width;
972
/** The height of the rendered text in the list */
973
public short height;
974
/** Application data stored in the list */
975
public Object userData;
976
977
DisplayList () {
978
}
979
}
980
}
981
982