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.font.effects.Effect;
25 import org.newdawn.slick.opengl.Texture;
26 import org.newdawn.slick.opengl.TextureImpl;
27 import org.newdawn.slick.opengl.renderer.Renderer;
28 import org.newdawn.slick.opengl.renderer.SGL;
29 import org.newdawn.slick.util.ResourceLoader;
30
31 import javax.annotation.Nonnull;
32 import javax.annotation.Nullable;
33
34
35
36
37
38
39
40
41 public class UnicodeFont implements org.newdawn.slick.Font {
42
43 private static final int DISPLAY_LIST_CACHE_SIZE = 200;
44
45 static private final int MAX_GLYPH_CODE = 0x10FFFF;
46
47 private static final int PAGE_SIZE = 512;
48
49 private static final int PAGES = MAX_GLYPH_CODE / PAGE_SIZE;
50
51 private static final SGL GL = Renderer.get();
52
53 private static final DisplayList EMPTY_DISPLAY_LIST = new DisplayList();
54
55
56
57
58
59
60
61
62
63 private static Font createFont (String ttfFileRef) throws SlickException {
64 try {
65 return Font.createFont(Font.TRUETYPE_FONT, ResourceLoader.getResourceAsStream(ttfFileRef));
66 } catch (FontFormatException ex) {
67 throw new SlickException("Invalid font: " + ttfFileRef, ex);
68 } catch (IOException ex) {
69 throw new SlickException("Error reading font: " + ttfFileRef, ex);
70 }
71 }
72
73
74
75
76 private static final Comparator<Glyph> heightComparator = (o1, o2) -> o1.getHeight() - o2.getHeight();
77
78
79 private Font font;
80
81 private String ttfFileRef;
82
83 private int ascent;
84
85 private int descent;
86
87 private int leading;
88
89 private int spaceWidth;
90
91 private final Glyph[][] glyphs = new Glyph[PAGES][];
92
93 private final List<GlyphPage> glyphPages = new ArrayList<>();
94
95 private final List<Glyph> queuedGlyphs = new ArrayList<>(256);
96
97 private final List<Effect> effects = new ArrayList<>();
98
99
100 private int paddingTop;
101
102 private int paddingLeft;
103
104 private int paddingBottom;
105
106 private int paddingRight;
107
108 private int paddingAdvanceX;
109
110 private int paddingAdvanceY;
111
112 @Nullable
113 private Glyph missingGlyph;
114
115
116 private int glyphPageWidth = 512;
117
118 private int glyphPageHeight = 512;
119
120
121 private boolean displayListCaching = true;
122
123 private int baseDisplayListID = -1;
124
125 private int eldestDisplayListID;
126
127 @Nullable
128 private final Map<CharSequence, DisplayList> displayLists = new LinkedHashMap<CharSequence, DisplayList>(DISPLAY_LIST_CACHE_SIZE, 1, true) {
129
130
131
132 private static final long serialVersionUID = 1L;
133
134 protected boolean removeEldestEntry (@Nonnull Entry<CharSequence, DisplayList> eldest) {
135 DisplayList displayList = eldest.getValue();
136 if (displayList != null) eldestDisplayListID = displayList.id;
137 return size() > DISPLAY_LIST_CACHE_SIZE;
138 }
139 };
140
141
142
143
144
145
146
147
148 public UnicodeFont (String ttfFileRef, String hieroFileRef) throws SlickException {
149 this(ttfFileRef, new HieroSettings(hieroFileRef));
150 }
151
152
153
154
155
156
157
158
159 private UnicodeFont(String ttfFileRef, @Nonnull HieroSettings settings) throws SlickException {
160 this.ttfFileRef = ttfFileRef;
161 Font font = createFont(ttfFileRef);
162 initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());
163 loadSettings(settings);
164 }
165
166
167
168
169
170
171
172
173
174
175 public UnicodeFont (String ttfFileRef, int size, boolean bold, boolean italic) throws SlickException {
176 this.ttfFileRef = ttfFileRef;
177 initializeFont(createFont(ttfFileRef), size, bold, italic);
178 }
179
180
181
182
183
184
185
186
187 public UnicodeFont (@Nonnull Font font, String hieroFileRef) throws SlickException {
188 this(font, new HieroSettings(hieroFileRef));
189 }
190
191
192
193
194
195
196
197 private UnicodeFont(@Nonnull Font font, @Nonnull HieroSettings settings) {
198 initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());
199 loadSettings(settings);
200 }
201
202
203
204
205
206
207 public UnicodeFont (@Nonnull Font font) {
208 initializeFont(font, font.getSize(), font.isBold(), font.isItalic());
209 }
210
211
212
213
214
215
216
217
218
219 public UnicodeFont (@Nonnull Font font, int size, boolean bold, boolean italic) {
220 initializeFont(font, size, bold, italic);
221 }
222
223
224
225
226
227
228
229
230
231 @SuppressWarnings({"unchecked","rawtypes"})
232 private void initializeFont(@Nonnull Font baseFont, int size, boolean bold, boolean italic) {
233 Map attributes = baseFont.getAttributes();
234 attributes.put(TextAttribute.SIZE, (float) size);
235 attributes.put(TextAttribute.WEIGHT, bold ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);
236 attributes.put(TextAttribute.POSTURE, italic ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);
237 try {
238 attributes.put(TextAttribute.class.getDeclaredField("KERNING").get(null), TextAttribute.class.getDeclaredField(
239 "KERNING_ON").get(null));
240 } catch (Exception ignored) {
241 }
242 font = baseFont.deriveFont(attributes);
243
244 FontMetrics metrics = GlyphPage.getScratchGraphics().getFontMetrics(font);
245
246 ascent = metrics.getAscent();
247 descent = metrics.getDescent();
248 leading = metrics.getLeading();
249
250
251 char[] chars = " ".toCharArray();
252 GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
253 spaceWidth = vector.getGlyphLogicalBounds(0).getBounds().width;
254 }
255
256
257
258
259
260
261 private void loadSettings(@Nonnull HieroSettings settings) {
262 paddingTop = settings.getPaddingTop();
263 paddingLeft = settings.getPaddingLeft();
264 paddingBottom = settings.getPaddingBottom();
265 paddingRight = settings.getPaddingRight();
266 paddingAdvanceX = settings.getPaddingAdvanceX();
267 paddingAdvanceY = settings.getPaddingAdvanceY();
268 glyphPageWidth = settings.getGlyphPageWidth();
269 glyphPageHeight = settings.getGlyphPageHeight();
270 effects.addAll(settings.getEffects());
271 }
272
273
274
275
276
277
278
279
280
281
282
283 void addGlyphs(int startCodePoint, int endCodePoint) {
284 for (int codePoint = startCodePoint; codePoint <= endCodePoint; codePoint++)
285 addGlyphs(new String(Character.toChars(codePoint)));
286 }
287
288
289
290
291
292
293
294 void addGlyphs(@Nullable String text) {
295 if (text == null) throw new IllegalArgumentException("text cannot be null.");
296
297 char[] chars = text.toCharArray();
298 GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
299 for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
300 int codePoint = text.codePointAt(vector.getGlyphCharIndex(i));
301 Rectangle bounds = getGlyphBounds(vector, i, codePoint);
302 getGlyph(vector.getGlyphCode(i), codePoint, bounds, vector, i);
303 }
304 }
305
306
307
308
309
310 public void addAsciiGlyphs () {
311 addGlyphs(32, 127);
312 }
313
314
315
316
317
318
319
320 @Deprecated
321 public void addNeheGlyphs () {
322 addGlyphs(32, 32 + 96);
323 }
324
325
326
327
328
329
330
331
332
333 public boolean loadGlyphs () {
334 return loadGlyphs(-1);
335 }
336
337
338
339
340
341
342
343
344
345 boolean loadGlyphs(int maxGlyphsToLoad) {
346 if (queuedGlyphs.isEmpty()) return false;
347
348 if (effects.isEmpty())
349 throw new IllegalStateException("The UnicodeFont must have at least one effect before any glyphs can be loaded.");
350
351 for (Iterator<Glyph> iter = queuedGlyphs.iterator(); iter.hasNext();) {
352 Glyph glyph = iter.next();
353 int codePoint = glyph.getCodePoint();
354
355
356 if (glyph.getWidth() == 0 || codePoint == ' ') {
357 iter.remove();
358 continue;
359 }
360
361
362 if (glyph.isMissing()) {
363 if (missingGlyph != null) {
364 if (glyph != missingGlyph) iter.remove();
365 continue;
366 }
367 missingGlyph = glyph;
368 }
369 }
370
371 Collections.sort(queuedGlyphs, heightComparator);
372
373
374 for (GlyphPage glyphPage : glyphPages) {
375 maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);
376 if (maxGlyphsToLoad == 0 || queuedGlyphs.isEmpty())
377 return true;
378 }
379
380
381 while (!queuedGlyphs.isEmpty()) {
382 GlyphPage glyphPage = new GlyphPage(this, glyphPageWidth, glyphPageHeight);
383 glyphPages.add(glyphPage);
384 maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);
385 if (maxGlyphsToLoad == 0) return true;
386 }
387 System.out.println("glyphs loaded");
388 return true;
389 }
390
391
392
393
394 void clearGlyphs() {
395 for (int i = 0; i < PAGES; i++)
396 glyphs[i] = null;
397
398 for (GlyphPage page : glyphPages) {
399 page.getImage().destroy();
400 }
401 glyphPages.clear();
402
403 if (baseDisplayListID != -1) {
404 GL.glDeleteLists(baseDisplayListID, displayLists.size());
405 baseDisplayListID = -1;
406 }
407
408 queuedGlyphs.clear();
409 missingGlyph = null;
410 }
411
412
413
414
415
416 public void destroy () {
417
418 clearGlyphs();
419 }
420
421
422
423
424
425
426
427
428
429
430
431
432
433 @Nullable
434 DisplayList drawDisplayList(float x, float y, @Nullable CharSequence text, @Nullable Color color, int startIndex, int endIndex) {
435 if (text == null) throw new IllegalArgumentException("text cannot be null.");
436 if (text.length() == 0) return EMPTY_DISPLAY_LIST;
437 if (color == null) throw new IllegalArgumentException("color cannot be null.");
438
439 x -= paddingLeft;
440 y -= paddingTop;
441
442 CharSequence displayListKey = text.subSequence(startIndex, endIndex);
443
444 color.bind();
445 TextureImpl.bindNone();
446
447 DisplayList displayList = null;
448 if (displayListCaching && queuedGlyphs.isEmpty()) {
449 if (baseDisplayListID == -1) {
450 baseDisplayListID = GL.glGenLists(DISPLAY_LIST_CACHE_SIZE);
451 if (baseDisplayListID == 0) {
452 baseDisplayListID = -1;
453 displayListCaching = false;
454 return new DisplayList();
455 }
456 }
457
458 displayList = displayLists.get(displayListKey);
459 if (displayList != null) {
460 if (displayList.invalid)
461 displayList.invalid = false;
462 else {
463 GL.glTranslatef(x, y, 0);
464 GL.glCallList(displayList.id);
465 GL.glTranslatef(-x, -y, 0);
466 return displayList;
467 }
468 } else if (displayList == null) {
469
470 displayList = new DisplayList();
471 int displayListCount = displayLists.size();
472 displayLists.put(displayListKey, displayList);
473 if (displayListCount < DISPLAY_LIST_CACHE_SIZE)
474 displayList.id = baseDisplayListID + displayListCount;
475 else
476 displayList.id = eldestDisplayListID;
477 }
478 displayLists.put(displayListKey, displayList);
479 }
480
481 GL.glTranslatef(x, y, 0);
482
483 if (displayList != null) GL.glNewList(displayList.id, SGL.GL_COMPILE_AND_EXECUTE);
484
485 char[] chars = toCharArray(text, 0, endIndex);
486 GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
487
488 int maxWidth = 0, totalHeight = 0, lines = 0;
489 int extraX = 0, extraY = ascent;
490 boolean startNewLine = false;
491 Texture lastBind = null;
492 for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
493 int charIndex = vector.getGlyphCharIndex(glyphIndex);
494 if (charIndex < startIndex) continue;
495 if (charIndex > endIndex) break;
496
497 int codePoint = Character.codePointAt(text, charIndex);
498
499 Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);
500 Glyph glyph = getGlyph(vector.getGlyphCode(glyphIndex), codePoint, bounds, vector, glyphIndex);
501
502 if (startNewLine && codePoint != '\n') {
503 extraX = -bounds.x;
504 startNewLine = false;
505 }
506
507 Image image = glyph.getImage();
508 if (image == null && missingGlyph != null && glyph.isMissing()) image = missingGlyph.getImage();
509 if (image != null) {
510
511 Texture texture = image.getTexture();
512 if (lastBind != null && lastBind != texture) {
513 GL.glEnd();
514 lastBind = null;
515 }
516 if (lastBind == null) {
517 texture.bind();
518 GL.glBegin(SGL.GL_QUADS);
519 lastBind = texture;
520 }
521 image.drawEmbedded(bounds.x + extraX, bounds.y + extraY, image.getWidth(), image.getHeight());
522 }
523
524 if (glyphIndex >= 0) extraX += paddingRight + paddingLeft + paddingAdvanceX;
525 maxWidth = Math.max(maxWidth, bounds.x + extraX + bounds.width);
526 totalHeight = Math.max(totalHeight, ascent + bounds.y + bounds.height);
527
528 if (codePoint == '\n') {
529 startNewLine = true;
530 extraY += getLineHeight();
531 lines++;
532 totalHeight = 0;
533 }
534 }
535 if (lastBind != null) GL.glEnd();
536
537 if (displayList != null) {
538 GL.glEndList();
539
540 if (!queuedGlyphs.isEmpty()) displayList.invalid = true;
541 }
542
543 GL.glTranslatef(-x, -y, 0);
544
545 if (displayList == null) displayList = new DisplayList();
546 displayList.width = (short)maxWidth;
547 displayList.height = (short)(lines * getLineHeight() + totalHeight);
548 return displayList;
549 }
550
551 public void drawString (float x, float y, CharSequence text, Color color, int startIndex, int endIndex) {
552 drawDisplayList(x, y, text, color, startIndex, endIndex);
553 }
554
555 public void drawString (float x, float y, @Nonnull CharSequence text) {
556 drawString(x, y, text, Color.white);
557 }
558
559 public void drawString (float x, float y, @Nonnull CharSequence text, Color col) {
560 drawString(x, y, text, col, 0, text.length());
561 }
562
563
564
565
566
567
568
569
570
571
572
573
574 private Glyph getGlyph (int glyphCode, int codePoint, @Nonnull Rectangle bounds, @Nonnull GlyphVector vector, int index) {
575 if (glyphCode < 0 || glyphCode >= MAX_GLYPH_CODE) {
576
577 return new Glyph(codePoint, bounds, vector, index, this) {
578 public boolean isMissing () {
579 return true;
580 }
581 };
582 }
583 int pageIndex = glyphCode / PAGE_SIZE;
584 int glyphIndex = glyphCode & (PAGE_SIZE - 1);
585 Glyph glyph;
586 Glyph[] page = glyphs[pageIndex];
587 if (page != null) {
588 glyph = page[glyphIndex];
589 if (glyph != null) return glyph;
590 } else
591 page = glyphs[pageIndex] = new Glyph[PAGE_SIZE];
592
593 glyph = page[glyphIndex] = new Glyph(codePoint, bounds, vector, index, this);
594 queuedGlyphs.add(glyph);
595 return glyph;
596 }
597
598
599
600
601
602
603
604
605 private Rectangle getGlyphBounds (@Nonnull GlyphVector vector, int index, int codePoint) {
606 Rectangle bounds = vector.getGlyphPixelBounds(index, GlyphPage.renderContext, 0, 0);
607 if (codePoint == ' ') bounds.width = spaceWidth;
608 return bounds;
609 }
610
611
612
613
614 public int getSpaceWidth () {
615 return spaceWidth;
616 }
617
618
619
620
621 public int getWidth (@Nullable CharSequence text) {
622 if (text == null) throw new IllegalArgumentException("text cannot be null.");
623 if (text.length() == 0) return 0;
624
625 if (displayListCaching) {
626 DisplayList displayList = displayLists.get(text);
627 if (displayList != null) return displayList.width;
628 }
629
630 char[] chars = toCharArray(text, 0, text.length());
631 GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
632
633 int width = 0;
634 int extraX = 0;
635 boolean startNewLine = false;
636 for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
637 int charIndex = vector.getGlyphCharIndex(glyphIndex);
638 int codePoint = Character.codePointAt(text, charIndex);
639 Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);
640
641 if (startNewLine && codePoint != '\n') extraX = -bounds.x;
642
643 if (glyphIndex > 0) extraX += paddingLeft + paddingRight + paddingAdvanceX;
644 width = Math.max(width, bounds.x + extraX + bounds.width);
645
646 if (codePoint == '\n') startNewLine = true;
647 }
648
649 return width;
650 }
651
652 private static int indexOf(@Nonnull CharSequence cs, char chr) {
653 for (int i=0; i<cs.length(); i++) {
654 if (cs.charAt(i)==chr)
655 return i;
656 }
657 return -1;
658 }
659
660
661 @Nonnull
662 private static char[] toCharArray(@Nonnull CharSequence cs, int startIndex, int endIndex) {
663 if (startIndex==0 && endIndex==cs.length() && cs instanceof String)
664 return ((String)cs).toCharArray();
665 int s = endIndex-startIndex;
666 char[] ar = new char[s];
667 for (int x=0, i=startIndex; i<s; x++, i++)
668 ar[x] = cs.charAt(i);
669 return ar;
670 }
671
672
673
674
675
676 public int getHeight (@Nullable CharSequence text) {
677 if (text == null) throw new IllegalArgumentException("text cannot be null.");
678 if (text.length() == 0) return 0;
679
680 if (displayListCaching) {
681 DisplayList displayList = displayLists.get(text);
682 if (displayList != null) return displayList.height;
683 }
684
685 char[] chars = toCharArray(text, 0, text.length());
686 GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
687
688 int lines = 0, height = 0;
689 for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
690 int charIndex = vector.getGlyphCharIndex(i);
691 int codePoint = Character.codePointAt(text, charIndex);
692 if (codePoint == ' ') continue;
693 Rectangle bounds = getGlyphBounds(vector, i, codePoint);
694
695 height = Math.max(height, ascent + bounds.y + bounds.height);
696
697 if (codePoint == '\n') {
698 lines++;
699 height = 0;
700 }
701 }
702 return lines * getLineHeight() + height;
703 }
704
705
706
707
708
709
710
711
712 public int getYOffset (@Nullable CharSequence text) {
713 if (text == null) throw new IllegalArgumentException("text cannot be null.");
714
715 DisplayList displayList = null;
716 if (displayListCaching) {
717 displayList = displayLists.get(text);
718 if (displayList != null && displayList.yOffset != null) return displayList.yOffset.intValue();
719 }
720
721
722
723 int end = indexOf(text, '\n');
724 if (end==-1)
725 end = text.length();
726 char[] chars = toCharArray(text, 0, end);
727 GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
728 int yOffset = ascent + vector.getPixelBounds(null, 0, 0).y;
729
730 if (displayList != null) displayList.yOffset = (short) yOffset;
731
732 return yOffset;
733 }
734
735
736
737
738
739
740 public Font getFont() {
741 return font;
742 }
743
744
745
746
747
748
749 public int getPaddingTop() {
750 return paddingTop;
751 }
752
753
754
755
756
757
758 public void setPaddingTop(int paddingTop) {
759 this.paddingTop = paddingTop;
760 }
761
762
763
764
765
766
767 public int getPaddingLeft() {
768 return paddingLeft;
769 }
770
771
772
773
774
775
776 public void setPaddingLeft(int paddingLeft) {
777 this.paddingLeft = paddingLeft;
778 }
779
780
781
782
783
784
785 public int getPaddingBottom() {
786 return paddingBottom;
787 }
788
789
790
791
792
793
794 public void setPaddingBottom(int paddingBottom) {
795 this.paddingBottom = paddingBottom;
796 }
797
798
799
800
801
802
803 public int getPaddingRight () {
804 return paddingRight;
805 }
806
807
808
809
810
811
812 public void setPaddingRight (int paddingRight) {
813 this.paddingRight = paddingRight;
814 }
815
816
817
818
819
820
821 public int getPaddingAdvanceX() {
822 return paddingAdvanceX;
823 }
824
825
826
827
828
829
830
831 public void setPaddingAdvanceX (int paddingAdvanceX) {
832 this.paddingAdvanceX = paddingAdvanceX;
833 }
834
835
836
837
838
839
840 public int getPaddingAdvanceY () {
841 return paddingAdvanceY;
842 }
843
844
845
846
847
848
849
850 public void setPaddingAdvanceY (int paddingAdvanceY) {
851 this.paddingAdvanceY = paddingAdvanceY;
852 }
853
854
855
856
857
858 public int getLineHeight() {
859 return descent + ascent + leading + paddingTop + paddingBottom + paddingAdvanceY;
860 }
861
862
863
864
865
866
867 public int getAscent() {
868 return ascent;
869 }
870
871
872
873
874
875
876
877 public int getDescent () {
878 return descent;
879 }
880
881
882
883
884
885
886 public int getLeading () {
887 return leading;
888 }
889
890
891
892
893
894
895 public int getGlyphPageWidth () {
896 return glyphPageWidth;
897 }
898
899
900
901
902
903
904 public void setGlyphPageWidth(int glyphPageWidth) {
905 this.glyphPageWidth = glyphPageWidth;
906 }
907
908
909
910
911
912
913 public int getGlyphPageHeight() {
914 return glyphPageHeight;
915 }
916
917
918
919
920
921
922 public void setGlyphPageHeight(int glyphPageHeight) {
923 this.glyphPageHeight = glyphPageHeight;
924 }
925
926
927
928
929
930
931 @Nonnull
932 public List<GlyphPage> getGlyphPages () {
933 return glyphPages;
934 }
935
936
937
938
939
940
941
942 @Nonnull
943 public List<Effect> getEffects () {
944 return effects;
945 }
946
947
948
949
950
951
952
953 public boolean isCaching () {
954 return displayListCaching;
955 }
956
957
958
959
960
961
962
963 public void setDisplayListCaching (boolean displayListCaching) {
964 this.displayListCaching = displayListCaching;
965 }
966
967
968
969
970
971
972
973 @Nullable
974 public String getFontFile () {
975 if (ttfFileRef == null || ttfFileRef.length()==0) {
976
977 try {
978 Object font2D = Class.forName("sun.font.FontManager").getDeclaredMethod("getFont2D", new Class[] {Font.class})
979 .invoke(null, font);
980 Field platNameField = Class.forName("sun.font.PhysicalFont").getDeclaredField("platName");
981 platNameField.setAccessible(true);
982 ttfFileRef = (String)platNameField.get(font2D);
983 } catch (Throwable ignored) {
984
985 }
986 if (ttfFileRef == null) ttfFileRef = "";
987 }
988 if (ttfFileRef.length() == 0) return null;
989 return ttfFileRef;
990 }
991
992
993
994
995 public static class DisplayList {
996
997 boolean invalid;
998
999 int id;
1000
1001 Short yOffset;
1002
1003
1004 public short width;
1005
1006 public short height;
1007
1008 public Object userData;
1009
1010 DisplayList () {
1011 }
1012 }
1013 }