forked from openjdk/jdk
/
SunFontManager.java
3561 lines (3269 loc) · 148 KB
/
SunFontManager.java
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.font;
import java.awt.Font;
import java.awt.FontFormatException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.plaf.FontUIResource;
import sun.awt.FontConfiguration;
import sun.awt.SunToolkit;
import sun.awt.util.ThreadGroupUtils;
import sun.java2d.FontSupport;
import sun.util.logging.PlatformLogger;
/**
* The base implementation of the {@link FontManager} interface. It implements
* the platform independent, shared parts of OpenJDK's FontManager
* implementations. The platform specific parts are declared as abstract
* methods that have to be implemented by specific implementations.
*/
public abstract class SunFontManager implements FontSupport, FontManagerForSGE {
private static class TTFilter implements FilenameFilter {
public boolean accept(File dir,String name) {
/* all conveniently have the same suffix length */
int offset = name.length()-4;
if (offset <= 0) { /* must be at least A.ttf */
return false;
} else {
return(name.startsWith(".ttf", offset) ||
name.startsWith(".TTF", offset) ||
name.startsWith(".ttc", offset) ||
name.startsWith(".TTC", offset) ||
name.startsWith(".otf", offset) ||
name.startsWith(".OTF", offset));
}
}
}
private static class T1Filter implements FilenameFilter {
public boolean accept(File dir,String name) {
if (noType1Font) {
return false;
}
/* all conveniently have the same suffix length */
int offset = name.length()-4;
if (offset <= 0) { /* must be at least A.pfa */
return false;
} else {
return(name.startsWith(".pfa", offset) ||
name.startsWith(".pfb", offset) ||
name.startsWith(".PFA", offset) ||
name.startsWith(".PFB", offset));
}
}
}
private static class TTorT1Filter implements FilenameFilter {
public boolean accept(File dir, String name) {
/* all conveniently have the same suffix length */
int offset = name.length()-4;
if (offset <= 0) { /* must be at least A.ttf or A.pfa */
return false;
} else {
boolean isTT =
name.startsWith(".ttf", offset) ||
name.startsWith(".TTF", offset) ||
name.startsWith(".ttc", offset) ||
name.startsWith(".TTC", offset) ||
name.startsWith(".otf", offset) ||
name.startsWith(".OTF", offset);
if (isTT) {
return true;
} else if (noType1Font) {
return false;
} else {
return(name.startsWith(".pfa", offset) ||
name.startsWith(".pfb", offset) ||
name.startsWith(".PFA", offset) ||
name.startsWith(".PFB", offset));
}
}
}
}
private static Font2DHandle FONT_HANDLE_NULL = new Font2DHandle(null);
public static final int FONTFORMAT_NONE = -1;
public static final int FONTFORMAT_TRUETYPE = 0;
public static final int FONTFORMAT_TYPE1 = 1;
public static final int FONTFORMAT_TTC = 2;
public static final int FONTFORMAT_COMPOSITE = 3;
public static final int FONTFORMAT_NATIVE = 4;
/* Pool of 20 font file channels chosen because some UTF-8 locale
* composite fonts can use up to 16 platform fonts (including the
* Lucida fall back). This should prevent channel thrashing when
* dealing with one of these fonts.
* The pool array stores the fonts, rather than directly referencing
* the channels, as the font needs to do the open/close work.
*/
// MACOSX begin -- need to access these in subclass
protected static final int CHANNELPOOLSIZE = 20;
protected FileFont[] fontFileCache = new FileFont[CHANNELPOOLSIZE];
// MACOSX end
private int lastPoolIndex = 0;
/* Need to implement a simple linked list scheme for fast
* traversal and lookup.
* Also want to "fast path" dialog so there's minimal overhead.
*/
/* There are at exactly 20 composite fonts: 5 faces (but some are not
* usually different), in 4 styles. The array may be auto-expanded
* later if more are needed, eg for user-defined composites or locale
* variants.
*/
private int maxCompFont = 0;
private CompositeFont [] compFonts = new CompositeFont[20];
private ConcurrentHashMap<String, CompositeFont>
compositeFonts = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, PhysicalFont>
physicalFonts = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, PhysicalFont>
registeredFonts = new ConcurrentHashMap<>();
/* given a full name find the Font. Remind: there's duplication
* here in that this contains the content of compositeFonts +
* physicalFonts.
*/
// MACOSX begin -- need to access this in subclass
protected ConcurrentHashMap<String, Font2D>
fullNameToFont = new ConcurrentHashMap<>();
// MACOSX end
/* TrueType fonts have localised names. Support searching all
* of these before giving up on a name.
*/
private HashMap<String, TrueTypeFont> localeFullNamesToFont;
private PhysicalFont defaultPhysicalFont;
static boolean longAddresses;
private boolean loaded1dot0Fonts = false;
boolean loadedAllFonts = false;
boolean loadedAllFontFiles = false;
String[] jreOtherFontFiles;
boolean noOtherJREFontFiles = false; // initial assumption.
public static String jreLibDirName;
public static String jreFontDirName;
private static HashSet<String> missingFontFiles = null;
private String defaultFontName;
private String defaultFontFileName;
protected HashSet<String> registeredFontFiles = new HashSet<>();
private ArrayList<String> badFonts;
/* fontPath is the location of all fonts on the system, excluding the
* JRE's own font directory but including any path specified using the
* sun.java2d.fontpath property. Together with that property, it is
* initialised by the getPlatformFontPath() method
* This call must be followed by a call to registerFontDirs(fontPath)
* once any extra debugging path has been appended.
*/
protected String fontPath;
private FontConfiguration fontConfig;
/* discoveredAllFonts is set to true when all fonts on the font path are
* discovered. This usually also implies opening, validating and
* registering, but an implementation may be optimized to avold this.
* So see also "loadedAllFontFiles"
*/
private boolean discoveredAllFonts = false;
/* No need to keep consing up new instances - reuse a singleton.
* The trade-off is that these objects don't get GC'd.
*/
private static final FilenameFilter ttFilter = new TTFilter();
private static final FilenameFilter t1Filter = new T1Filter();
private Font[] allFonts;
private String[] allFamilies; // cache for default locale only
private Locale lastDefaultLocale;
public static boolean noType1Font;
/* Used to indicate required return type from toArray(..); */
private static String[] STR_ARRAY = new String[0];
/**
* Deprecated, unsupported hack - actually invokes a bug!
* Left in for a customer, don't remove.
*/
private boolean usePlatformFontMetrics = false;
/**
* Returns the global SunFontManager instance. This is similar to
* {@link FontManagerFactory#getInstance()} but it returns a
* SunFontManager instance instead. This is only used in internal classes
* where we can safely assume that a SunFontManager is to be used.
*
* @return the global SunFontManager instance
*/
public static SunFontManager getInstance() {
FontManager fm = FontManagerFactory.getInstance();
return (SunFontManager) fm;
}
public FilenameFilter getTrueTypeFilter() {
return ttFilter;
}
public FilenameFilter getType1Filter() {
return t1Filter;
}
/* After we reach MAXSOFTREFCNT, use weak refs for created fonts.
* This means that a small number of created fonts as used in a UI app
* will not be eagerly collected, but an app that create many will
* have them collected more frequently to reclaim storage.
*/
private static int maxSoftRefCnt = 10;
static {
initStatic();
}
@SuppressWarnings("removal")
private static void initStatic() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
FontManagerNativeLibrary.load();
// JNI throws an exception if a class/method/field is not found,
// so there's no need to do anything explicit here.
initIDs();
switch (StrikeCache.nativeAddressSize) {
case 8: longAddresses = true; break;
case 4: longAddresses = false; break;
default: throw new RuntimeException("Unexpected address size");
}
noType1Font = "true".equals(System.getProperty("sun.java2d.noType1Font"));
jreLibDirName = System.getProperty("java.home","") + File.separator + "lib";
jreFontDirName = jreLibDirName + File.separator + "fonts";
maxSoftRefCnt = Integer.getInteger("sun.java2d.font.maxSoftRefs", 10);
return null;
}
});
}
/**
* If the module image layout changes the location of JDK fonts,
* this will be updated to reflect that.
*/
public static final String getJDKFontDir() {
return jreFontDirName;
}
public TrueTypeFont getEUDCFont() {
// Overridden in Windows.
return null;
}
/* Initialise ptrs used by JNI methods */
private static native void initIDs();
@SuppressWarnings("removal")
protected SunFontManager() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
File badFontFile =
new File(jreFontDirName + File.separator + "badfonts.txt");
if (badFontFile.exists()) {
badFonts = new ArrayList<>();
try (FileInputStream fis = new FileInputStream(badFontFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
while (true) {
String name = br.readLine();
if (name == null) {
break;
} else {
if (FontUtilities.debugFonts()) {
FontUtilities.logWarning("read bad font: " + name);
}
badFonts.add(name);
}
}
} catch (IOException e) {
}
}
/* Here we get the fonts in jre/lib/fonts and register
* them so they are always available and preferred over
* other fonts. This needs to be registered before the
* composite fonts as otherwise some native font that
* corresponds may be found as we don't have a way to
* handle two fonts of the same name, so the JRE one
* must be the first one registered. Pass "true" to
* registerFonts method as on-screen these JRE fonts
* always go through the JDK rasteriser.
*/
if (FontUtilities.isLinux) {
/* Linux font configuration uses these fonts */
registerFontDir(jreFontDirName);
}
registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK,
true, false);
/* Create the font configuration and get any font path
* that might be specified.
*/
fontConfig = createFontConfiguration();
String[] fontInfo = getDefaultPlatformFont();
defaultFontName = fontInfo[0];
if (defaultFontName == null && FontUtilities.debugFonts()) {
FontUtilities.logWarning("defaultFontName is null");
}
defaultFontFileName = fontInfo[1];
String extraFontPath = fontConfig.getExtraFontPath();
/* In prior releases the debugging font path replaced
* all normally located font directories except for the
* JRE fonts dir. This directory is still always located
* and placed at the head of the path but as an
* augmentation to the previous behaviour the
* changes below allow you to additionally append to
* the font path by starting with append: or prepend by
* starting with a prepend: sign. Eg: to append
* -Dsun.java2d.fontpath=append:/usr/local/myfonts
* and to prepend
* -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp
*
* If there is an appendedfontpath it in the font
* configuration it is used instead of searching the
* system for dirs.
* The behaviour of append and prepend is then similar
* to the normal case. ie it goes after what
* you prepend and * before what you append. If the
* sun.java2d.fontpath property is used, but it
* neither the append or prepend syntaxes is used then
* as except for the JRE dir the path is replaced and it
* is up to you to make sure that all the right
* directories are located. This is platform and
* locale-specific so its almost impossible to get
* right, so it should be used with caution.
*/
boolean prependToPath = false;
boolean appendToPath = false;
String dbgFontPath = System.getProperty("sun.java2d.fontpath");
if (dbgFontPath != null) {
if (dbgFontPath.startsWith("prepend:")) {
prependToPath = true;
dbgFontPath =
dbgFontPath.substring("prepend:".length());
} else if (dbgFontPath.startsWith("append:")) {
appendToPath = true;
dbgFontPath =
dbgFontPath.substring("append:".length());
}
}
if (FontUtilities.debugFonts()) {
FontUtilities.logInfo("JRE font directory: " + jreFontDirName);
FontUtilities.logInfo("Extra font path: " + extraFontPath);
FontUtilities.logInfo("Debug font path: " + dbgFontPath);
}
if (dbgFontPath != null) {
/* In debugging mode we register all the paths
* Caution: this is a very expensive call on Solaris:-
*/
fontPath = getPlatformFontPath(noType1Font);
if (extraFontPath != null) {
fontPath = extraFontPath + File.pathSeparator + fontPath;
}
if (appendToPath) {
fontPath += File.pathSeparator + dbgFontPath;
} else if (prependToPath) {
fontPath = dbgFontPath + File.pathSeparator + fontPath;
} else {
fontPath = dbgFontPath;
}
registerFontDirs(fontPath);
} else if (extraFontPath != null) {
/* If the font configuration contains an
* "appendedfontpath" entry, it is interpreted as a
* set of locations that should always be registered.
* It may be additional to locations normally found
* for that place, or it may be locations that need
* to have all their paths registered to locate all
* the needed platform names.
* This is typically when the same .TTF file is
* referenced from multiple font.dir files and all
* of these must be read to find all the native
* (XLFD) names for the font, so that X11 font APIs
* can be used for as many code points as possible.
*/
registerFontDirs(extraFontPath);
}
initCompositeFonts(fontConfig, null);
return null;
}
});
boolean platformFont = AccessController.doPrivileged(
new PrivilegedAction<Boolean>() {
public Boolean run() {
String prop = System.getProperty("java2d.font.usePlatformFont");
String env = System.getenv("JAVA2D_USEPLATFORMFONT");
return "true".equals(prop) || env != null;
}
});
if (platformFont) {
usePlatformFontMetrics = true;
System.out.println("Enabling platform font metrics for win32. This is an unsupported option.");
System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases.");
System.out.println("It is appropriate only for use by applications which do not use any Java 2");
System.out.println("functionality. This property will be removed in a later release.");
}
}
public Font2DHandle getNewComposite(String family, int style,
Font2DHandle handle) {
if (!(handle.font2D instanceof CompositeFont)) {
return handle;
}
CompositeFont oldComp = (CompositeFont)handle.font2D;
PhysicalFont oldFont = oldComp.getSlotFont(0);
if (family == null) {
family = oldFont.getFamilyName(null);
}
if (style == -1) {
style = oldComp.getStyle();
}
Font2D newFont = findFont2D(family, style, NO_FALLBACK);
if (!(newFont instanceof PhysicalFont)) {
newFont = oldFont;
}
PhysicalFont physicalFont = (PhysicalFont)newFont;
CompositeFont dialog2D =
(CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
if (dialog2D == null) { /* shouldn't happen */
return handle;
}
CompositeFont compFont = new CompositeFont(physicalFont, dialog2D);
Font2DHandle newHandle = new Font2DHandle(compFont);
return newHandle;
}
protected void registerCompositeFont(String compositeName,
String[] componentFileNames,
String[] componentNames,
int numMetricsSlots,
int[] exclusionRanges,
int[] exclusionMaxIndex,
boolean defer) {
CompositeFont cf = new CompositeFont(compositeName,
componentFileNames,
componentNames,
numMetricsSlots,
exclusionRanges,
exclusionMaxIndex, defer, this);
addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK);
synchronized (compFonts) {
compFonts[maxCompFont++] = cf;
}
}
/* This variant is used only when the application specifies
* a variant of composite fonts which prefers locale specific or
* proportional fonts.
*/
protected static void registerCompositeFont(String compositeName,
String[] componentFileNames,
String[] componentNames,
int numMetricsSlots,
int[] exclusionRanges,
int[] exclusionMaxIndex,
boolean defer,
ConcurrentHashMap<String, Font2D>
altNameCache) {
CompositeFont cf = new CompositeFont(compositeName,
componentFileNames,
componentNames,
numMetricsSlots,
exclusionRanges,
exclusionMaxIndex, defer,
SunFontManager.getInstance());
/* if the cache has an existing composite for this case, make
* its handle point to this new font.
* This ensures that when the altNameCache that is passed in
* is the global mapNameCache - ie we are running as an application -
* that any statically created java.awt.Font instances which already
* have a Font2D instance will have that re-directed to the new Font
* on subsequent uses. This is particularly important for "the"
* default font instance, or similar cases where a UI toolkit (eg
* Swing) has cached a java.awt.Font. Note that if Swing is using
* a custom composite APIs which update the standard composites have
* no effect - this is typically the case only when using the Windows
* L&F where these APIs would conflict with that L&F anyway.
*/
Font2D oldFont =altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH));
if (oldFont instanceof CompositeFont) {
oldFont.handle.font2D = cf;
}
altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf);
}
private void addCompositeToFontList(CompositeFont f, int rank) {
if (FontUtilities.isLogging()) {
FontUtilities.logInfo("Add to Family " + f.familyName +
", Font " + f.fullName + " rank=" + rank);
}
f.setRank(rank);
compositeFonts.put(f.fullName, f);
fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f);
FontFamily family = FontFamily.getFamily(f.familyName);
if (family == null) {
family = new FontFamily(f.familyName, true, rank);
}
family.setFont(f, f.style);
}
/*
* Systems may have fonts with the same name.
* We want to register only one of such fonts (at least until
* such time as there might be APIs which can accommodate > 1).
* Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts,
* 4) Type1 fonts, 5) native fonts.
*
* If the new font has the same name as the old font, the higher
* ranked font gets added, replacing the lower ranked one.
* If the fonts are of equal rank, then make a special case of
* font configuration rank fonts, which are on closer inspection,
* OT/TT fonts such that the larger font is registered. This is
* a heuristic since a font may be "larger" in the sense of more
* code points, or be a larger "file" because it has more bitmaps.
* So it is possible that using filesize may lead to less glyphs, and
* using glyphs may lead to lower quality display. Probably number
* of glyphs is the ideal, but filesize is information we already
* have and is good enough for the known cases.
* Also don't want to register fonts that match JRE font families
* but are coming from a source other than the JRE.
* This will ensure that we will algorithmically style the JRE
* plain font and get the same set of glyphs for all styles.
*
* Note that this method returns a value
* if it returns the same object as its argument that means this
* font was newly registered.
* If it returns a different object it means this font already exists,
* and you should use that one.
* If it returns null means this font was not registered and none
* in that name is registered. The caller must find a substitute
*/
// MACOSX begin -- need to access this in subclass
protected PhysicalFont addToFontList(PhysicalFont f, int rank) {
// MACOSX end
String fontName = f.fullName;
String familyName = f.familyName;
if (fontName == null || fontName.isEmpty()) {
return null;
}
if (compositeFonts.containsKey(fontName)) {
/* Don't register any font that has the same name as a composite */
return null;
}
f.setRank(rank);
if (!physicalFonts.containsKey(fontName)) {
if (FontUtilities.isLogging()) {
FontUtilities.logInfo("Add to Family " + familyName +
", Font " + fontName + " rank=" + rank);
}
physicalFonts.put(fontName, f);
FontFamily family = FontFamily.getFamily(familyName);
if (family == null) {
family = new FontFamily(familyName, false, rank);
family.setFont(f, f.style);
} else {
family.setFont(f, f.style);
}
fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f);
return f;
} else {
PhysicalFont newFont = f;
PhysicalFont oldFont = physicalFonts.get(fontName);
if (oldFont == null) {
return null;
}
/* If the new font is of an equal or higher rank, it is a
* candidate to replace the current one, subject to further tests.
*/
if (oldFont.getRank() >= rank) {
/* All fonts initialise their mapper when first
* used. If the mapper is non-null then this font
* has been accessed at least once. In that case
* do not replace it. This may be overly stringent,
* but its probably better not to replace a font that
* someone is already using without a compelling reason.
* Additionally the primary case where it is known
* this behaviour is important is in certain composite
* fonts, and since all the components of a given
* composite are usually initialised together this
* is unlikely. For this to be a problem, there would
* have to be a case where two different composites used
* different versions of the same-named font, and they
* were initialised and used at separate times.
* In that case we continue on and allow the new font to
* be installed, but replaceFont will continue to allow
* the original font to be used in Composite fonts.
*/
if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) {
return oldFont;
}
/* Normally we require a higher rank to replace a font,
* but as a special case, if the two fonts are the same rank,
* and are instances of TrueTypeFont we want the
* more complete (larger) one.
*/
if (oldFont.getRank() == rank) {
if (oldFont instanceof TrueTypeFont &&
newFont instanceof TrueTypeFont) {
TrueTypeFont oldTTFont = (TrueTypeFont)oldFont;
TrueTypeFont newTTFont = (TrueTypeFont)newFont;
if (oldTTFont.fileSize >= newTTFont.fileSize) {
return oldFont;
}
} else {
return oldFont;
}
}
/* Don't replace ever JRE fonts.
* This test is in case a font configuration references
* a Lucida font, which has been mapped to a Lucida
* from the host O/S. The assumption here is that any
* such font configuration file is probably incorrect, or
* the host O/S version is for the use of AWT.
* In other words if we reach here, there's a possible
* problem with our choice of font configuration fonts.
*/
if (oldFont.platName.startsWith(jreFontDirName)) {
if (FontUtilities.isLogging()) {
FontUtilities.logWarning("Unexpected attempt to replace a JRE " +
" font " + fontName + " from " + oldFont.platName +
" with " + newFont.platName);
}
return oldFont;
}
if (FontUtilities.isLogging()) {
FontUtilities.logInfo("Replace in Family " + familyName +
",Font " + fontName + " new rank="+rank +
" from " + oldFont.platName +
" with " + newFont.platName);
}
replaceFont(oldFont, newFont);
physicalFonts.put(fontName, newFont);
fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH),
newFont);
FontFamily family = FontFamily.getFamily(familyName);
if (family == null) {
family = new FontFamily(familyName, false, rank);
family.setFont(newFont, newFont.style);
} else {
family.setFont(newFont, newFont.style);
}
return newFont;
} else {
return oldFont;
}
}
}
public Font2D[] getRegisteredFonts() {
PhysicalFont[] physFonts = getPhysicalFonts();
int mcf = maxCompFont; /* for MT-safety */
Font2D[] regFonts = new Font2D[physFonts.length+mcf];
System.arraycopy(compFonts, 0, regFonts, 0, mcf);
System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length);
return regFonts;
}
protected PhysicalFont[] getPhysicalFonts() {
return physicalFonts.values().toArray(new PhysicalFont[0]);
}
/* The class FontRegistrationInfo is used when a client says not
* to register a font immediately. This mechanism is used to defer
* initialisation of all the components of composite fonts at JRE
* start-up. The CompositeFont class is "aware" of this and when it
* is first used it asks for the registration of its components.
* Also in the event that any physical font is requested the
* deferred fonts are initialised before triggering a search of the
* system.
* Two maps are used. One to track the deferred fonts. The
* other to track the fonts that have been initialised through this
* mechanism.
*/
private static final class FontRegistrationInfo {
String fontFilePath;
String[] nativeNames;
int fontFormat;
boolean javaRasterizer;
int fontRank;
FontRegistrationInfo(String fontPath, String[] names, int format,
boolean useJavaRasterizer, int rank) {
this.fontFilePath = fontPath;
this.nativeNames = names;
this.fontFormat = format;
this.javaRasterizer = useJavaRasterizer;
this.fontRank = rank;
}
}
private final ConcurrentHashMap<String, FontRegistrationInfo>
deferredFontFiles = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Font2DHandle>
initialisedFonts = new ConcurrentHashMap<>();
/* Remind: possibly enhance initialiseDeferredFonts() to be
* optionally given a name and a style and it could stop when it
* finds that font - but this would be a problem if two of the
* fonts reference the same font face name (cf the Solaris
* euro fonts).
*/
protected synchronized void initialiseDeferredFonts() {
for (String fileName : deferredFontFiles.keySet()) {
initialiseDeferredFont(fileName);
}
}
protected synchronized void registerDeferredJREFonts(String jreDir) {
for (FontRegistrationInfo info : deferredFontFiles.values()) {
if (info.fontFilePath != null &&
info.fontFilePath.startsWith(jreDir)) {
initialiseDeferredFont(info.fontFilePath);
}
}
}
public boolean isDeferredFont(String fileName) {
return deferredFontFiles.containsKey(fileName);
}
PhysicalFont findJREDeferredFont(String name, int style) {
/* Iterate over the deferred font files looking for any in the
* jre directory that we didn't recognise, open each of these.
* In almost all installations this will quickly fall through
* because jreOtherFontFiles will be empty.
* noOtherJREFontFiles is used so we can skip this block as soon
* as its determined that it's not needed - almost always after the
* very first time through.
*/
if (noOtherJREFontFiles) {
return null;
}
synchronized (jreFontDirName) {
if (jreOtherFontFiles == null) {
HashSet<String> otherFontFiles = new HashSet<>();
for (String deferredFile : deferredFontFiles.keySet()) {
File file = new File(deferredFile);
String dir = file.getParent();
/* skip names which aren't absolute, aren't in the JRE
* directory, or are known Lucida fonts.
*/
if (dir == null || !dir.equals(jreFontDirName)) {
continue;
}
otherFontFiles.add(deferredFile);
}
jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY);
if (jreOtherFontFiles.length == 0) {
noOtherJREFontFiles = true;
}
}
for (int i=0; i<jreOtherFontFiles.length;i++) {
String fileName = jreOtherFontFiles[i];
if (fileName == null) {
continue;
}
jreOtherFontFiles[i] = null;
PhysicalFont physicalFont = initialiseDeferredFont(fileName);
if (physicalFont != null &&
(physicalFont.getFontName(null).equalsIgnoreCase(name) ||
physicalFont.getFamilyName(null).equalsIgnoreCase(name))
&& physicalFont.style == style) {
return physicalFont;
}
}
}
return null;
}
private PhysicalFont findOtherDeferredFont(String name, int style) {
for (String fileName : deferredFontFiles.keySet()) {
PhysicalFont physicalFont = initialiseDeferredFont(fileName);
if (physicalFont != null &&
(physicalFont.getFontName(null).equalsIgnoreCase(name) ||
physicalFont.getFamilyName(null).equalsIgnoreCase(name)) &&
physicalFont.style == style) {
return physicalFont;
}
}
return null;
}
private PhysicalFont findDeferredFont(String name, int style) {
PhysicalFont physicalFont = findJREDeferredFont(name, style);
if (physicalFont != null) {
return physicalFont;
} else {
return findOtherDeferredFont(name, style);
}
}
public void registerDeferredFont(String fileNameKey,
String fullPathName,
String[] nativeNames,
int fontFormat,
boolean useJavaRasterizer,
int fontRank) {
FontRegistrationInfo regInfo =
new FontRegistrationInfo(fullPathName, nativeNames, fontFormat,
useJavaRasterizer, fontRank);
deferredFontFiles.put(fileNameKey, regInfo);
}
public synchronized
PhysicalFont initialiseDeferredFont(String fileNameKey) {
if (fileNameKey == null) {
return null;
}
if (FontUtilities.isLogging()) {
FontUtilities.logInfo("Opening deferred font file " + fileNameKey);
}
PhysicalFont physicalFont = null;
FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey);
if (regInfo != null) {
deferredFontFiles.remove(fileNameKey);
physicalFont = registerFontFile(regInfo.fontFilePath,
regInfo.nativeNames,
regInfo.fontFormat,
regInfo.javaRasterizer,
regInfo.fontRank);
if (physicalFont != null) {
/* Store the handle, so that if a font is bad, we
* retrieve the substituted font.
*/
initialisedFonts.put(fileNameKey, physicalFont.handle);
} else {
initialisedFonts.put(fileNameKey, FONT_HANDLE_NULL);
}
} else {
Font2DHandle handle = initialisedFonts.get(fileNameKey);
if (handle == null) {
/* Probably shouldn't happen, but just in case */
initialisedFonts.put(fileNameKey, FONT_HANDLE_NULL);
} else {
physicalFont = (PhysicalFont)(handle.font2D);
}
}
return physicalFont;
}
public boolean isRegisteredFontFile(String name) {
return registeredFonts.containsKey(name);
}
public PhysicalFont getRegisteredFontFile(String name) {
return registeredFonts.get(name);
}
/* Note that the return value from this method is not always
* derived from this file, and may be null. See addToFontList for
* some explanation of this.
*/
public PhysicalFont registerFontFile(String fileName,
String[] nativeNames,
int fontFormat,
boolean useJavaRasterizer,
int fontRank) {
PhysicalFont regFont = registeredFonts.get(fileName);
if (regFont != null) {
return regFont;
}
PhysicalFont physicalFont = null;
try {
switch (fontFormat) {
case FONTFORMAT_TRUETYPE:
int fn = 0;
TrueTypeFont ttf;
do {
ttf = new TrueTypeFont(fileName, nativeNames, fn++,
useJavaRasterizer);
PhysicalFont pf = addToFontList(ttf, fontRank);
if (physicalFont == null) {
physicalFont = pf;
}
}
while (fn < ttf.getFontCount());
break;
case FONTFORMAT_TYPE1:
Type1Font t1f = new Type1Font(fileName, nativeNames);
physicalFont = addToFontList(t1f, fontRank);
break;
case FONTFORMAT_NATIVE:
NativeFont nf = new NativeFont(fileName, false);
physicalFont = addToFontList(nf, fontRank);
break;
default:
}
if (FontUtilities.isLogging()) {
FontUtilities.logInfo("Registered file " + fileName + " as font " +
physicalFont + " rank=" + fontRank);
}
} catch (FontFormatException ffe) {
if (FontUtilities.isLogging()) {
FontUtilities.logInfo("Unusable font: " + fileName + " " + ffe.toString());
}