1 /*
2  * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.font;
27 
28 import java.awt.Font;
29 import java.awt.FontFormatException;
30 import java.io.BufferedReader;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.FilenameFilter;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.security.AccessController;
37 import java.security.PrivilegedAction;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Hashtable;
42 import java.util.List;
43 import java.util.Locale;
44 import java.util.Map;
45 import java.util.NoSuchElementException;
46 import java.util.StringTokenizer;
47 import java.util.TreeMap;
48 import java.util.Vector;
49 import java.util.concurrent.ConcurrentHashMap;
50 
51 import javax.swing.plaf.FontUIResource;
52 
53 import sun.awt.FontConfiguration;
54 import sun.awt.SunToolkit;
55 import sun.awt.util.ThreadGroupUtils;
56 import sun.java2d.FontSupport;
57 import sun.util.logging.PlatformLogger;
58 
59 /**
60  * The base implementation of the {@link FontManager} interface. It implements
61  * the platform independent, shared parts of OpenJDK's FontManager
62  * implementations. The platform specific parts are declared as abstract
63  * methods that have to be implemented by specific implementations.
64  */
65 public abstract class SunFontManager implements FontSupport, FontManagerForSGE {
66 
67     private static class TTFilter implements FilenameFilter {
accept(File dir,String name)68         public boolean accept(File dir,String name) {
69             /* all conveniently have the same suffix length */
70             int offset = name.length()-4;
71             if (offset <= 0) { /* must be at least A.ttf */
72                 return false;
73             } else {
74                 return(name.startsWith(".ttf", offset) ||
75                        name.startsWith(".TTF", offset) ||
76                        name.startsWith(".ttc", offset) ||
77                        name.startsWith(".TTC", offset) ||
78                        name.startsWith(".otf", offset) ||
79                        name.startsWith(".OTF", offset));
80             }
81         }
82     }
83 
84     private static class T1Filter implements FilenameFilter {
accept(File dir,String name)85         public boolean accept(File dir,String name) {
86             if (noType1Font) {
87                 return false;
88             }
89             /* all conveniently have the same suffix length */
90             int offset = name.length()-4;
91             if (offset <= 0) { /* must be at least A.pfa */
92                 return false;
93             } else {
94                 return(name.startsWith(".pfa", offset) ||
95                        name.startsWith(".pfb", offset) ||
96                        name.startsWith(".PFA", offset) ||
97                        name.startsWith(".PFB", offset));
98             }
99         }
100     }
101 
102      private static class TTorT1Filter implements FilenameFilter {
accept(File dir, String name)103         public boolean accept(File dir, String name) {
104 
105             /* all conveniently have the same suffix length */
106             int offset = name.length()-4;
107             if (offset <= 0) { /* must be at least A.ttf or A.pfa */
108                 return false;
109             } else {
110                 boolean isTT =
111                     name.startsWith(".ttf", offset) ||
112                     name.startsWith(".TTF", offset) ||
113                     name.startsWith(".ttc", offset) ||
114                     name.startsWith(".TTC", offset) ||
115                     name.startsWith(".otf", offset) ||
116                     name.startsWith(".OTF", offset);
117                 if (isTT) {
118                     return true;
119                 } else if (noType1Font) {
120                     return false;
121                 } else {
122                     return(name.startsWith(".pfa", offset) ||
123                            name.startsWith(".pfb", offset) ||
124                            name.startsWith(".PFA", offset) ||
125                            name.startsWith(".PFB", offset));
126                 }
127             }
128         }
129     }
130 
131     private static Font2DHandle FONT_HANDLE_NULL = new Font2DHandle(null);
132 
133     public static final int FONTFORMAT_NONE = -1;
134     public static final int FONTFORMAT_TRUETYPE = 0;
135     public static final int FONTFORMAT_TYPE1 = 1;
136     public static final int FONTFORMAT_TTC = 2;
137     public static final int FONTFORMAT_COMPOSITE = 3;
138     public static final int FONTFORMAT_NATIVE = 4;
139 
140     /* Pool of 20 font file channels chosen because some UTF-8 locale
141      * composite fonts can use up to 16 platform fonts (including the
142      * Lucida fall back). This should prevent channel thrashing when
143      * dealing with one of these fonts.
144      * The pool array stores the fonts, rather than directly referencing
145      * the channels, as the font needs to do the open/close work.
146      */
147     // MACOSX begin -- need to access these in subclass
148     protected static final int CHANNELPOOLSIZE = 20;
149     protected FileFont[] fontFileCache = new FileFont[CHANNELPOOLSIZE];
150     // MACOSX end
151     private int lastPoolIndex = 0;
152 
153     /* Need to implement a simple linked list scheme for fast
154      * traversal and lookup.
155      * Also want to "fast path" dialog so there's minimal overhead.
156      */
157     /* There are at exactly 20 composite fonts: 5 faces (but some are not
158      * usually different), in 4 styles. The array may be auto-expanded
159      * later if more are needed, eg for user-defined composites or locale
160      * variants.
161      */
162     private int maxCompFont = 0;
163     private CompositeFont [] compFonts = new CompositeFont[20];
164     private ConcurrentHashMap<String, CompositeFont>
165         compositeFonts = new ConcurrentHashMap<>();
166     private ConcurrentHashMap<String, PhysicalFont>
167         physicalFonts = new ConcurrentHashMap<>();
168     private ConcurrentHashMap<String, PhysicalFont>
169         registeredFonts = new ConcurrentHashMap<>();
170 
171     /* given a full name find the Font. Remind: there's duplication
172      * here in that this contains the content of compositeFonts +
173      * physicalFonts.
174      */
175     // MACOSX begin -- need to access this in subclass
176     protected ConcurrentHashMap<String, Font2D>
177         fullNameToFont = new ConcurrentHashMap<>();
178     // MACOSX end
179 
180     /* TrueType fonts have localised names. Support searching all
181      * of these before giving up on a name.
182      */
183     private HashMap<String, TrueTypeFont> localeFullNamesToFont;
184 
185     private PhysicalFont defaultPhysicalFont;
186 
187     static boolean longAddresses;
188     private boolean loaded1dot0Fonts = false;
189     boolean loadedAllFonts = false;
190     boolean loadedAllFontFiles = false;
191     String[] jreOtherFontFiles;
192     boolean noOtherJREFontFiles = false; // initial assumption.
193 
194     public static String jreLibDirName;
195     public static String jreFontDirName;
196     private static HashSet<String> missingFontFiles = null;
197     private String defaultFontName;
198     private String defaultFontFileName;
199     protected HashSet<String> registeredFontFiles = new HashSet<>();
200 
201     private ArrayList<String> badFonts;
202     /* fontPath is the location of all fonts on the system, excluding the
203      * JRE's own font directory but including any path specified using the
204      * sun.java2d.fontpath property. Together with that property,  it is
205      * initialised by the getPlatformFontPath() method
206      * This call must be followed by a call to registerFontDirs(fontPath)
207      * once any extra debugging path has been appended.
208      */
209     protected String fontPath;
210     private FontConfiguration fontConfig;
211     /* discoveredAllFonts is set to true when all fonts on the font path are
212      * discovered. This usually also implies opening, validating and
213      * registering, but an implementation may be optimized to avold this.
214      * So see also "loadedAllFontFiles"
215      */
216     private boolean discoveredAllFonts = false;
217 
218     /* No need to keep consing up new instances - reuse a singleton.
219      * The trade-off is that these objects don't get GC'd.
220      */
221     private static final FilenameFilter ttFilter = new TTFilter();
222     private static final FilenameFilter t1Filter = new T1Filter();
223 
224     private Font[] allFonts;
225     private String[] allFamilies; // cache for default locale only
226     private Locale lastDefaultLocale;
227 
228     public static boolean noType1Font;
229 
230     /* Used to indicate required return type from toArray(..); */
231     private static String[] STR_ARRAY = new String[0];
232 
233     /**
234      * Deprecated, unsupported hack - actually invokes a bug!
235      * Left in for a customer, don't remove.
236      */
237     private boolean usePlatformFontMetrics = false;
238 
239     /**
240      * Returns the global SunFontManager instance. This is similar to
241      * {@link FontManagerFactory#getInstance()} but it returns a
242      * SunFontManager instance instead. This is only used in internal classes
243      * where we can safely assume that a SunFontManager is to be used.
244      *
245      * @return the global SunFontManager instance
246      */
getInstance()247     public static SunFontManager getInstance() {
248         FontManager fm = FontManagerFactory.getInstance();
249         return (SunFontManager) fm;
250     }
251 
getTrueTypeFilter()252     public FilenameFilter getTrueTypeFilter() {
253         return ttFilter;
254     }
255 
getType1Filter()256     public FilenameFilter getType1Filter() {
257         return t1Filter;
258     }
259 
260     /* After we reach MAXSOFTREFCNT, use weak refs for created fonts.
261      * This means that a small number of created fonts as used in a UI app
262      * will not be eagerly collected, but an app that create many will
263      * have them collected more frequently to reclaim storage.
264      */
265     private static int maxSoftRefCnt = 10;
266 
267     static {
initStatic()268         initStatic();
269     }
270 
271     @SuppressWarnings("removal")
initStatic()272     private static void initStatic() {
273         AccessController.doPrivileged(new PrivilegedAction<Void>() {
274             public Void run() {
275                 FontManagerNativeLibrary.load();
276 
277                 // JNI throws an exception if a class/method/field is not found,
278                 // so there's no need to do anything explicit here.
279                 initIDs();
280 
281                 switch (StrikeCache.nativeAddressSize) {
282                 case 8: longAddresses = true; break;
283                 case 4: longAddresses = false; break;
284                 default: throw new RuntimeException("Unexpected address size");
285                 }
286 
287                 noType1Font = "true".equals(System.getProperty("sun.java2d.noType1Font"));
288                 jreLibDirName = System.getProperty("java.home","") + File.separator + "lib";
289                 jreFontDirName = jreLibDirName + File.separator + "fonts";
290 
291                 maxSoftRefCnt = Integer.getInteger("sun.java2d.font.maxSoftRefs", 10);
292                 return null;
293             }
294         });
295     }
296 
297     /**
298      * If the module image layout changes the location of JDK fonts,
299      * this will be updated to reflect that.
300      */
getJDKFontDir()301     public static final String getJDKFontDir() {
302         return jreFontDirName;
303     }
304 
getEUDCFont()305     public TrueTypeFont getEUDCFont() {
306         // Overridden in Windows.
307         return null;
308     }
309 
310     /* Initialise ptrs used by JNI methods */
initIDs()311     private static native void initIDs();
312 
313     @SuppressWarnings("removal")
SunFontManager()314     protected SunFontManager() {
315         AccessController.doPrivileged(new PrivilegedAction<Void>() {
316             public Void run() {
317                 File badFontFile =
318                     new File(jreFontDirName + File.separator + "badfonts.txt");
319                 if (badFontFile.exists()) {
320                     badFonts = new ArrayList<>();
321                     try (FileInputStream fis = new FileInputStream(badFontFile);
322                          BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
323                         while (true) {
324                             String name = br.readLine();
325                             if (name == null) {
326                                 break;
327                             } else {
328                                 if (FontUtilities.debugFonts()) {
329                                     FontUtilities.logWarning("read bad font: " + name);
330                                 }
331                                 badFonts.add(name);
332                             }
333                         }
334                     } catch (IOException e) {
335                     }
336                 }
337 
338                 /* Here we get the fonts in jre/lib/fonts and register
339                  * them so they are always available and preferred over
340                  * other fonts. This needs to be registered before the
341                  * composite fonts as otherwise some native font that
342                  * corresponds may be found as we don't have a way to
343                  * handle two fonts of the same name, so the JRE one
344                  * must be the first one registered. Pass "true" to
345                  * registerFonts method as on-screen these JRE fonts
346                  * always go through the JDK rasteriser.
347                  */
348                 if (FontUtilities.isLinux || FontUtilities.isBSD) {
349                     /* Linux font configuration uses these fonts */
350                     registerFontDir(jreFontDirName);
351                 }
352                 registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK,
353                                    true, false);
354 
355                 /* Create the font configuration and get any font path
356                  * that might be specified.
357                  */
358                 fontConfig = createFontConfiguration();
359 
360                 String[] fontInfo = getDefaultPlatformFont();
361                 defaultFontName = fontInfo[0];
362                 if (defaultFontName == null && FontUtilities.debugFonts()) {
363                     FontUtilities.logWarning("defaultFontName is null");
364                 }
365                 defaultFontFileName = fontInfo[1];
366 
367                 String extraFontPath = fontConfig.getExtraFontPath();
368 
369                 /* In prior releases the debugging font path replaced
370                  * all normally located font directories except for the
371                  * JRE fonts dir. This directory is still always located
372                  * and placed at the head of the path but as an
373                  * augmentation to the previous behaviour the
374                  * changes below allow you to additionally append to
375                  * the font path by starting with append: or prepend by
376                  * starting with a prepend: sign. Eg: to append
377                  * -Dsun.java2d.fontpath=append:/usr/local/myfonts
378                  * and to prepend
379                  * -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp
380                  *
381                  * If there is an appendedfontpath it in the font
382                  * configuration it is used instead of searching the
383                  * system for dirs.
384                  * The behaviour of append and prepend is then similar
385                  * to the normal case. ie it goes after what
386                  * you prepend and * before what you append. If the
387                  * sun.java2d.fontpath property is used, but it
388                  * neither the append or prepend syntaxes is used then
389                  * as except for the JRE dir the path is replaced and it
390                  * is up to you to make sure that all the right
391                  * directories are located. This is platform and
392                  * locale-specific so its almost impossible to get
393                  * right, so it should be used with caution.
394                  */
395                 boolean prependToPath = false;
396                 boolean appendToPath = false;
397                 String dbgFontPath = System.getProperty("sun.java2d.fontpath");
398 
399                 if (dbgFontPath != null) {
400                     if (dbgFontPath.startsWith("prepend:")) {
401                         prependToPath = true;
402                         dbgFontPath =
403                             dbgFontPath.substring("prepend:".length());
404                     } else if (dbgFontPath.startsWith("append:")) {
405                         appendToPath = true;
406                         dbgFontPath =
407                             dbgFontPath.substring("append:".length());
408                     }
409                 }
410 
411                 if (FontUtilities.debugFonts()) {
412                     FontUtilities.logInfo("JRE font directory: " + jreFontDirName);
413                     FontUtilities.logInfo("Extra font path: " + extraFontPath);
414                     FontUtilities.logInfo("Debug font path: " + dbgFontPath);
415                 }
416 
417                 if (dbgFontPath != null) {
418                     /* In debugging mode we register all the paths
419                      * Caution: this is a very expensive call on Solaris:-
420                      */
421                     fontPath = getPlatformFontPath(noType1Font);
422 
423                     if (extraFontPath != null) {
424                         fontPath = extraFontPath + File.pathSeparator + fontPath;
425                     }
426                     if (appendToPath) {
427                         fontPath += File.pathSeparator + dbgFontPath;
428                     } else if (prependToPath) {
429                         fontPath = dbgFontPath + File.pathSeparator + fontPath;
430                     } else {
431                         fontPath = dbgFontPath;
432                     }
433                     registerFontDirs(fontPath);
434                 } else if (extraFontPath != null) {
435                     /* If the font configuration contains an
436                      * "appendedfontpath" entry, it is interpreted as a
437                      * set of locations that should always be registered.
438                      * It may be additional to locations normally found
439                      * for that place, or it may be locations that need
440                      * to have all their paths registered to locate all
441                      * the needed platform names.
442                      * This is typically when the same .TTF file is
443                      * referenced from multiple font.dir files and all
444                      * of these must be read to find all the native
445                      * (XLFD) names for the font, so that X11 font APIs
446                      * can be used for as many code points as possible.
447                      */
448                     registerFontDirs(extraFontPath);
449                 }
450 
451                 initCompositeFonts(fontConfig, null);
452 
453                 return null;
454             }
455         });
456 
457         boolean platformFont = AccessController.doPrivileged(
458             new PrivilegedAction<Boolean>() {
459                     public Boolean run() {
460                         String prop = System.getProperty("java2d.font.usePlatformFont");
461                         String env = System.getenv("JAVA2D_USEPLATFORMFONT");
462                         return "true".equals(prop) || env != null;
463                     }
464             });
465 
466         if (platformFont) {
467             usePlatformFontMetrics = true;
468             System.out.println("Enabling platform font metrics for win32. This is an unsupported option.");
469             System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases.");
470             System.out.println("It is appropriate only for use by applications which do not use any Java 2");
471             System.out.println("functionality. This property will be removed in a later release.");
472         }
473     }
474 
getNewComposite(String family, int style, Font2DHandle handle)475     public Font2DHandle getNewComposite(String family, int style,
476                                         Font2DHandle handle) {
477 
478         if (!(handle.font2D instanceof CompositeFont)) {
479             return handle;
480         }
481 
482         CompositeFont oldComp = (CompositeFont)handle.font2D;
483         PhysicalFont oldFont = oldComp.getSlotFont(0);
484 
485         if (family == null) {
486             family = oldFont.getFamilyName(null);
487         }
488         if (style == -1) {
489             style = oldComp.getStyle();
490         }
491 
492         Font2D newFont = findFont2D(family, style, NO_FALLBACK);
493         if (!(newFont instanceof PhysicalFont)) {
494             newFont = oldFont;
495         }
496         PhysicalFont physicalFont = (PhysicalFont)newFont;
497         CompositeFont dialog2D =
498             (CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
499         if (dialog2D == null) { /* shouldn't happen */
500             return handle;
501         }
502         CompositeFont compFont = new CompositeFont(physicalFont, dialog2D);
503         Font2DHandle newHandle = new Font2DHandle(compFont);
504         return newHandle;
505     }
506 
registerCompositeFont(String compositeName, String[] componentFileNames, String[] componentNames, int numMetricsSlots, int[] exclusionRanges, int[] exclusionMaxIndex, boolean defer)507     protected void registerCompositeFont(String compositeName,
508                                       String[] componentFileNames,
509                                       String[] componentNames,
510                                       int numMetricsSlots,
511                                       int[] exclusionRanges,
512                                       int[] exclusionMaxIndex,
513                                       boolean defer) {
514 
515         CompositeFont cf = new CompositeFont(compositeName,
516                                              componentFileNames,
517                                              componentNames,
518                                              numMetricsSlots,
519                                              exclusionRanges,
520                                              exclusionMaxIndex, defer, this);
521         addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK);
522         synchronized (compFonts) {
523             compFonts[maxCompFont++] = cf;
524         }
525     }
526 
527     /* This variant is used only when the application specifies
528      * a variant of composite fonts which prefers locale specific or
529      * proportional fonts.
530      */
registerCompositeFont(String compositeName, String[] componentFileNames, String[] componentNames, int numMetricsSlots, int[] exclusionRanges, int[] exclusionMaxIndex, boolean defer, ConcurrentHashMap<String, Font2D> altNameCache)531     protected static void registerCompositeFont(String compositeName,
532                                                 String[] componentFileNames,
533                                                 String[] componentNames,
534                                                 int numMetricsSlots,
535                                                 int[] exclusionRanges,
536                                                 int[] exclusionMaxIndex,
537                                                 boolean defer,
538                                                 ConcurrentHashMap<String, Font2D>
539                                                 altNameCache) {
540 
541         CompositeFont cf = new CompositeFont(compositeName,
542                                              componentFileNames,
543                                              componentNames,
544                                              numMetricsSlots,
545                                              exclusionRanges,
546                                              exclusionMaxIndex, defer,
547                                              SunFontManager.getInstance());
548 
549         /* if the cache has an existing composite for this case, make
550          * its handle point to this new font.
551          * This ensures that when the altNameCache that is passed in
552          * is the global mapNameCache - ie we are running as an application -
553          * that any statically created java.awt.Font instances which already
554          * have a Font2D instance will have that re-directed to the new Font
555          * on subsequent uses. This is particularly important for "the"
556          * default font instance, or similar cases where a UI toolkit (eg
557          * Swing) has cached a java.awt.Font. Note that if Swing is using
558          * a custom composite APIs which update the standard composites have
559          * no effect - this is typically the case only when using the Windows
560          * L&F where these APIs would conflict with that L&F anyway.
561          */
562         Font2D oldFont =altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH));
563         if (oldFont instanceof CompositeFont) {
564             oldFont.handle.font2D = cf;
565         }
566         altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf);
567     }
568 
addCompositeToFontList(CompositeFont f, int rank)569     private void addCompositeToFontList(CompositeFont f, int rank) {
570         if (FontUtilities.isLogging()) {
571             FontUtilities.logInfo("Add to Family " + f.familyName +
572                         ", Font " + f.fullName + " rank=" + rank);
573         }
574         f.setRank(rank);
575         compositeFonts.put(f.fullName, f);
576         fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f);
577 
578         FontFamily family = FontFamily.getFamily(f.familyName);
579         if (family == null) {
580             family = new FontFamily(f.familyName, true, rank);
581         }
582         family.setFont(f, f.style);
583     }
584 
585     /*
586      * Systems may have fonts with the same name.
587      * We want to register only one of such fonts (at least until
588      * such time as there might be APIs which can accommodate > 1).
589      * Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts,
590      * 4) Type1 fonts, 5) native fonts.
591      *
592      * If the new font has the same name as the old font, the higher
593      * ranked font gets added, replacing the lower ranked one.
594      * If the fonts are of equal rank, then make a special case of
595      * font configuration rank fonts, which are on closer inspection,
596      * OT/TT fonts such that the larger font is registered. This is
597      * a heuristic since a font may be "larger" in the sense of more
598      * code points, or be a larger "file" because it has more bitmaps.
599      * So it is possible that using filesize may lead to less glyphs, and
600      * using glyphs may lead to lower quality display. Probably number
601      * of glyphs is the ideal, but filesize is information we already
602      * have and is good enough for the known cases.
603      * Also don't want to register fonts that match JRE font families
604      * but are coming from a source other than the JRE.
605      * This will ensure that we will algorithmically style the JRE
606      * plain font and get the same set of glyphs for all styles.
607      *
608      * Note that this method returns a value
609      * if it returns the same object as its argument that means this
610      * font was newly registered.
611      * If it returns a different object it means this font already exists,
612      * and you should use that one.
613      * If it returns null means this font was not registered and none
614      * in that name is registered. The caller must find a substitute
615      */
616     // MACOSX begin -- need to access this in subclass
addToFontList(PhysicalFont f, int rank)617     protected PhysicalFont addToFontList(PhysicalFont f, int rank) {
618     // MACOSX end
619 
620         String fontName = f.fullName;
621         String familyName = f.familyName;
622         if (fontName == null || fontName.isEmpty()) {
623             return null;
624         }
625         if (compositeFonts.containsKey(fontName)) {
626             /* Don't register any font that has the same name as a composite */
627             return null;
628         }
629         f.setRank(rank);
630         if (!physicalFonts.containsKey(fontName)) {
631             if (FontUtilities.isLogging()) {
632                 FontUtilities.logInfo("Add to Family " + familyName +
633                             ", Font " + fontName + " rank=" + rank);
634             }
635             physicalFonts.put(fontName, f);
636             FontFamily family = FontFamily.getFamily(familyName);
637             if (family == null) {
638                 family = new FontFamily(familyName, false, rank);
639                 family.setFont(f, f.style);
640             } else {
641                 family.setFont(f, f.style);
642             }
643             fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f);
644             return f;
645         } else {
646             PhysicalFont newFont = f;
647             PhysicalFont oldFont = physicalFonts.get(fontName);
648             if (oldFont == null) {
649                 return null;
650             }
651             /* If the new font is of an equal or higher rank, it is a
652              * candidate to replace the current one, subject to further tests.
653              */
654             if (oldFont.getRank() >= rank) {
655 
656                 /* All fonts initialise their mapper when first
657                  * used. If the mapper is non-null then this font
658                  * has been accessed at least once. In that case
659                  * do not replace it. This may be overly stringent,
660                  * but its probably better not to replace a font that
661                  * someone is already using without a compelling reason.
662                  * Additionally the primary case where it is known
663                  * this behaviour is important is in certain composite
664                  * fonts, and since all the components of a given
665                  * composite are usually initialised together this
666                  * is unlikely. For this to be a problem, there would
667                  * have to be a case where two different composites used
668                  * different versions of the same-named font, and they
669                  * were initialised and used at separate times.
670                  * In that case we continue on and allow the new font to
671                  * be installed, but replaceFont will continue to allow
672                  * the original font to be used in Composite fonts.
673                  */
674                 if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) {
675                     return oldFont;
676                 }
677 
678                 /* Normally we require a higher rank to replace a font,
679                  * but as a special case, if the two fonts are the same rank,
680                  * and are instances of TrueTypeFont we want the
681                  * more complete (larger) one.
682                  */
683                 if (oldFont.getRank() == rank) {
684                     if (oldFont instanceof TrueTypeFont &&
685                         newFont instanceof TrueTypeFont) {
686                         TrueTypeFont oldTTFont = (TrueTypeFont)oldFont;
687                         TrueTypeFont newTTFont = (TrueTypeFont)newFont;
688                         if (oldTTFont.fileSize >= newTTFont.fileSize) {
689                             return oldFont;
690                         }
691                     } else {
692                         return oldFont;
693                     }
694                 }
695                 /* Don't replace ever JRE fonts.
696                  * This test is in case a font configuration references
697                  * a Lucida font, which has been mapped to a Lucida
698                  * from the host O/S. The assumption here is that any
699                  * such font configuration file is probably incorrect, or
700                  * the host O/S version is for the use of AWT.
701                  * In other words if we reach here, there's a possible
702                  * problem with our choice of font configuration fonts.
703                  */
704                 if (oldFont.platName.startsWith(jreFontDirName)) {
705                     if (FontUtilities.isLogging()) {
706                         FontUtilities.logWarning("Unexpected attempt to replace a JRE " +
707                                        " font " + fontName + " from " + oldFont.platName +
708                                        " with " + newFont.platName);
709                     }
710                     return oldFont;
711                 }
712 
713                 if (FontUtilities.isLogging()) {
714                     FontUtilities.logInfo("Replace in Family " + familyName +
715                                     ",Font " + fontName + " new rank="+rank +
716                                     " from " + oldFont.platName +
717                                     " with " + newFont.platName);
718                 }
719                 replaceFont(oldFont, newFont);
720                 physicalFonts.put(fontName, newFont);
721                 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH),
722                                    newFont);
723 
724                 FontFamily family = FontFamily.getFamily(familyName);
725                 if (family == null) {
726                     family = new FontFamily(familyName, false, rank);
727                     family.setFont(newFont, newFont.style);
728                 } else {
729                     family.setFont(newFont, newFont.style);
730                 }
731                 return newFont;
732             } else {
733                 return oldFont;
734             }
735         }
736     }
737 
getRegisteredFonts()738     public Font2D[] getRegisteredFonts() {
739         PhysicalFont[] physFonts = getPhysicalFonts();
740         int mcf = maxCompFont; /* for MT-safety */
741         Font2D[] regFonts = new Font2D[physFonts.length+mcf];
742         System.arraycopy(compFonts, 0, regFonts, 0, mcf);
743         System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length);
744         return regFonts;
745     }
746 
getPhysicalFonts()747     protected PhysicalFont[] getPhysicalFonts() {
748         return physicalFonts.values().toArray(new PhysicalFont[0]);
749     }
750 
751 
752     /* The class FontRegistrationInfo is used when a client says not
753      * to register a font immediately. This mechanism is used to defer
754      * initialisation of all the components of composite fonts at JRE
755      * start-up. The CompositeFont class is "aware" of this and when it
756      * is first used it asks for the registration of its components.
757      * Also in the event that any physical font is requested the
758      * deferred fonts are initialised before triggering a search of the
759      * system.
760      * Two maps are used. One to track the deferred fonts. The
761      * other to track the fonts that have been initialised through this
762      * mechanism.
763      */
764 
765     private static final class FontRegistrationInfo {
766 
767         String fontFilePath;
768         String[] nativeNames;
769         int fontFormat;
770         boolean javaRasterizer;
771         int fontRank;
772 
FontRegistrationInfo(String fontPath, String[] names, int format, boolean useJavaRasterizer, int rank)773         FontRegistrationInfo(String fontPath, String[] names, int format,
774                              boolean useJavaRasterizer, int rank) {
775             this.fontFilePath = fontPath;
776             this.nativeNames = names;
777             this.fontFormat = format;
778             this.javaRasterizer = useJavaRasterizer;
779             this.fontRank = rank;
780         }
781     }
782 
783     private final ConcurrentHashMap<String, FontRegistrationInfo>
784         deferredFontFiles = new ConcurrentHashMap<>();
785     private final ConcurrentHashMap<String, Font2DHandle>
786         initialisedFonts = new ConcurrentHashMap<>();
787 
788     /* Remind: possibly enhance initialiseDeferredFonts() to be
789      * optionally given a name and a style and it could stop when it
790      * finds that font - but this would be a problem if two of the
791      * fonts reference the same font face name (cf the Solaris
792      * euro fonts).
793      */
initialiseDeferredFonts()794     protected synchronized void initialiseDeferredFonts() {
795         for (String fileName : deferredFontFiles.keySet()) {
796             initialiseDeferredFont(fileName);
797         }
798     }
799 
registerDeferredJREFonts(String jreDir)800     protected synchronized void registerDeferredJREFonts(String jreDir) {
801         for (FontRegistrationInfo info : deferredFontFiles.values()) {
802             if (info.fontFilePath != null &&
803                 info.fontFilePath.startsWith(jreDir)) {
804                 initialiseDeferredFont(info.fontFilePath);
805             }
806         }
807     }
808 
isDeferredFont(String fileName)809     public boolean isDeferredFont(String fileName) {
810         return deferredFontFiles.containsKey(fileName);
811     }
812 
findJREDeferredFont(String name, int style)813     PhysicalFont findJREDeferredFont(String name, int style) {
814 
815         /* Iterate over the deferred font files looking for any in the
816          * jre directory that we didn't recognise, open each of these.
817          * In almost all installations this will quickly fall through
818          * because jreOtherFontFiles will be empty.
819          * noOtherJREFontFiles is used so we can skip this block as soon
820          * as its determined that it's not needed - almost always after the
821          * very first time through.
822          */
823         if (noOtherJREFontFiles) {
824             return null;
825         }
826         synchronized (jreFontDirName) {
827             if (jreOtherFontFiles == null) {
828                 HashSet<String> otherFontFiles = new HashSet<>();
829                 for (String deferredFile : deferredFontFiles.keySet()) {
830                     File file = new File(deferredFile);
831                     String dir = file.getParent();
832                     /* skip names which aren't absolute, aren't in the JRE
833                      * directory, or are known Lucida fonts.
834                      */
835                     if (dir == null || !dir.equals(jreFontDirName)) {
836                         continue;
837                     }
838                     otherFontFiles.add(deferredFile);
839                 }
840                 jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY);
841                 if (jreOtherFontFiles.length == 0) {
842                     noOtherJREFontFiles = true;
843                 }
844             }
845 
846             for (int i=0; i<jreOtherFontFiles.length;i++) {
847                 String fileName = jreOtherFontFiles[i];
848                 if (fileName == null) {
849                     continue;
850                 }
851                 jreOtherFontFiles[i] = null;
852                 PhysicalFont physicalFont = initialiseDeferredFont(fileName);
853                 if (physicalFont != null &&
854                     (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
855                      physicalFont.getFamilyName(null).equalsIgnoreCase(name))
856                     && physicalFont.style == style) {
857                     return physicalFont;
858                 }
859             }
860         }
861 
862         return null;
863     }
864 
findOtherDeferredFont(String name, int style)865     private PhysicalFont findOtherDeferredFont(String name, int style) {
866         for (String fileName : deferredFontFiles.keySet()) {
867             PhysicalFont physicalFont = initialiseDeferredFont(fileName);
868             if (physicalFont != null &&
869                 (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
870                 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) &&
871                 physicalFont.style == style) {
872                 return physicalFont;
873             }
874         }
875         return null;
876     }
877 
findDeferredFont(String name, int style)878     private PhysicalFont findDeferredFont(String name, int style) {
879         PhysicalFont physicalFont = findJREDeferredFont(name, style);
880         if (physicalFont != null) {
881             return physicalFont;
882         } else {
883             return findOtherDeferredFont(name, style);
884         }
885     }
886 
registerDeferredFont(String fileNameKey, String fullPathName, String[] nativeNames, int fontFormat, boolean useJavaRasterizer, int fontRank)887     public void registerDeferredFont(String fileNameKey,
888                                      String fullPathName,
889                                      String[] nativeNames,
890                                      int fontFormat,
891                                      boolean useJavaRasterizer,
892                                      int fontRank) {
893         FontRegistrationInfo regInfo =
894             new FontRegistrationInfo(fullPathName, nativeNames, fontFormat,
895                                      useJavaRasterizer, fontRank);
896         deferredFontFiles.put(fileNameKey, regInfo);
897     }
898 
899 
900     public synchronized
initialiseDeferredFont(String fileNameKey)901          PhysicalFont initialiseDeferredFont(String fileNameKey) {
902 
903         if (fileNameKey == null) {
904             return null;
905         }
906         if (FontUtilities.isLogging()) {
907             FontUtilities.logInfo("Opening deferred font file " + fileNameKey);
908         }
909 
910         PhysicalFont physicalFont = null;
911         FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey);
912         if (regInfo != null) {
913             deferredFontFiles.remove(fileNameKey);
914             physicalFont = registerFontFile(regInfo.fontFilePath,
915                                             regInfo.nativeNames,
916                                             regInfo.fontFormat,
917                                             regInfo.javaRasterizer,
918                                             regInfo.fontRank);
919 
920             if (physicalFont != null) {
921                 /* Store the handle, so that if a font is bad, we
922                  * retrieve the substituted font.
923                  */
924                 initialisedFonts.put(fileNameKey, physicalFont.handle);
925             } else {
926                 initialisedFonts.put(fileNameKey, FONT_HANDLE_NULL);
927             }
928         } else {
929             Font2DHandle handle = initialisedFonts.get(fileNameKey);
930             if (handle == null) {
931                 /* Probably shouldn't happen, but just in case */
932                 initialisedFonts.put(fileNameKey, FONT_HANDLE_NULL);
933             } else {
934                 physicalFont = (PhysicalFont)(handle.font2D);
935             }
936         }
937         return physicalFont;
938     }
939 
isRegisteredFontFile(String name)940     public boolean isRegisteredFontFile(String name) {
941         return registeredFonts.containsKey(name);
942     }
943 
getRegisteredFontFile(String name)944     public PhysicalFont getRegisteredFontFile(String name) {
945         return registeredFonts.get(name);
946     }
947 
948     /* Note that the return value from this method is not always
949      * derived from this file, and may be null. See addToFontList for
950      * some explanation of this.
951      */
registerFontFile(String fileName, String[] nativeNames, int fontFormat, boolean useJavaRasterizer, int fontRank)952     public PhysicalFont registerFontFile(String fileName,
953                                          String[] nativeNames,
954                                          int fontFormat,
955                                          boolean useJavaRasterizer,
956                                          int fontRank) {
957 
958         PhysicalFont regFont = registeredFonts.get(fileName);
959         if (regFont != null) {
960             return regFont;
961         }
962 
963         PhysicalFont physicalFont = null;
964         try {
965             switch (fontFormat) {
966 
967             case FONTFORMAT_TRUETYPE:
968                 int fn = 0;
969                 TrueTypeFont ttf;
970                 do {
971                     ttf = new TrueTypeFont(fileName, nativeNames, fn++,
972                                            useJavaRasterizer);
973                     PhysicalFont pf = addToFontList(ttf, fontRank);
974                     if (physicalFont == null) {
975                         physicalFont = pf;
976                     }
977                 }
978                 while (fn < ttf.getFontCount());
979                 break;
980 
981             case FONTFORMAT_TYPE1:
982                 Type1Font t1f = new Type1Font(fileName, nativeNames);
983                 physicalFont = addToFontList(t1f, fontRank);
984                 break;
985 
986             case FONTFORMAT_NATIVE:
987                 NativeFont nf = new NativeFont(fileName, false);
988                 physicalFont = addToFontList(nf, fontRank);
989                 break;
990             default:
991 
992             }
993             if (FontUtilities.isLogging()) {
994                 FontUtilities.logInfo("Registered file " + fileName + " as font " +
995                                 physicalFont + " rank="  + fontRank);
996             }
997         } catch (FontFormatException ffe) {
998             if (FontUtilities.isLogging()) {
999                 FontUtilities.logInfo("Unusable font: " + fileName + " " + ffe.toString());
1000             }
1001         }
1002         if (physicalFont != null &&
1003             fontFormat != FONTFORMAT_NATIVE) {
1004             registeredFonts.put(fileName, physicalFont);
1005         }
1006         return physicalFont;
1007     }
1008 
registerFonts(String[] fileNames, String[][] nativeNames, int fontCount, int fontFormat, boolean useJavaRasterizer, int fontRank, boolean defer)1009     public void registerFonts(String[] fileNames,
1010                               String[][] nativeNames,
1011                               int fontCount,
1012                               int fontFormat,
1013                               boolean useJavaRasterizer,
1014                               int fontRank, boolean defer) {
1015 
1016         for (int i=0; i < fontCount; i++) {
1017             if (defer) {
1018                 registerDeferredFont(fileNames[i],fileNames[i], nativeNames[i],
1019                                      fontFormat, useJavaRasterizer, fontRank);
1020             } else {
1021                 registerFontFile(fileNames[i], nativeNames[i],
1022                                  fontFormat, useJavaRasterizer, fontRank);
1023             }
1024         }
1025     }
1026 
1027     /*
1028      * This is the Physical font used when some other font on the system
1029      * can't be located. There has to be at least one font or the font
1030      * system is not useful and the graphics environment cannot sustain
1031      * the Java platform.
1032      */
getDefaultPhysicalFont()1033     public PhysicalFont getDefaultPhysicalFont() {
1034         if (defaultPhysicalFont == null) {
1035             String defaultFontName = getDefaultFontFaceName();
1036             // findFont2D will load all fonts
1037             Font2D font2d = findFont2D(defaultFontName, Font.PLAIN, NO_FALLBACK);
1038             if (font2d != null) {
1039                 if (font2d instanceof PhysicalFont) {
1040                     defaultPhysicalFont = (PhysicalFont)font2d;
1041                 } else {
1042                     if (FontUtilities.isLogging()) {
1043                         FontUtilities.logWarning("Font returned by findFont2D for default font name " +
1044                                      defaultFontName + " is not a physical font: " + font2d.getFontName(null));
1045                     }
1046                 }
1047             }
1048             if (defaultPhysicalFont == null) {
1049                 /* Because of the findFont2D call above, if we reach here, we
1050                  * know all fonts have already been loaded, just accept any
1051                  * match at this point. If this fails we are in real trouble
1052                  * and I don't know how to recover from there being absolutely
1053                  * no fonts anywhere on the system.
1054                  */
1055                 defaultPhysicalFont = physicalFonts.values().stream().findFirst()
1056                     .orElseThrow(()->new Error("Probable fatal error: No physical fonts found."));
1057             }
1058         }
1059         return defaultPhysicalFont;
1060     }
1061 
getDefaultLogicalFont(int style)1062     public Font2D getDefaultLogicalFont(int style) {
1063         return findFont2D("dialog", style, NO_FALLBACK);
1064     }
1065 
1066     /*
1067      * return String representation of style prepended with "."
1068      * This is useful for performance to avoid unnecessary string operations.
1069      */
dotStyleStr(int num)1070     private static String dotStyleStr(int num) {
1071         switch(num){
1072           case Font.BOLD:
1073             return ".bold";
1074           case Font.ITALIC:
1075             return ".italic";
1076           case Font.ITALIC | Font.BOLD:
1077             return ".bolditalic";
1078           default:
1079             return ".plain";
1080         }
1081     }
1082 
1083     /* This is implemented only on windows and is called from code that
1084      * executes only on windows. This isn't pretty but its not a precedent
1085      * in this file. This very probably should be cleaned up at some point.
1086      */
1087     protected void
populateFontFileNameMap(HashMap<String,String> fontToFileMap, HashMap<String,String> fontToFamilyNameMap, HashMap<String,ArrayList<String>> familyToFontListMap, Locale locale)1088         populateFontFileNameMap(HashMap<String,String> fontToFileMap,
1089                                 HashMap<String,String> fontToFamilyNameMap,
1090                                 HashMap<String,ArrayList<String>>
1091                                 familyToFontListMap,
1092                                 Locale locale) {
1093     }
1094 
1095     /* Obtained from Platform APIs (windows only)
1096      * Map from lower-case font full name to basename of font file.
1097      * Eg "arial bold" -> ARIALBD.TTF.
1098      * For TTC files, there is a mapping for each font in the file.
1099      */
1100     private HashMap<String,String> fontToFileMap = null;
1101 
1102     /* Obtained from Platform APIs (windows only)
1103      * Map from lower-case font full name to the name of its font family
1104      * Eg "arial bold" -> "Arial"
1105      */
1106     private HashMap<String,String> fontToFamilyNameMap = null;
1107 
1108     /* Obtained from Platform APIs (windows only)
1109      * Map from a lower-case family name to a list of full names of
1110      * the member fonts, eg:
1111      * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"]
1112      */
1113     private HashMap<String,ArrayList<String>> familyToFontListMap= null;
1114 
1115     /* The directories which contain platform fonts */
1116     private String[] pathDirs = null;
1117 
1118     private boolean haveCheckedUnreferencedFontFiles;
1119 
1120     @SuppressWarnings("removal")
getFontFilesFromPath(boolean noType1)1121     private String[] getFontFilesFromPath(boolean noType1) {
1122         final FilenameFilter filter;
1123         if (noType1) {
1124             filter = ttFilter;
1125         } else {
1126             filter = new TTorT1Filter();
1127         }
1128         return AccessController.doPrivileged(new PrivilegedAction<String[]>() {
1129             public String[] run() {
1130                 if (pathDirs.length == 1) {
1131                     File dir = new File(pathDirs[0]);
1132                     String[] files = dir.list(filter);
1133                     if (files == null) {
1134                         return new String[0];
1135                     }
1136                     for (int f=0; f<files.length; f++) {
1137                         files[f] = files[f].toLowerCase();
1138                     }
1139                     return files;
1140                 } else {
1141                     ArrayList<String> fileList = new ArrayList<>();
1142                     for (int i = 0; i< pathDirs.length; i++) {
1143                         File dir = new File(pathDirs[i]);
1144                         String[] files = dir.list(filter);
1145                         if (files == null) {
1146                             continue;
1147                         }
1148                         for (int f = 0; f < files.length ; f++) {
1149                             fileList.add(files[f].toLowerCase());
1150                         }
1151                     }
1152                     return fileList.toArray(STR_ARRAY);
1153                 }
1154             }
1155         });
1156     }
1157 
1158     /* This is needed since some windows registry names don't match
1159      * the font names.
1160      * - UPC styled font names have a double space, but the
1161      * registry entry mapping to a file doesn't.
1162      * - Marlett is in a hidden file not listed in the registry
1163      * - The registry advertises that the file david.ttf contains a
1164      * font with the full name "David Regular" when in fact its
1165      * just "David".
1166      * Directly fix up these known cases as this is faster.
1167      * If a font which doesn't match these known cases has no file,
1168      * it may be a font that has been temporarily added to the known set
1169      * or it may be an installed font with a missing registry entry.
1170      * Installed fonts are those in the windows font directories.
1171      * Make a best effort attempt to locate these.
1172      * We obtain the list of TrueType fonts in these directories and
1173      * filter out all the font files we already know about from the registry.
1174      * What remains may be "bad" fonts, duplicate fonts, or perhaps the
1175      * missing font(s) we are looking for.
1176      * Open each of these files to find out.
1177      */
1178     private void resolveWindowsFonts() {
1179 
1180         ArrayList<String> unmappedFontNames = null;
1181         for (String font : fontToFamilyNameMap.keySet()) {
1182             String file = fontToFileMap.get(font);
1183             if (file == null) {
1184                 if (font.indexOf("  ") > 0) {
1185                     String newName = font.replaceFirst("  ", " ");
1186                     file = fontToFileMap.get(newName);
1187                     /* If this name exists and isn't for a valid name
1188                      * replace the mapping to the file with this font
1189                      */
1190                     if (file != null &&
1191                         !fontToFamilyNameMap.containsKey(newName)) {
1192                         fontToFileMap.remove(newName);
1193                         fontToFileMap.put(font, file);
1194                     }
1195                 } else if (font.equals("marlett")) {
1196                     fontToFileMap.put(font, "marlett.ttf");
1197                 } else if (font.equals("david")) {
1198                     file = fontToFileMap.get("david regular");
1199                     if (file != null) {
1200                         fontToFileMap.remove("david regular");
1201                         fontToFileMap.put("david", file);
1202                     }
1203                 } else {
1204                     if (unmappedFontNames == null) {
1205                         unmappedFontNames = new ArrayList<>();
1206                     }
1207                     unmappedFontNames.add(font);
1208                 }
1209             }
1210         }
1211 
1212         if (unmappedFontNames != null) {
1213             HashSet<String> unmappedFontFiles = new HashSet<>();
1214 
1215             /* Every font key in fontToFileMap ought to correspond to a
1216              * font key in fontToFamilyNameMap. Entries that don't seem
1217              * to correspond are likely fonts that were named differently
1218              * by GDI than in the registry. One known cause of this is when
1219              * Windows has had its regional settings changed so that from
1220              * GDI we get a localised (eg Chinese or Japanese) name for the
1221              * font, but the registry retains the English version of the name
1222              * that corresponded to the "install" locale for windows.
1223              * Since we are in this code block because there are unmapped
1224              * font names, we can look to find unused font->file mappings
1225              * and then open the files to read the names. We don't generally
1226              * want to open font files, as its a performance hit, but this
1227              * occurs only for a small number of fonts on specific system
1228              * configs - ie is believed that a "true" Japanese windows would
1229              * have JA names in the registry too.
1230              * Clone fontToFileMap and remove from the clone all keys which
1231              * match a fontToFamilyNameMap key. What remains maps to the
1232              * files we want to open to find the fonts GDI returned.
1233              * A font in such a file is added to the fontToFileMap after
1234              * checking its one of the unmappedFontNames we are looking for.
1235              * The original name that didn't map is removed from fontToFileMap
1236              * so essentially this "fixes up" fontToFileMap to use the same
1237              * name as GDI.
1238              * Also note that typically the fonts for which this occurs in
1239              * CJK locales are TTC fonts and not all fonts in a TTC may have
1240              * localised names. Eg MSGOTHIC.TTC contains 3 fonts and one of
1241              * them "MS UI Gothic" has no JA name whereas the other two do.
1242              * So not every font in these files is unmapped or new.
1243              */
1244             @SuppressWarnings("unchecked")
1245             HashMap<String,String> ffmapCopy =
1246                 (HashMap<String,String>)(fontToFileMap.clone());
1247             for (String key : fontToFamilyNameMap.keySet()) {
1248                 ffmapCopy.remove(key);
1249             }
1250             for (String key : ffmapCopy.keySet()) {
1251                 unmappedFontFiles.add(ffmapCopy.get(key));
1252                 fontToFileMap.remove(key);
1253             }
1254 
1255             resolveFontFiles(unmappedFontFiles, unmappedFontNames);
1256 
1257             /* If there are still unmapped font names, this means there's
1258              * something that wasn't in the registry. We need to get all
1259              * the font files directly and look at the ones that weren't
1260              * found in the registry.
1261              */
1262             if (unmappedFontNames.size() > 0) {
1263 
1264                 /* getFontFilesFromPath() returns all lower case names.
1265                  * To compare we also need lower case
1266                  * versions of the names from the registry.
1267                  */
1268                 ArrayList<String> registryFiles = new ArrayList<>();
1269 
1270                 for (String regFile : fontToFileMap.values()) {
1271                     registryFiles.add(regFile.toLowerCase());
1272                 }
1273                 /* We don't look for Type1 files here as windows will
1274                  * not enumerate these, so aren't useful in reconciling
1275                  * GDI's unmapped files. We do find these later when
1276                  * we enumerate all fonts.
1277                  */
1278                 for (String pathFile : getFontFilesFromPath(true)) {
1279                     if (!registryFiles.contains(pathFile)) {
1280                         unmappedFontFiles.add(pathFile);
1281                     }
1282                 }
1283 
1284                 resolveFontFiles(unmappedFontFiles, unmappedFontNames);
1285             }
1286 
1287             /* remove from the set of names that will be returned to the
1288              * user any fonts that can't be mapped to files.
1289              */
1290             if (unmappedFontNames.size() > 0) {
1291                 int sz = unmappedFontNames.size();
1292                 for (int i=0; i<sz; i++) {
1293                     String name = unmappedFontNames.get(i);
1294                     String familyName = fontToFamilyNameMap.get(name);
1295                     if (familyName != null) {
1296                         ArrayList<String> family = familyToFontListMap.get(familyName);
1297                         if (family != null) {
1298                             if (family.size() <= 1) {
1299                                 familyToFontListMap.remove(familyName);
1300                             }
1301                         }
1302                     }
1303                     fontToFamilyNameMap.remove(name);
1304                     if (FontUtilities.isLogging()) {
1305                         FontUtilities.logInfo("No file for font:" + name);
1306                     }
1307                 }
1308             }
1309         }
1310     }
1311 
1312     /**
1313      * In some cases windows may have fonts in the fonts folder that
1314      * don't show up in the registry or in the GDI calls to enumerate fonts.
1315      * The only way to find these is to list the directory. We invoke this
1316      * only in getAllFonts/Families, so most searches for a specific
1317      * font that is satisfied by the GDI/registry calls don't take the
1318      * additional hit of listing the directory. This hit is small enough
1319      * that its not significant in these 'enumerate all the fonts' cases.
1320      * The basic approach is to cross-reference the files windows found
1321      * with the ones in the directory listing approach, and for each
1322      * in the latter list that is missing from the former list, register it.
1323      */
1324     private synchronized void checkForUnreferencedFontFiles() {
1325         if (haveCheckedUnreferencedFontFiles) {
1326             return;
1327         }
1328         haveCheckedUnreferencedFontFiles = true;
1329         if (!FontUtilities.isWindows) {
1330             return;
1331         }
1332         /* getFontFilesFromPath() returns all lower case names.
1333          * To compare we also need lower case
1334          * versions of the names from the registry.
1335          */
1336         ArrayList<String> registryFiles = new ArrayList<>();
1337         for (String regFile : fontToFileMap.values()) {
1338             registryFiles.add(regFile.toLowerCase());
1339         }
1340 
1341         /* To avoid any issues with concurrent modification, create
1342          * copies of the existing maps, add the new fonts into these
1343          * and then replace the references to the old ones with the
1344          * new maps. ConcurrentHashmap is another option but its a lot
1345          * more changes and with this exception, these maps are intended
1346          * to be static.
1347          */
1348         HashMap<String,String> fontToFileMap2 = null;
1349         HashMap<String,String> fontToFamilyNameMap2 = null;
1350         HashMap<String,ArrayList<String>> familyToFontListMap2 = null;;
1351 
1352         for (String pathFile : getFontFilesFromPath(false)) {
1353             if (!registryFiles.contains(pathFile)) {
1354                 if (FontUtilities.isLogging()) {
1355                     FontUtilities.logInfo("Found non-registry file : " + pathFile);
1356                 }
1357                 PhysicalFont f = registerFontFile(getPathName(pathFile));
1358                 if (f == null) {
1359                     continue;
1360                 }
1361                 if (fontToFileMap2 == null) {
1362                     fontToFileMap2 = new HashMap<>(fontToFileMap);
1363                     fontToFamilyNameMap2 = new HashMap<>(fontToFamilyNameMap);
1364                     familyToFontListMap2 = new HashMap<>(familyToFontListMap);
1365                 }
1366                 String fontName = f.getFontName(null);
1367                 String family = f.getFamilyName(null);
1368                 String familyLC = family.toLowerCase();
1369                 fontToFamilyNameMap2.put(fontName, family);
1370                 fontToFileMap2.put(fontName, pathFile);
1371                 ArrayList<String> fonts = familyToFontListMap2.get(familyLC);
1372                 if (fonts == null) {
1373                     fonts = new ArrayList<>();
1374                 } else {
1375                     fonts = new ArrayList<>(fonts);
1376                 }
1377                 fonts.add(fontName);
1378                 familyToFontListMap2.put(familyLC, fonts);
1379             }
1380         }
1381         if (fontToFileMap2 != null) {
1382             fontToFileMap = fontToFileMap2;
1383             familyToFontListMap = familyToFontListMap2;
1384             fontToFamilyNameMap = fontToFamilyNameMap2;
1385         }
1386     }
1387 
1388     private void resolveFontFiles(HashSet<String> unmappedFiles,
1389                                   ArrayList<String> unmappedFonts) {
1390 
1391         Locale l = SunToolkit.getStartupLocale();
1392 
1393         for (String file : unmappedFiles) {
1394             try {
1395                 int fn = 0;
1396                 TrueTypeFont ttf;
1397                 String fullPath = getPathName(file);
1398                 if (FontUtilities.isLogging()) {
1399                     FontUtilities.logInfo("Trying to resolve file " + fullPath);
1400                 }
1401                 do {
1402                     ttf = new TrueTypeFont(fullPath, null, fn++, false);
1403                     //  prefer the font's locale name.
1404                     String fontName = ttf.getFontName(l).toLowerCase();
1405                     if (unmappedFonts.contains(fontName)) {
1406                         fontToFileMap.put(fontName, file);
1407                         unmappedFonts.remove(fontName);
1408                         if (FontUtilities.isLogging()) {
1409                             FontUtilities.logInfo("Resolved absent registry entry for " +
1410                                             fontName + " located in " + fullPath);
1411                         }
1412                     }
1413                 }
1414                 while (fn < ttf.getFontCount());
1415             } catch (Exception e) {
1416             }
1417         }
1418     }
1419 
1420     /* Hardwire the English names and expected file names of fonts
1421      * commonly used at start up. Avoiding until later even the small
1422      * cost of calling platform APIs to locate these can help.
1423      * The code that registers these fonts needs to "bail" if any
1424      * of the files do not exist, so it will verify the existence of
1425      * all non-null file names first.
1426      * They are added in to a map with nominally the first
1427      * word in the name of the family as the key. In all the cases
1428      * we are using the family name is a single word, and as is
1429      * more or less required the family name is the initial sequence
1430      * in a full name. So lookup first finds the matching description,
1431      * then registers the whole family, returning the right font.
1432      */
1433     public static class FamilyDescription {
1434         public String familyName;
1435         public String plainFullName;
1436         public String boldFullName;
1437         public String italicFullName;
1438         public String boldItalicFullName;
1439         public String plainFileName;
1440         public String boldFileName;
1441         public String italicFileName;
1442         public String boldItalicFileName;
1443         boolean failed;
1444     }
1445 
1446     static volatile HashMap<String, FamilyDescription> platformFontMap;
1447 
1448     /**
1449      * default implementation does nothing.
1450      */
1451     public HashMap<String, FamilyDescription> populateHardcodedFileNameMap() {
1452         return new HashMap<>(0);
1453     }
1454 
1455     @SuppressWarnings("removal")
1456     Font2D findFontFromPlatformMap(String lcName, int style) {
1457         HashMap<String, FamilyDescription> platformFontMap = SunFontManager.platformFontMap;
1458         if (platformFontMap == null) {
1459             platformFontMap = populateHardcodedFileNameMap();
1460             SunFontManager.platformFontMap = platformFontMap;
1461         }
1462 
1463         if (platformFontMap == null || platformFontMap.size() == 0) {
1464             return null;
1465         }
1466 
1467         int spaceIndex = lcName.indexOf(' ');
1468         String firstWord = lcName;
1469         if (spaceIndex > 0) {
1470             firstWord = lcName.substring(0, spaceIndex);
1471         }
1472 
1473         FamilyDescription fd = platformFontMap.get(firstWord);
1474         if (fd == null || fd.failed) {
1475             return null;
1476         }
1477         /* Once we've established that its at least the first word,
1478          * we need to dig deeper to make sure its a match for either
1479          * a full name, or the family name, to make sure its not
1480          * a request for some other font that just happens to start
1481          * with the same first word.
1482          */
1483         int styleIndex = -1;
1484         if (lcName.equalsIgnoreCase(fd.plainFullName)) {
1485             styleIndex = 0;
1486         } else if (lcName.equalsIgnoreCase(fd.boldFullName)) {
1487             styleIndex = 1;
1488         } else if (lcName.equalsIgnoreCase(fd.italicFullName)) {
1489             styleIndex = 2;
1490         } else if (lcName.equalsIgnoreCase(fd.boldItalicFullName)) {
1491             styleIndex = 3;
1492         }
1493         if (styleIndex == -1 && !lcName.equalsIgnoreCase(fd.familyName)) {
1494             return null;
1495         }
1496 
1497         String plainFile = null, boldFile = null,
1498             italicFile = null, boldItalicFile = null;
1499 
1500         boolean failure = false;
1501         /* In a terminal server config, its possible that getPathName()
1502          * will return null, if the file doesn't exist, hence the null
1503          * checks on return. But in the normal client config we need to
1504          * follow this up with a check to see if all the files really
1505          * exist for the non-null paths.
1506          */
1507          getPlatformFontDirs(noType1Font);
1508 
1509         if (fd.plainFileName != null) {
1510             plainFile = getPathName(fd.plainFileName);
1511             if (plainFile == null) {
1512                 failure = true;
1513             }
1514         }
1515 
1516         if (fd.boldFileName != null) {
1517             boldFile = getPathName(fd.boldFileName);
1518             if (boldFile == null) {
1519                 failure = true;
1520             }
1521         }
1522 
1523         if (fd.italicFileName != null) {
1524             italicFile = getPathName(fd.italicFileName);
1525             if (italicFile == null) {
1526                 failure = true;
1527             }
1528         }
1529 
1530         if (fd.boldItalicFileName != null) {
1531             boldItalicFile = getPathName(fd.boldItalicFileName);
1532             if (boldItalicFile == null) {
1533                 failure = true;
1534             }
1535         }
1536 
1537         if (failure) {
1538             if (FontUtilities.isLogging()) {
1539                 FontUtilities.logInfo("Hardcoded file missing looking for " + lcName);
1540             }
1541             fd.failed = true;
1542             return null;
1543         }
1544 
1545         /* Some of these may be null,as not all styles have to exist */
1546         final String[] files = {
1547             plainFile, boldFile, italicFile, boldItalicFile } ;
1548 
1549         failure = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
1550              public Boolean run() {
1551                  for (int i=0; i<files.length; i++) {
1552                      if (files[i] == null) {
1553                          continue;
1554                      }
1555                      File f = new File(files[i]);
1556                      if (!f.exists()) {
1557                          return Boolean.TRUE;
1558                      }
1559                  }
1560                  return Boolean.FALSE;
1561              }
1562          });
1563 
1564         if (failure) {
1565             if (FontUtilities.isLogging()) {
1566                 FontUtilities.logInfo("Hardcoded file missing looking for " + lcName);
1567             }
1568             fd.failed = true;
1569             return null;
1570         }
1571 
1572         /* If we reach here we know that we have all the files we
1573          * expect, so all should be fine so long as the contents
1574          * are what we'd expect. Now on to registering the fonts.
1575          * Currently this code only looks for TrueType fonts, so format
1576          * and rank can be specified without looking at the filename.
1577          */
1578         Font2D font = null;
1579         for (int f=0;f<files.length;f++) {
1580             if (files[f] == null) {
1581                 continue;
1582             }
1583             PhysicalFont pf =
1584                 registerFontFile(files[f], null,
1585                                  FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK);
1586             if (f == styleIndex) {
1587                 font = pf;
1588             }
1589         }
1590 
1591 
1592         /* Two general cases need a bit more work here.
1593          * 1) If font is null, then it was perhaps a request for a
1594          * non-existent font, such as "Tahoma Italic", or a family name -
1595          * where family and full name of the plain font differ.
1596          * Fall back to finding the closest one in the family.
1597          * This could still fail if a client specified "Segoe" instead of
1598          * "Segoe UI".
1599          * 2) The request is of the form "MyFont Bold", style=Font.ITALIC,
1600          * and so we want to see if there's a Bold Italic font, or
1601          * "MyFamily", style=Font.BOLD, and we may have matched the plain,
1602          * but now need to revise that to the BOLD font.
1603          */
1604         FontFamily fontFamily = FontFamily.getFamily(fd.familyName);
1605         if (fontFamily != null) {
1606             if (font == null) {
1607                 font = fontFamily.getFont(style);
1608                 if (font == null) {
1609                     font = fontFamily.getClosestStyle(style);
1610                 }
1611             } else if (style > 0 && style != font.style) {
1612                 style |= font.style;
1613                 font = fontFamily.getFont(style);
1614                 if (font == null) {
1615                     font = fontFamily.getClosestStyle(style);
1616                 }
1617             }
1618         }
1619 
1620         return font;
1621     }
1622     private synchronized HashMap<String,String> getFullNameToFileMap() {
1623         if (fontToFileMap == null) {
1624 
1625             pathDirs = getPlatformFontDirs(noType1Font);
1626 
1627             fontToFileMap = new HashMap<>(100);
1628             fontToFamilyNameMap = new HashMap<>(100);
1629             familyToFontListMap = new HashMap<>(50);
1630             populateFontFileNameMap(fontToFileMap,
1631                                     fontToFamilyNameMap,
1632                                     familyToFontListMap,
1633                                     Locale.ENGLISH);
1634             if (FontUtilities.isWindows) {
1635                 resolveWindowsFonts();
1636             }
1637             if (FontUtilities.isLogging()) {
1638                 logPlatformFontInfo();
1639             }
1640         }
1641         return fontToFileMap;
1642     }
1643 
1644     private void logPlatformFontInfo() {
1645         PlatformLogger logger = FontUtilities.getLogger();
1646         for (int i=0; i< pathDirs.length;i++) {
1647             logger.info("fontdir="+pathDirs[i]);
1648         }
1649         for (String keyName : fontToFileMap.keySet()) {
1650             logger.info("font="+keyName+" file="+ fontToFileMap.get(keyName));
1651         }
1652         for (String keyName : fontToFamilyNameMap.keySet()) {
1653             logger.info("font="+keyName+" family="+
1654                         fontToFamilyNameMap.get(keyName));
1655         }
1656         for (String keyName : familyToFontListMap.keySet()) {
1657             logger.info("family="+keyName+ " fonts="+
1658                         familyToFontListMap.get(keyName));
1659         }
1660     }
1661 
1662     /* Note this return list excludes logical fonts and JRE fonts */
1663     protected String[] getFontNamesFromPlatform() {
1664         if (getFullNameToFileMap().size() == 0) {
1665             return null;
1666         }
1667         checkForUnreferencedFontFiles();
1668         /* This odd code with TreeMap is used to preserve a historical
1669          * behaviour wrt the sorting order .. */
1670         ArrayList<String> fontNames = new ArrayList<>();
1671         for (ArrayList<String> a : familyToFontListMap.values()) {
1672             for (String s : a) {
1673                 fontNames.add(s);
1674             }
1675         }
1676         return fontNames.toArray(STR_ARRAY);
1677     }
1678 
1679     public boolean gotFontsFromPlatform() {
1680         return getFullNameToFileMap().size() != 0;
1681     }
1682 
1683     public String getFileNameForFontName(String fontName) {
1684         String fontNameLC = fontName.toLowerCase(Locale.ENGLISH);
1685         return fontToFileMap.get(fontNameLC);
1686     }
1687 
1688     private PhysicalFont registerFontFile(String file) {
1689         if (new File(file).isAbsolute() &&
1690             !registeredFonts.containsKey(file)) {
1691             int fontFormat = FONTFORMAT_NONE;
1692             int fontRank = Font2D.UNKNOWN_RANK;
1693             if (ttFilter.accept(null, file)) {
1694                 fontFormat = FONTFORMAT_TRUETYPE;
1695                 fontRank = Font2D.TTF_RANK;
1696             } else if
1697                 (t1Filter.accept(null, file)) {
1698                 fontFormat = FONTFORMAT_TYPE1;
1699                 fontRank = Font2D.TYPE1_RANK;
1700             }
1701             if (fontFormat == FONTFORMAT_NONE) {
1702                 return null;
1703             }
1704             return registerFontFile(file, null, fontFormat, false, fontRank);
1705         }
1706         return null;
1707     }
1708 
1709     /* Used to register any font files that are found by platform APIs
1710      * that weren't previously found in the standard font locations.
1711      * the isAbsolute() check is needed since that's whats stored in the
1712      * set, and on windows, the fonts in the system font directory that
1713      * are in the fontToFileMap are just basenames. We don't want to try
1714      * to register those again, but we do want to register other registry
1715      * installed fonts.
1716      */
1717     protected void registerOtherFontFiles(HashSet<String> registeredFontFiles) {
1718         if (getFullNameToFileMap().size() == 0) {
1719             return;
1720         }
1721         for (String file : fontToFileMap.values()) {
1722             registerFontFile(file);
1723         }
1724     }
1725 
1726     public boolean
1727         getFamilyNamesFromPlatform(TreeMap<String,String> familyNames,
1728                                    Locale requestedLocale) {
1729         if (getFullNameToFileMap().size() == 0) {
1730             return false;
1731         }
1732         checkForUnreferencedFontFiles();
1733         for (String name : fontToFamilyNameMap.values()) {
1734             familyNames.put(name.toLowerCase(requestedLocale), name);
1735         }
1736         return true;
1737     }
1738 
1739     /* Path may be absolute or a base file name relative to one of
1740      * the platform font directories
1741      */
1742     private String getPathName(final String s) {
1743         File f = new File(s);
1744         if (f.isAbsolute()) {
1745             return s;
1746         } else if (pathDirs.length==1) {
1747             return pathDirs[0] + File.separator + s;
1748         } else {
1749             @SuppressWarnings("removal")
1750             String path = AccessController.doPrivileged(
1751                  new PrivilegedAction<String>() {
1752                      public String run() {
1753                          for (int p = 0; p < pathDirs.length; p++) {
1754                              File f = new File(pathDirs[p] +File.separator+ s);
1755                              if (f.exists()) {
1756                                  return f.getAbsolutePath();
1757                              }
1758                          }
1759                          return null;
1760                      }
1761                 });
1762             if (path != null) {
1763                 return path;
1764             }
1765         }
1766         return s; // shouldn't happen, but harmless
1767     }
1768 
1769     /* lcName is required to be lower case for use as a key.
1770      * lcName may be a full name, or a family name, and style may
1771      * be specified in addition to either of these. So be sure to
1772      * get the right one. Since an app *could* ask for "Foo Regular"
1773      * and later ask for "Foo Italic", if we don't register all the
1774      * styles, then logic in findFont2D may try to style the original
1775      * so we register the entire family if we get a match here.
1776      * This is still a big win because this code is invoked where
1777      * otherwise we would register all fonts.
1778      * It's also useful for the case where "Foo Bold" was specified with
1779      * style Font.ITALIC, as we would want in that case to try to return
1780      * "Foo Bold Italic" if it exists, and it is only by locating "Foo Bold"
1781      * and opening it that we really "know" it's Bold, and can look for
1782      * a font that supports that and the italic style.
1783      * The code in here is not overtly windows-specific but in fact it
1784      * is unlikely to be useful as is on other platforms. It is maintained
1785      * in this shared source file to be close to its sole client and
1786      * because so much of the logic is intertwined with the logic in
1787      * findFont2D.
1788      */
1789     private Font2D findFontFromPlatform(String lcName, int style) {
1790         if (getFullNameToFileMap().size() == 0) {
1791             return null;
1792         }
1793 
1794         ArrayList<String> family = null;
1795         String fontFile = null;
1796         String familyName = fontToFamilyNameMap.get(lcName);
1797         if (familyName != null) {
1798             fontFile = fontToFileMap.get(lcName);
1799             family = familyToFontListMap.get
1800                 (familyName.toLowerCase(Locale.ENGLISH));
1801         } else {
1802             family = familyToFontListMap.get(lcName); // is lcName is a family?
1803             if (family != null && family.size() > 0) {
1804                 String lcFontName = family.get(0).toLowerCase(Locale.ENGLISH);
1805                 if (lcFontName != null) {
1806                     familyName = fontToFamilyNameMap.get(lcFontName);
1807                 }
1808             }
1809         }
1810         if (family == null || familyName == null) {
1811             return null;
1812         }
1813         String [] fontList = family.toArray(STR_ARRAY);
1814         if (fontList.length == 0) {
1815             return null;
1816         }
1817 
1818         /* first check that for every font in this family we can find
1819          * a font file. The specific reason for doing this is that
1820          * in at least one case on Windows a font has the face name "David"
1821          * but the registry entry is "David Regular". That is the "unique"
1822          * name of the font but in other cases the registry contains the
1823          * "full" name. See the specifications of name ids 3 and 4 in the
1824          * TrueType 'name' table.
1825          * In general this could cause a problem that we fail to register
1826          * if we all members of a family that we may end up mapping to
1827          * the wrong font member: eg return Bold when Plain is needed.
1828          */
1829         for (int f=0;f<fontList.length;f++) {
1830             String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
1831             String fileName = fontToFileMap.get(fontNameLC);
1832             if (fileName == null) {
1833                 if (FontUtilities.isLogging()) {
1834                     FontUtilities.logInfo("Platform lookup : No file for font " +
1835                                     fontList[f] + " in family " +familyName);
1836                 }
1837                 return null;
1838             }
1839         }
1840 
1841         /* Currently this code only looks for TrueType fonts, so format
1842          * and rank can be specified without looking at the filename.
1843          */
1844         PhysicalFont physicalFont = null;
1845         if (fontFile != null) {
1846             physicalFont = registerFontFile(getPathName(fontFile), null,
1847                                             FONTFORMAT_TRUETYPE, false,
1848                                             Font2D.TTF_RANK);
1849         }
1850         /* Register all fonts in this family. */
1851         for (int f=0;f<fontList.length;f++) {
1852             String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
1853             String fileName = fontToFileMap.get(fontNameLC);
1854             if (fontFile != null && fontFile.equals(fileName)) {
1855                 continue;
1856             }
1857             /* Currently this code only looks for TrueType fonts, so format
1858              * and rank can be specified without looking at the filename.
1859              */
1860             registerFontFile(getPathName(fileName), null,
1861                              FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK);
1862         }
1863 
1864         Font2D font = null;
1865         FontFamily fontFamily = FontFamily.getFamily(familyName);
1866         /* Handle case where request "MyFont Bold", style=Font.ITALIC */
1867         if (physicalFont != null) {
1868             style |= physicalFont.style;
1869         }
1870         if (fontFamily != null) {
1871             font = fontFamily.getFont(style);
1872             if (font == null) {
1873                 font = fontFamily.getClosestStyle(style);
1874             }
1875         }
1876         return font;
1877     }
1878 
1879     private ConcurrentHashMap<String, Font2D> fontNameCache =
1880         new ConcurrentHashMap<>();
1881 
1882     /*
1883      * The client supplies a name and a style.
1884      * The name could be a family name, or a full name.
1885      * A font may exist with the specified style, or it may
1886      * exist only in some other style. For non-native fonts the scaler
1887      * may be able to emulate the required style.
1888      */
1889     public Font2D findFont2D(String name, int style, int fallback) {
1890         if (name == null) return null;
1891         String lowerCaseName = name.toLowerCase(Locale.ENGLISH);
1892         String mapName = lowerCaseName + dotStyleStr(style);
1893 
1894         /* If preferLocaleFonts() or preferProportionalFonts() has been
1895          * called we may be using an alternate set of composite fonts in this
1896          * app context. The presence of a pre-built name map indicates whether
1897          * this is so, and gives access to the alternate composite for the
1898          * name.
1899          */
1900         Font2D font = fontNameCache.get(mapName);
1901         if (font != null) {
1902             return font;
1903         }
1904         if (FontUtilities.isLogging()) {
1905             FontUtilities.logInfo("Search for font: " + name);
1906         }
1907 
1908         // The check below is just so that the bitmap fonts being set by
1909         // AWT and Swing thru the desktop properties do not trigger the
1910         // the load fonts case. The two bitmap fonts are now mapped to
1911         // appropriate equivalents for serif and sansserif.
1912         // Note that the cost of this comparison is only for the first
1913         // call until the map is filled.
1914         if (FontUtilities.isWindows) {
1915             if (lowerCaseName.equals("ms sans serif")) {
1916                 name = "sansserif";
1917             } else if (lowerCaseName.equals("ms serif")) {
1918                 name = "serif";
1919             }
1920         }
1921 
1922         /* This isn't intended to support a client passing in the
1923          * string default, but if a client passes in null for the name
1924          * the java.awt.Font class internally substitutes this name.
1925          * So we need to recognise it here to prevent a loadFonts
1926          * on the unrecognised name. The only potential problem with
1927          * this is it would hide any real font called "default"!
1928          * But that seems like a potential problem we can ignore for now.
1929          */
1930         if (lowerCaseName.equals("default")) {
1931             name = "dialog";
1932         }
1933 
1934         /* First see if its a family name. */
1935         FontFamily family = FontFamily.getFamily(name);
1936         if (family != null) {
1937             font = family.getFontWithExactStyleMatch(style);
1938             if (font == null) {
1939                 font = findDeferredFont(name, style);
1940             }
1941             if (font == null) {
1942                 font = findFontFromPlatform(lowerCaseName, style);
1943             }
1944             if (font == null) {
1945                 font = family.getFont(style);
1946             }
1947             if (font == null) {
1948                 font = family.getClosestStyle(style);
1949             }
1950             if (font != null) {
1951                 fontNameCache.put(mapName, font);
1952                 return font;
1953             }
1954         }
1955 
1956         /* If it wasn't a family name, it should be a full name of
1957          * either a composite, or a physical font
1958          */
1959         font = fullNameToFont.get(lowerCaseName);
1960         if (font != null) {
1961             /* Check that the requested style matches the matched font's style.
1962              * But also match style automatically if the requested style is
1963              * "plain". This because the existing behaviour is that the fonts
1964              * listed via getAllFonts etc always list their style as PLAIN.
1965              * This does lead to non-commutative behaviours where you might
1966              * start with "Lucida Sans Regular" and ask for a BOLD version
1967              * and get "Lucida Sans DemiBold" but if you ask for the PLAIN
1968              * style of "Lucida Sans DemiBold" you get "Lucida Sans DemiBold".
1969              * This consistent however with what happens if you have a bold
1970              * version of a font and no plain version exists - alg. styling
1971              * doesn't "unbolden" the font.
1972              */
1973             if (font.style == style || style == Font.PLAIN) {
1974                 fontNameCache.put(mapName, font);
1975                 return font;
1976             } else {
1977                 /* If it was a full name like "Lucida Sans Regular", but
1978                  * the style requested is "bold", then we want to see if
1979                  * there's the appropriate match against another font in
1980                  * that family before trying to load all fonts, or applying a
1981                  * algorithmic styling
1982                  */
1983                 family = FontFamily.getFamily(font.getFamilyName(null));
1984                 if (family != null) {
1985                     Font2D familyFont = family.getFont(style|font.style);
1986                     /* We exactly matched the requested style, use it! */
1987                     if (familyFont != null) {
1988                         fontNameCache.put(mapName, familyFont);
1989                         return familyFont;
1990                     } else {
1991                         /* This next call is designed to support the case
1992                          * where bold italic is requested, and if we must
1993                          * style, then base it on either bold or italic -
1994                          * not on plain!
1995                          */
1996                         familyFont = family.getClosestStyle(style|font.style);
1997                         if (familyFont != null) {
1998                             /* The next check is perhaps one
1999                              * that shouldn't be done. ie if we get this
2000                              * far we have probably as close a match as we
2001                              * are going to get. We could load all fonts to
2002                              * see if somehow some parts of the family are
2003                              * loaded but not all of it.
2004                              */
2005                             if (familyFont.canDoStyle(style|font.style)) {
2006                                 fontNameCache.put(mapName, familyFont);
2007                                 return familyFont;
2008                             }
2009                         }
2010                     }
2011                 }
2012             }
2013         }
2014 
2015         if (FontUtilities.isWindows) {
2016 
2017             font = findFontFromPlatformMap(lowerCaseName, style);
2018             if (FontUtilities.isLogging()) {
2019                 FontUtilities.logInfo("findFontFromPlatformMap returned " + font);
2020             }
2021 
2022             if (font != null) {
2023                 fontNameCache.put(mapName, font);
2024                 return font;
2025             }
2026             /* Don't want Windows to return a font from C:\Windows\Fonts
2027              * if someone has installed a font with the same name
2028              * in the JRE.
2029              */
2030             if (deferredFontFiles.size() > 0) {
2031                 font = findJREDeferredFont(lowerCaseName, style);
2032                 if (font != null) {
2033                     fontNameCache.put(mapName, font);
2034                     return font;
2035                 }
2036             }
2037             font = findFontFromPlatform(lowerCaseName, style);
2038             if (font != null) {
2039                 if (FontUtilities.isLogging()) {
2040                     FontUtilities.logInfo("Found font via platform API for request:\"" +
2041                                     name + "\":, style="+style+
2042                                     " found font: " + font);
2043                 }
2044                 fontNameCache.put(mapName, font);
2045                 return font;
2046             }
2047         }
2048 
2049         /* If reach here and no match has been located, then if there are
2050          * uninitialised deferred fonts, load as many of those as needed
2051          * to find the deferred font. If none is found through that
2052          * search continue on.
2053          * There is possibly a minor issue when more than one
2054          * deferred font implements the same font face. Since deferred
2055          * fonts are only those in font configuration files, this is a
2056          * controlled situation, the known case being Solaris euro_fonts
2057          * versions of Arial, Times New Roman, Courier New. However
2058          * the larger font will transparently replace the smaller one
2059          *  - see addToFontList() - when it is needed by the composite font.
2060          */
2061         if (deferredFontFiles.size() > 0) {
2062             font = findDeferredFont(name, style);
2063             if (font != null) {
2064                 fontNameCache.put(mapName, font);
2065                 return font;
2066             }
2067         }
2068 
2069         /* We check for application registered fonts before
2070          * explicitly loading all fonts as if necessary the registration
2071          * code will have done so anyway. And we don't want to needlessly
2072          * load the actual files for all fonts.
2073          * Just as for installed fonts we check for family before fullname.
2074          * We do not add these fonts to fontNameCache for the
2075          * app context case which eliminates the overhead of a per context
2076          * cache for these.
2077          */
2078 
2079         if (fontsAreRegistered) {
2080             Hashtable<String, FontFamily> familyTable = createdByFamilyName;
2081             Hashtable<String, Font2D> nameTable = createdByFullName;
2082 
2083             family = familyTable.get(lowerCaseName);
2084             if (family != null) {
2085                 font = family.getFontWithExactStyleMatch(style);
2086                 if (font == null) {
2087                     font = family.getFont(style);
2088                 }
2089                 if (font == null) {
2090                     font = family.getClosestStyle(style);
2091                 }
2092                 if (font != null) {
2093                     if (fontsAreRegistered) {
2094                         fontNameCache.put(mapName, font);
2095                     }
2096                     return font;
2097                 }
2098             }
2099             font = nameTable.get(lowerCaseName);
2100             if (font != null) {
2101                 if (fontsAreRegistered) {
2102                     fontNameCache.put(mapName, font);
2103                 }
2104                 return font;
2105             }
2106         }
2107 
2108         /* If reach here and no match has been located, then if all fonts
2109          * are not yet loaded, do so, and then recurse.
2110          */
2111         if (!loadedAllFonts) {
2112             if (FontUtilities.isLogging()) {
2113                 FontUtilities.logInfo("Load fonts looking for:" + name);
2114             }
2115             loadFonts();
2116             loadedAllFonts = true;
2117             return findFont2D(name, style, fallback);
2118         }
2119 
2120         if (!loadedAllFontFiles) {
2121             if (FontUtilities.isLogging()) {
2122                 FontUtilities.logInfo("Load font files looking for:" + name);
2123             }
2124             loadFontFiles();
2125             loadedAllFontFiles = true;
2126             return findFont2D(name, style, fallback);
2127         }
2128 
2129         /* The primary name is the locale default - ie not US/English but
2130          * whatever is the default in this locale. This is the way it always
2131          * has been but may be surprising to some developers if "Arial Regular"
2132          * were hard-coded in their app and yet "Arial Regular" was not the
2133          * default name. Fortunately for them, as a consequence of the JDK
2134          * supporting returning names and family names for arbitrary locales,
2135          * we also need to support searching all localised names for a match.
2136          * But because this case of the name used to reference a font is not
2137          * the same as the default for this locale is rare, it makes sense to
2138          * search a much shorter list of default locale names and only go to
2139          * a longer list of names in the event that no match was found.
2140          * So add here code which searches localised names too.
2141          * As in 1.4.x this happens only after loading all fonts, which
2142          * is probably the right order.
2143          */
2144         if ((font = findFont2DAllLocales(name, style)) != null) {
2145             fontNameCache.put(mapName, font);
2146             return font;
2147         }
2148 
2149         /* Perhaps its a "compatibility" name - timesroman, helvetica,
2150          * or courier, which 1.0 apps used for logical fonts.
2151          * We look for these "late" after a loadFonts as we must not
2152          * hide real fonts of these names.
2153          * Map these appropriately:
2154          * On windows this means according to the rules specified by the
2155          * FontConfiguration : do it only for encoding==Cp1252
2156          *
2157          * REMIND: this is something we plan to remove.
2158          */
2159         if (FontUtilities.isWindows) {
2160             String compatName =
2161                 getFontConfiguration().getFallbackFamilyName(name, null);
2162             if (compatName != null) {
2163                 font = findFont2D(compatName, style, fallback);
2164                 fontNameCache.put(mapName, font);
2165                 return font;
2166             }
2167         } else if (lowerCaseName.equals("timesroman")) {
2168             font = findFont2D("serif", style, fallback);
2169             fontNameCache.put(mapName, font);
2170             return font;
2171         } else if (lowerCaseName.equals("helvetica")) {
2172             font = findFont2D("sansserif", style, fallback);
2173             fontNameCache.put(mapName, font);
2174             return font;
2175         } else if (lowerCaseName.equals("courier")) {
2176             font = findFont2D("monospaced", style, fallback);
2177             fontNameCache.put(mapName, font);
2178             return font;
2179         }
2180         if (FontUtilities.isLogging()) {
2181             FontUtilities.logInfo("No font found for:" + name);
2182         }
2183 
2184         switch (fallback) {
2185         case PHYSICAL_FALLBACK: return getDefaultPhysicalFont();
2186         case LOGICAL_FALLBACK: return getDefaultLogicalFont(style);
2187         default: return null;
2188         }
2189     }
2190 
2191     /*
2192      * Workaround for apps which are dependent on a font metrics bug
2193      * in JDK 1.1. This is an unsupported win32 private setting.
2194      * Left in for a customer - do not remove.
2195      */
2196     public boolean usePlatformFontMetrics() {
2197         return usePlatformFontMetrics;
2198     }
2199 
2200     public int getNumFonts() {
2201         return physicalFonts.size()+maxCompFont;
2202     }
2203 
2204     private static boolean fontSupportsEncoding(Font font, String encoding) {
2205         return FontUtilities.getFont2D(font).supportsEncoding(encoding);
2206     }
2207 
2208     protected abstract String getFontPath(boolean noType1Fonts);
2209 
2210     Thread fileCloser = null;
2211     Vector<File> tmpFontFiles = null;
2212 
2213     private int createdFontCount = 0;
2214 
2215     @SuppressWarnings("removal")
2216     public Font2D[] createFont2D(File fontFile, int fontFormat, boolean all,
2217                                  boolean isCopy, CreatedFontTracker tracker)
2218     throws FontFormatException {
2219 
2220         List<Font2D> fList = new ArrayList<>();
2221         int cnt = 1;
2222         String fontFilePath = fontFile.getPath();
2223         FileFont font2D = null;
2224         final File fFile = fontFile;
2225         final CreatedFontTracker _tracker = tracker;
2226         boolean weakRefs = false;
2227         int maxStrikes = 0;
2228         synchronized (this) {
2229             if (createdFontCount < maxSoftRefCnt) {
2230                 createdFontCount++;
2231             } else {
2232                   weakRefs = true;
2233                       maxStrikes = 10;
2234             }
2235         }
2236         try {
2237             switch (fontFormat) {
2238             case Font.TRUETYPE_FONT:
2239                 font2D = new TrueTypeFont(fontFilePath, null, 0, true);
2240                 font2D.setUseWeakRefs(weakRefs, maxStrikes);
2241                 fList.add(font2D);
2242                 if (!all) {
2243                     break;
2244                 }
2245                 cnt = ((TrueTypeFont)font2D).getFontCount();
2246                 int index = 1;
2247                 while (index < cnt) {
2248                     font2D = new TrueTypeFont(fontFilePath, null, index++, true);
2249                     font2D.setUseWeakRefs(weakRefs, maxStrikes);
2250                     fList.add(font2D);
2251                 }
2252                 break;
2253             case Font.TYPE1_FONT:
2254                 font2D = new Type1Font(fontFilePath, null, isCopy);
2255                 font2D.setUseWeakRefs(weakRefs, maxStrikes);
2256                 fList.add(font2D);
2257                 break;
2258             default:
2259                 throw new FontFormatException("Unrecognised Font Format");
2260             }
2261         } catch (FontFormatException e) {
2262             if (isCopy) {
2263                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
2264                     public Void run() {
2265                         if (_tracker != null) {
2266                             _tracker.subBytes((int)fFile.length());
2267                         }
2268                         fFile.delete();
2269                         return null;
2270                     }
2271                 });
2272             }
2273             throw(e);
2274         }
2275         if (isCopy) {
2276             FileFont.setFileToRemove(fList, fontFile, cnt, tracker);
2277             synchronized (FontManager.class) {
2278 
2279                 if (tmpFontFiles == null) {
2280                     tmpFontFiles = new Vector<File>();
2281                 }
2282                 tmpFontFiles.add(fontFile);
2283 
2284                 if (fileCloser == null) {
2285                     final Runnable fileCloserRunnable = new Runnable() {
2286                         public void run() {
2287                             AccessController.doPrivileged(new PrivilegedAction<Void>() {
2288                                 public Void run() {
2289                                     for (int i = 0;i < CHANNELPOOLSIZE; i++) {
2290                                         if (fontFileCache[i] != null) {
2291                                             try {
2292                                                 fontFileCache[i].close();
2293                                             } catch (Exception e) {
2294                                             }
2295                                         }
2296                                     }
2297                                     if (tmpFontFiles != null) {
2298                                         File[] files = new File[tmpFontFiles.size()];
2299                                         files = tmpFontFiles.toArray(files);
2300                                         for (int f=0; f<files.length;f++) {
2301                                             try {
2302                                                 files[f].delete();
2303                                             } catch (Exception e) {
2304                                             }
2305                                         }
2306                                     }
2307                                     return null;
2308                                 }
2309                             });
2310                         }
2311                     };
2312                     AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
2313                         ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup();
2314                         fileCloser = new Thread(rootTG, fileCloserRunnable,
2315                                                 "FileCloser", 0, false);
2316                         fileCloser.setContextClassLoader(null);
2317                         Runtime.getRuntime().addShutdownHook(fileCloser);
2318                         return null;
2319                     });
2320                 }
2321             }
2322         }
2323         return fList.toArray(new Font2D[0]);
2324     }
2325 
2326     /* remind: used in X11GraphicsEnvironment and called often enough
2327      * that we ought to obsolete this code
2328      */
2329     public synchronized String getFullNameByFileName(String fileName) {
2330         PhysicalFont[] physFonts = getPhysicalFonts();
2331         for (int i=0;i<physFonts.length;i++) {
2332             if (physFonts[i].platName.equals(fileName)) {
2333                 return (physFonts[i].getFontName(null));
2334             }
2335         }
2336         return null;
2337     }
2338 
2339     /*
2340      * This is called when font is determined to be invalid/bad.
2341      * It designed to be called (for example) by the font scaler
2342      * when in processing a font file it is discovered to be incorrect.
2343      * This is different than the case where fonts are discovered to
2344      * be incorrect during initial verification, as such fonts are
2345      * never registered.
2346      * Handles to this font held are re-directed to a default font.
2347      * This default may not be an ideal substitute buts it better than
2348      * crashing This code assumes a PhysicalFont parameter as it doesn't
2349      * make sense for a Composite to be "bad".
2350      */
2351     public synchronized void deRegisterBadFont(Font2D font2D) {
2352         if (!(font2D instanceof PhysicalFont)) {
2353             /* We should never reach here, but just in case */
2354             return;
2355         } else {
2356             if (FontUtilities.isLogging()) {
2357                 FontUtilities.logSevere("Deregister bad font: " + font2D);
2358             }
2359             replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont());
2360         }
2361     }
2362 
2363     /*
2364      * This encapsulates all the work that needs to be done when a
2365      * Font2D is replaced by a different Font2D.
2366      */
2367     public synchronized void replaceFont(PhysicalFont oldFont,
2368                                          PhysicalFont newFont) {
2369 
2370         if (oldFont.handle.font2D != oldFont) {
2371             /* already done */
2372             return;
2373         }
2374 
2375         /* If we try to replace the font with itself, that won't work,
2376          * so pick any alternative physical font
2377          */
2378         if (oldFont == newFont) {
2379             if (FontUtilities.isLogging()) {
2380                 FontUtilities.logSevere("Can't replace bad font with itself " + oldFont);
2381             }
2382             PhysicalFont[] physFonts = getPhysicalFonts();
2383             for (int i=0; i<physFonts.length;i++) {
2384                 if (physFonts[i] != newFont) {
2385                     newFont = physFonts[i];
2386                     break;
2387                 }
2388             }
2389             if (oldFont == newFont) {
2390                 if (FontUtilities.isLogging()) {
2391                     FontUtilities.logSevere("This is bad. No good physicalFonts found.");
2392                 }
2393                 return;
2394             }
2395         }
2396 
2397         /* eliminate references to this font, so it won't be located
2398          * by future callers, and will be eligible for GC when all
2399          * references are removed
2400          */
2401         oldFont.handle.font2D = newFont;
2402         physicalFonts.remove(oldFont.fullName);
2403         fullNameToFont.remove(oldFont.fullName.toLowerCase(Locale.ENGLISH));
2404         FontFamily.remove(oldFont);
2405         if (localeFullNamesToFont != null) {
2406             Map.Entry<?, ?>[] mapEntries = localeFullNamesToFont.entrySet().
2407                 toArray(new Map.Entry<?, ?>[0]);
2408             /* Should I be replacing these, or just I just remove
2409              * the names from the map?
2410              */
2411             for (int i=0; i<mapEntries.length;i++) {
2412                 if (mapEntries[i].getValue() == oldFont) {
2413                     try {
2414                         @SuppressWarnings("unchecked")
2415                         Map.Entry<String, PhysicalFont> tmp = (Map.Entry<String, PhysicalFont>)mapEntries[i];
2416                         tmp.setValue(newFont);
2417                     } catch (Exception e) {
2418                         /* some maps don't support this operation.
2419                          * In this case just give up and remove the entry.
2420                          */
2421                         localeFullNamesToFont.remove(mapEntries[i].getKey());
2422                     }
2423                 }
2424             }
2425         }
2426 
2427         for (int i=0; i<maxCompFont; i++) {
2428             /* Deferred initialization of composites shouldn't be
2429              * a problem for this case, since a font must have been
2430              * initialised to be discovered to be bad.
2431              * Some JRE composites on Solaris use two versions of the same
2432              * font. The replaced font isn't bad, just "smaller" so there's
2433              * no need to make the slot point to the new font.
2434              * Since composites have a direct reference to the Font2D (not
2435              * via a handle) making this substitution is not safe and could
2436              * cause an additional problem and so this substitution is
2437              * warranted only when a font is truly "bad" and could cause
2438              * a crash. So we now replace it only if its being substituted
2439              * with some font other than a fontconfig rank font
2440              * Since in practice a substitution will have the same rank
2441              * this may never happen, but the code is safer even if its
2442              * also now a no-op.
2443              * The only obvious "glitch" from this stems from the current
2444              * implementation that when asked for the number of glyphs in a
2445              * composite it lies and returns the number in slot 0 because
2446              * composite glyphs aren't contiguous. Since we live with that
2447              * we can live with the glitch that depending on how it was
2448              * initialised a composite may return different values for this.
2449              * Fixing the issues with composite glyph ids is tricky as
2450              * there are exclusion ranges and unlike other fonts even the
2451              * true "numGlyphs" isn't a contiguous range. Likely the only
2452              * solution is an API that returns an array of glyph ranges
2453              * which takes precedence over the existing API. That might
2454              * also need to address excluding ranges which represent a
2455              * code point supported by an earlier component.
2456              */
2457             if (newFont.getRank() > Font2D.FONT_CONFIG_RANK) {
2458                 compFonts[i].replaceComponentFont(oldFont, newFont);
2459             }
2460         }
2461     }
2462 
2463     private synchronized void loadLocaleNames() {
2464         if (localeFullNamesToFont != null) {
2465             return;
2466         }
2467         localeFullNamesToFont = new HashMap<>();
2468         Font2D[] fonts = getRegisteredFonts();
2469         for (int i=0; i<fonts.length; i++) {
2470             if (fonts[i] instanceof TrueTypeFont) {
2471                 TrueTypeFont ttf = (TrueTypeFont)fonts[i];
2472                 String[] fullNames = ttf.getAllFullNames();
2473                 for (int n=0; n<fullNames.length; n++) {
2474                     localeFullNamesToFont.put(fullNames[n], ttf);
2475                 }
2476                 FontFamily family = FontFamily.getFamily(ttf.familyName);
2477                 if (family != null) {
2478                     FontFamily.addLocaleNames(family, ttf.getAllFamilyNames());
2479                 }
2480             }
2481         }
2482     }
2483 
2484     /* This replicate the core logic of findFont2D but operates on
2485      * all the locale names. This hasn't been merged into findFont2D to
2486      * keep the logic simpler and reduce overhead, since this case is
2487      * almost never used. The main case in which it is called is when
2488      * a bogus font name is used and we need to check all possible names
2489      * before returning the default case.
2490      */
2491     private Font2D findFont2DAllLocales(String name, int style) {
2492         if (FontUtilities.isLogging()) {
2493             FontUtilities.logInfo("Searching localised font names for:" + name);
2494         }
2495 
2496         /* If reach here and no match has been located, then if we have
2497          * not yet built the map of localeFullNamesToFont for TT fonts, do so
2498          * now. This method must be called after all fonts have been loaded.
2499          */
2500         if (localeFullNamesToFont == null) {
2501             loadLocaleNames();
2502         }
2503         String lowerCaseName = name.toLowerCase();
2504         Font2D font = null;
2505 
2506         /* First see if its a family name. */
2507         FontFamily family = FontFamily.getLocaleFamily(lowerCaseName);
2508         if (family != null) {
2509           font = family.getFont(style);
2510           if (font == null) {
2511             font = family.getClosestStyle(style);
2512           }
2513           if (font != null) {
2514               return font;
2515           }
2516         }
2517 
2518         /* If it wasn't a family name, it should be a full name. */
2519         synchronized (this) {
2520             font = localeFullNamesToFont.get(name);
2521         }
2522         if (font != null) {
2523             if (font.style == style || style == Font.PLAIN) {
2524                 return font;
2525             } else {
2526                 family = FontFamily.getFamily(font.getFamilyName(null));
2527                 if (family != null) {
2528                     Font2D familyFont = family.getFont(style);
2529                     /* We exactly matched the requested style, use it! */
2530                     if (familyFont != null) {
2531                         return familyFont;
2532                     } else {
2533                         familyFont = family.getClosestStyle(style);
2534                         if (familyFont != null) {
2535                             /* The next check is perhaps one
2536                              * that shouldn't be done. ie if we get this
2537                              * far we have probably as close a match as we
2538                              * are going to get. We could load all fonts to
2539                              * see if somehow some parts of the family are
2540                              * loaded but not all of it.
2541                              * This check is commented out for now.
2542                              */
2543                             if (!familyFont.canDoStyle(style)) {
2544                                 familyFont = null;
2545                             }
2546                             return familyFont;
2547                         }
2548                     }
2549                 }
2550             }
2551         }
2552         return font;
2553     }
2554 
2555     /* Supporting "alternate" composite fonts on 2D graphics objects
2556      * is accessed by the application by calling methods on the local
2557      * GraphicsEnvironment. The overall implementation is described
2558      * in one place, here, since otherwise the implementation is spread
2559      * around it may be difficult to track.
2560      * The methods below call into SunGraphicsEnvironment which creates a
2561      * new FontConfiguration instance. The FontConfiguration class,
2562      * and its platform sub-classes are updated to take parameters requesting
2563      * these behaviours. This is then used to create new composite font
2564      * instances. Since this calls the initCompositeFont method in
2565      * SunGraphicsEnvironment it performs the same initialization as is
2566      * performed normally. There may be some duplication of effort, but
2567      * that code is already written to be able to perform properly if called
2568      * to duplicate work. The main difference is that if we detect we are
2569      * running in an applet/browser/Java plugin environment these new fonts
2570      * are not placed in the "default" maps but into an AppContext instance.
2571      * The font lookup mechanism in java.awt.Font.getFont2D() is also updated
2572      * so that look-up for composite fonts will in that case always
2573      * do a lookup rather than returning a cached result.
2574      * This is inefficient but necessary else singleton java.awt.Font
2575      * instances would not retrieve the correct Font2D for the appcontext.
2576      * sun.font.FontManager.findFont2D is also updated to that it uses
2577      * a name map cache specific to that appcontext.
2578      *
2579      * Getting an AppContext is expensive, so there is a global variable
2580      * that records whether these methods have ever been called and can
2581      * avoid the expense for almost all applications. Once the correct
2582      * CompositeFont is associated with the Font, everything should work
2583      * through existing mechanisms.
2584      * A special case is that GraphicsEnvironment.getAllFonts() must
2585      * return an AppContext specific list.
2586      *
2587      * Calling the methods below is "heavyweight" but it is expected that
2588      * these methods will be called very rarely.
2589      *
2590      * If _usingAlternateComposites is true, we are not in an "applet"
2591      * environment and the (single) application has selected
2592      * an alternate composite font behaviour.
2593      *
2594      * - Printing: The implementation delegates logical fonts to an AWT
2595      * mechanism which cannot use these alternate configurations.
2596      * We can detect that alternate fonts are in use and back-off to 2D, but
2597      * that uses outlines. Much of this can be fixed with additional work
2598      * but that may have to wait. The results should be correct, just not
2599      * optimal.
2600      */
2601     private boolean _usingAlternateComposites = false;
2602 
2603     private static boolean gAltJAFont = false;
2604     private boolean gLocalePref = false;
2605     private boolean gPropPref = false;
2606 
2607     /* Its used by the FontMetrics caching code which in such
2608      * a case cannot retrieve a cached metrics solely on the basis of
2609      * the Font.equals() method since it needs to also check if the Font2D
2610      * is the same.
2611      * We also use non-standard composites for Swing native L&F fonts on
2612      * Windows. In that case the policy is that the metrics reported are
2613      * based solely on the physical font in the first slot which is the
2614      * visible java.awt.Font. So in that case the metrics cache which tests
2615      * the Font does what we want. In the near future when we expand the GTK
2616      * logical font definitions we may need to revisit this if GTK reports
2617      * combined metrics instead. For now though this test can be simple.
2618      */
2619     public boolean usingAlternateCompositeFonts() {
2620         return _usingAlternateComposites;
2621     }
2622 
2623     /* Modifies the behaviour of a subsequent call to preferLocaleFonts()
2624      * to use Mincho instead of Gothic for dialoginput in JA locales
2625      * on windows. Not needed on other platforms.
2626      */
2627     public synchronized void useAlternateFontforJALocales() {
2628         if (FontUtilities.isLogging()) {
2629             FontUtilities.logInfo("Entered useAlternateFontforJALocales().");
2630         }
2631 
2632         if (!FontUtilities.isWindows) {
2633             return;
2634         }
2635         gAltJAFont = true;
2636     }
2637 
2638     public boolean usingAlternateFontforJALocales() {
2639         return gAltJAFont;
2640     }
2641 
2642     public synchronized void preferLocaleFonts() {
2643         if (FontUtilities.isLogging()) {
2644             FontUtilities.logInfo("Entered preferLocaleFonts().");
2645         }
2646 
2647         /* Test if re-ordering will have any effect */
2648         if (!FontConfiguration.willReorderForStartupLocale()) {
2649             return;
2650         }
2651         if (gLocalePref == true) {
2652             return;
2653         }
2654         gLocalePref = true;
2655         createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
2656         _usingAlternateComposites = true;
2657     }
2658 
2659     public synchronized void preferProportionalFonts() {
2660         if (FontUtilities.isLogging()) {
2661             FontUtilities.logInfo("Entered preferProportionalFonts().");
2662         }
2663 
2664         /* If no proportional fonts are configured, there's no need
2665          * to take any action.
2666          */
2667         if (!FontConfiguration.hasMonoToPropMap()) {
2668             return;
2669         }
2670         if (gPropPref == true) {
2671             return;
2672         }
2673         gPropPref = true;
2674         createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
2675         _usingAlternateComposites = true;
2676     }
2677 
2678     private static HashSet<String> installedNames = null;
2679     private static HashSet<String> getInstalledNames() {
2680         if (installedNames == null) {
2681            Locale l = getSystemStartupLocale();
2682            SunFontManager fontManager = SunFontManager.getInstance();
2683            String[] installedFamilies =
2684                fontManager.getInstalledFontFamilyNames(l);
2685            Font[] installedFonts = fontManager.getAllInstalledFonts();
2686            HashSet<String> names = new HashSet<>();
2687            for (int i=0; i<installedFamilies.length; i++) {
2688                names.add(installedFamilies[i].toLowerCase(l));
2689            }
2690            for (int i=0; i<installedFonts.length; i++) {
2691                names.add(installedFonts[i].getFontName(l).toLowerCase(l));
2692            }
2693            installedNames = names;
2694         }
2695         return installedNames;
2696     }
2697 
2698     private static final Object regFamilyLock  = new Object();
2699     private Hashtable<String,FontFamily> createdByFamilyName;
2700     private Hashtable<String,Font2D>     createdByFullName;
2701     private boolean fontsAreRegistered = false;
2702 
2703     public boolean registerFont(Font font) {
2704         /* This method should not be called with "null".
2705          * It is the caller's responsibility to ensure that.
2706          */
2707         if (font == null) {
2708             return false;
2709         }
2710 
2711         /* Initialise these objects only once we start to use this API */
2712         synchronized (regFamilyLock) {
2713             if (createdByFamilyName == null) {
2714                 createdByFamilyName = new Hashtable<String,FontFamily>();
2715                 createdByFullName = new Hashtable<String,Font2D>();
2716             }
2717         }
2718 
2719         if (! FontAccess.getFontAccess().isCreatedFont(font)) {
2720             return false;
2721         }
2722         /* We want to ensure that this font cannot override existing
2723          * installed fonts. Check these conditions :
2724          * - family name is not that of an installed font
2725          * - full name is not that of an installed font
2726          * - family name is not the same as the full name of an installed font
2727          * - full name is not the same as the family name of an installed font
2728          * The last two of these may initially look odd but the reason is
2729          * that (unfortunately) Font constructors do not distinuguish these.
2730          * An extreme example of such a problem would be a font which has
2731          * family name "Dialog.Plain" and full name of "Dialog".
2732          * The one arguably overly stringent restriction here is that if an
2733          * application wants to supply a new member of an existing family
2734          * It will get rejected. But since the JRE can perform synthetic
2735          * styling in many cases its not necessary.
2736          * We don't apply the same logic to registered fonts. If apps want
2737          * to do this lets assume they have a reason. It won't cause problems
2738          * except for themselves.
2739          */
2740         HashSet<String> names = getInstalledNames();
2741         Locale l = getSystemStartupLocale();
2742         String familyName = font.getFamily(l).toLowerCase();
2743         String fullName = font.getFontName(l).toLowerCase();
2744         if (names.contains(familyName) || names.contains(fullName)) {
2745             return false;
2746         }
2747 
2748         /* Checks passed, now register the font */
2749         Hashtable<String, FontFamily> familyTable = createdByFamilyName;
2750         Hashtable<String, Font2D> fullNameTable = createdByFullName;
2751         fontsAreRegistered = true;
2752 
2753         /* Create the FontFamily and add font to the tables */
2754         Font2D font2D = FontUtilities.getFont2D(font);
2755         int style = font2D.getStyle();
2756         FontFamily family = familyTable.get(familyName);
2757         if (family == null) {
2758             family = new FontFamily(font.getFamily(l));
2759             familyTable.put(familyName, family);
2760         }
2761         /* Remove name cache entries if not using app contexts.
2762          * To accommodate a case where code may have registered first a plain
2763          * family member and then used it and is now registering a bold family
2764          * member, we need to remove all members of the family, so that the
2765          * new style can get picked up rather than continuing to synthesise.
2766          */
2767         if (fontsAreRegistered) {
2768             removeFromCache(family.getFont(Font.PLAIN));
2769             removeFromCache(family.getFont(Font.BOLD));
2770             removeFromCache(family.getFont(Font.ITALIC));
2771             removeFromCache(family.getFont(Font.BOLD|Font.ITALIC));
2772             removeFromCache(fullNameTable.get(fullName));
2773         }
2774         family.setFont(font2D, style);
2775         fullNameTable.put(fullName, font2D);
2776         return true;
2777     }
2778 
2779     /* Remove from the name cache all references to the Font2D */
2780     private void removeFromCache(Font2D font) {
2781         if (font == null) {
2782             return;
2783         }
2784         String[] keys = fontNameCache.keySet().toArray(STR_ARRAY);
2785         for (int k=0; k<keys.length;k++) {
2786             if (fontNameCache.get(keys[k]) == font) {
2787                 fontNameCache.remove(keys[k]);
2788             }
2789         }
2790     }
2791 
2792     // It may look odd to use TreeMap but its more convenient to the caller.
2793     public TreeMap<String, String> getCreatedFontFamilyNames() {
2794 
2795         Hashtable<String,FontFamily> familyTable;
2796         if (fontsAreRegistered) {
2797             familyTable = createdByFamilyName;
2798         } else {
2799             return null;
2800         }
2801 
2802         Locale l = getSystemStartupLocale();
2803         synchronized (familyTable) {
2804             TreeMap<String, String> map = new TreeMap<String, String>();
2805             for (FontFamily f : familyTable.values()) {
2806                 Font2D font2D = f.getFont(Font.PLAIN);
2807                 if (font2D == null) {
2808                     font2D = f.getClosestStyle(Font.PLAIN);
2809                 }
2810                 String name = font2D.getFamilyName(l);
2811                 map.put(name.toLowerCase(l), name);
2812             }
2813             return map;
2814         }
2815     }
2816 
2817     public Font[] getCreatedFonts() {
2818 
2819         Hashtable<String,Font2D> nameTable;
2820         if (fontsAreRegistered) {
2821             nameTable = createdByFullName;
2822         } else {
2823             return null;
2824         }
2825 
2826         Locale l = getSystemStartupLocale();
2827         synchronized (nameTable) {
2828             Font[] fonts = new Font[nameTable.size()];
2829             int i=0;
2830             for (Font2D font2D : nameTable.values()) {
2831                 fonts[i++] = new Font(font2D.getFontName(l), Font.PLAIN, 1);
2832             }
2833             return fonts;
2834         }
2835     }
2836 
2837 
2838     protected String[] getPlatformFontDirs(boolean noType1Fonts) {
2839 
2840         /* First check if we already initialised path dirs */
2841         if (pathDirs != null) {
2842             return pathDirs;
2843         }
2844 
2845         String path = getPlatformFontPath(noType1Fonts);
2846         StringTokenizer parser =
2847             new StringTokenizer(path, File.pathSeparator);
2848         ArrayList<String> pathList = new ArrayList<>();
2849         try {
2850             while (parser.hasMoreTokens()) {
2851                 pathList.add(parser.nextToken());
2852             }
2853         } catch (NoSuchElementException e) {
2854         }
2855         pathDirs = pathList.toArray(new String[0]);
2856         return pathDirs;
2857     }
2858 
2859     /**
2860      * Returns an array of two strings. The first element is the
2861      * name of the font. The second element is the file name.
2862      */
2863     protected abstract String[] getDefaultPlatformFont();
2864 
2865     // Begin: Refactored from SunGraphicsEnviroment.
2866 
2867     /*
2868      * helper function for registerFonts
2869      */
2870     private void addDirFonts(String dirName, File dirFile,
2871                              FilenameFilter filter,
2872                              int fontFormat, boolean useJavaRasterizer,
2873                              int fontRank,
2874                              boolean defer, boolean resolveSymLinks) {
2875         String[] ls = dirFile.list(filter);
2876         if (ls == null || ls.length == 0) {
2877             return;
2878         }
2879         String[] fontNames = new String[ls.length];
2880         String[][] nativeNames = new String[ls.length][];
2881         int fontCount = 0;
2882 
2883         for (int i=0; i < ls.length; i++ ) {
2884             File theFile = new File(dirFile, ls[i]);
2885             String fullName = null;
2886             if (resolveSymLinks) {
2887                 try {
2888                     fullName = theFile.getCanonicalPath();
2889                 } catch (IOException e) {
2890                 }
2891             }
2892             if (fullName == null) {
2893                 fullName = dirName + File.separator + ls[i];
2894             }
2895 
2896             // REMIND: case compare depends on platform
2897             if (registeredFontFiles.contains(fullName)) {
2898                 continue;
2899             }
2900 
2901             if (badFonts != null && badFonts.contains(fullName)) {
2902                 if (FontUtilities.debugFonts()) {
2903                     FontUtilities.logWarning("skip bad font " + fullName);
2904                 }
2905                 continue; // skip this font file.
2906             }
2907 
2908             registeredFontFiles.add(fullName);
2909 
2910             if (FontUtilities.debugFonts()
2911                 && FontUtilities.getLogger().isLoggable(PlatformLogger.Level.INFO)) {
2912                 String message = "Registering font " + fullName;
2913                 String[] natNames = getNativeNames(fullName, null);
2914                 if (natNames == null) {
2915                     message += " with no native name";
2916                 } else {
2917                     message += " with native name(s) " + natNames[0];
2918                     for (int nn = 1; nn < natNames.length; nn++) {
2919                         message += ", " + natNames[nn];
2920                     }
2921                 }
2922                 FontUtilities.logInfo(message);
2923             }
2924             fontNames[fontCount] = fullName;
2925             nativeNames[fontCount++] = getNativeNames(fullName, null);
2926         }
2927         registerFonts(fontNames, nativeNames, fontCount, fontFormat,
2928                          useJavaRasterizer, fontRank, defer);
2929         return;
2930     }
2931 
2932     protected String[] getNativeNames(String fontFileName,
2933                                       String platformName) {
2934         return null;
2935     }
2936 
2937     /**
2938      * Returns a file name for the physical font represented by this platform
2939      * font name. The default implementation tries to obtain the file name
2940      * from the font configuration.
2941      * Subclasses may override to provide information from other sources.
2942      */
2943     protected String getFileNameFromPlatformName(String platformFontName) {
2944         return fontConfig.getFileNameFromPlatformName(platformFontName);
2945     }
2946 
2947     /**
2948      * Return the default font configuration.
2949      */
2950     public FontConfiguration getFontConfiguration() {
2951         return fontConfig;
2952     }
2953 
2954     /* A call to this method should be followed by a call to
2955      * registerFontDirs(..)
2956      */
2957     public String getPlatformFontPath(boolean noType1Font) {
2958         if (fontPath == null) {
2959             fontPath = getFontPath(noType1Font);
2960         }
2961         return fontPath;
2962     }
2963 
2964     @SuppressWarnings("removal")
2965     protected void loadFonts() {
2966         if (discoveredAllFonts) {
2967             return;
2968         }
2969         /* Use lock specific to the font system */
2970         synchronized (this) {
2971             if (FontUtilities.debugFonts()) {
2972                 Thread.dumpStack();
2973                 FontUtilities.logInfo("SunGraphicsEnvironment.loadFonts() called");
2974             }
2975             initialiseDeferredFonts();
2976 
2977             AccessController.doPrivileged(new PrivilegedAction<Void>() {
2978                 public Void run() {
2979                     if (fontPath == null) {
2980                         fontPath = getPlatformFontPath(noType1Font);
2981                         registerFontDirs(fontPath);
2982                     }
2983                     if (fontPath != null) {
2984                         // this will find all fonts including those already
2985                         // registered. But we have checks in place to prevent
2986                         // double registration.
2987                         if (! gotFontsFromPlatform()) {
2988                             registerFontsOnPath(fontPath, false,
2989                                                 Font2D.UNKNOWN_RANK,
2990                                                 false, true);
2991                             loadedAllFontFiles = true;
2992                         }
2993                     }
2994                     registerOtherFontFiles(registeredFontFiles);
2995                     discoveredAllFonts = true;
2996                     return null;
2997                 }
2998             });
2999         }
3000     }
3001 
3002     protected void registerFontDirs(String pathName) {
3003         return;
3004     }
3005 
3006     private void registerFontsOnPath(String pathName,
3007                                      boolean useJavaRasterizer, int fontRank,
3008                                      boolean defer, boolean resolveSymLinks) {
3009 
3010         StringTokenizer parser = new StringTokenizer(pathName,
3011                 File.pathSeparator);
3012         try {
3013             while (parser.hasMoreTokens()) {
3014                 registerFontsInDir(parser.nextToken(),
3015                         useJavaRasterizer, fontRank,
3016                         defer, resolveSymLinks);
3017             }
3018         } catch (NoSuchElementException e) {
3019         }
3020     }
3021 
3022     /* Called to register fall back fonts */
3023     public void registerFontsInDir(String dirName) {
3024         registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false);
3025     }
3026 
3027     // MACOSX begin -- need to access this in subclass
3028     protected void registerFontsInDir(String dirName, boolean useJavaRasterizer,
3029     // MACOSX end
3030                                     int fontRank,
3031                                     boolean defer, boolean resolveSymLinks) {
3032         File pathFile = new File(dirName);
3033         addDirFonts(dirName, pathFile, ttFilter,
3034                     FONTFORMAT_TRUETYPE, useJavaRasterizer,
3035                     fontRank==Font2D.UNKNOWN_RANK ?
3036                     Font2D.TTF_RANK : fontRank,
3037                     defer, resolveSymLinks);
3038         addDirFonts(dirName, pathFile, t1Filter,
3039                     FONTFORMAT_TYPE1, useJavaRasterizer,
3040                     fontRank==Font2D.UNKNOWN_RANK ?
3041                     Font2D.TYPE1_RANK : fontRank,
3042                     defer, resolveSymLinks);
3043     }
3044 
3045     protected void registerFontDir(String path) {
3046     }
3047 
3048     /**
3049      * Returns file name for default font, either absolute
3050      * or relative as needed by registerFontFile.
3051      */
3052     public synchronized String getDefaultFontFile() {
3053         return defaultFontFileName;
3054     }
3055 
3056     /**
3057      * Whether registerFontFile expects absolute or relative
3058      * font file names.
3059      */
3060     protected boolean useAbsoluteFontFileNames() {
3061         return true;
3062     }
3063 
3064     /**
3065      * Creates this environment's FontConfiguration.
3066      */
3067     protected abstract FontConfiguration createFontConfiguration();
3068 
3069     public abstract FontConfiguration
3070     createFontConfiguration(boolean preferLocaleFonts,
3071                             boolean preferPropFonts);
3072 
3073     /**
3074      * Returns face name for default font, or null if
3075      * no face names are used for CompositeFontDescriptors
3076      * for this platform.
3077      */
3078     public synchronized String getDefaultFontFaceName() {
3079         return defaultFontName;
3080     }
3081 
3082     @SuppressWarnings("removal")
3083     public void loadFontFiles() {
3084         loadFonts();
3085         if (loadedAllFontFiles) {
3086             return;
3087         }
3088         /* Use lock specific to the font system */
3089         synchronized (this) {
3090             if (FontUtilities.debugFonts()) {
3091                 Thread.dumpStack();
3092                 FontUtilities.logInfo("loadAllFontFiles() called");
3093             }
3094             AccessController.doPrivileged(new PrivilegedAction<Void>() {
3095                 public Void run() {
3096                     if (fontPath == null) {
3097                         fontPath = getPlatformFontPath(noType1Font);
3098                     }
3099                     if (fontPath != null) {
3100                         // this will find all fonts including those already
3101                         // registered. But we have checks in place to prevent
3102                         // double registration.
3103                         registerFontsOnPath(fontPath, false,
3104                                             Font2D.UNKNOWN_RANK,
3105                                             false, true);
3106                     }
3107                     loadedAllFontFiles = true;
3108                     return null;
3109                 }
3110             });
3111         }
3112     }
3113 
3114     /*
3115      * This method asks the font configuration API for all platform names
3116      * used as components of composite/logical fonts and iterates over these
3117      * looking up their corresponding file name and registers these fonts.
3118      * It also ensures that the fonts are accessible via platform APIs.
3119      * The composites themselves are then registered.
3120      */
3121     private void
3122         initCompositeFonts(FontConfiguration fontConfig,
3123                            ConcurrentHashMap<String, Font2D>  altNameCache) {
3124         if (FontUtilities.isLogging()) {
3125             FontUtilities.logInfo("Initialising composite fonts");
3126         }
3127 
3128         int numCoreFonts = fontConfig.getNumberCoreFonts();
3129         String[] fcFonts = fontConfig.getPlatformFontNames();
3130         for (int f=0; f<fcFonts.length; f++) {
3131             String platformFontName = fcFonts[f];
3132             String fontFileName =
3133                 getFileNameFromPlatformName(platformFontName);
3134             String[] nativeNames = null;
3135             if (fontFileName == null
3136                 || fontFileName.equals(platformFontName)) {
3137                 /* No file located, so register using the platform name,
3138                  * i.e. as a native font.
3139                  */
3140                 fontFileName = platformFontName;
3141             } else {
3142                 if (f < numCoreFonts) {
3143                     /* If platform APIs also need to access the font, add it
3144                      * to a set to be registered with the platform too.
3145                      * This may be used to add the parent directory to the X11
3146                      * font path if its not already there. See the docs for the
3147                      * subclass implementation.
3148                      * This is now mainly for the benefit of X11-based AWT
3149                      * But for historical reasons, 2D initialisation code
3150                      * makes these calls.
3151                      * If the fontconfiguration file is properly set up
3152                      * so that all fonts are mapped to files and all their
3153                      * appropriate directories are specified, then this
3154                      * method will be low cost as it will return after
3155                      * a test that finds a null lookup map.
3156                      */
3157                     addFontToPlatformFontPath(platformFontName);
3158                 }
3159                 nativeNames = getNativeNames(fontFileName, platformFontName);
3160             }
3161             /* Uncomment these two lines to "generate" the XLFD->filename
3162              * mappings needed to speed start-up on Solaris.
3163              * Augment this with the appendedpathname and the mappings
3164              * for native (F3) fonts
3165              */
3166             //String platName = platformFontName.replaceAll(" ", "_");
3167             //System.out.println("filename."+platName+"="+fontFileName);
3168             registerFontFile(fontFileName, nativeNames,
3169                              Font2D.FONT_CONFIG_RANK, true);
3170 
3171 
3172         }
3173         /* This registers accumulated paths from the calls to
3174          * addFontToPlatformFontPath(..) and any specified by
3175          * the font configuration. Rather than registering
3176          * the fonts it puts them in a place and form suitable for
3177          * the Toolkit to pick up and use if a toolkit is initialised,
3178          * and if it uses X11 fonts.
3179          */
3180         registerPlatformFontsUsedByFontConfiguration();
3181 
3182         CompositeFontDescriptor[] compositeFontInfo
3183                 = fontConfig.get2DCompositeFontInfo();
3184         for (int i = 0; i < compositeFontInfo.length; i++) {
3185             CompositeFontDescriptor descriptor = compositeFontInfo[i];
3186             String[] componentFileNames = descriptor.getComponentFileNames();
3187             String[] componentFaceNames = descriptor.getComponentFaceNames();
3188 
3189             /* It would be better eventually to handle this in the
3190              * FontConfiguration code which should also remove duplicate slots
3191              */
3192             if (missingFontFiles != null) {
3193                 for (int ii=0; ii<componentFileNames.length; ii++) {
3194                     if (missingFontFiles.contains(componentFileNames[ii])) {
3195                         componentFileNames[ii] = getDefaultFontFile();
3196                         componentFaceNames[ii] = getDefaultFontFaceName();
3197                     }
3198                 }
3199             }
3200 
3201             /* FontConfiguration needs to convey how many fonts it has added
3202              * as fallback component fonts which should not affect metrics.
3203              * The core component count will be the number of metrics slots.
3204              * This does not preclude other mechanisms for adding
3205              * fall back component fonts to the composite.
3206              */
3207             if (altNameCache != null) {
3208                 SunFontManager.registerCompositeFont(
3209                     descriptor.getFaceName(),
3210                     componentFileNames, componentFaceNames,
3211                     descriptor.getCoreComponentCount(),
3212                     descriptor.getExclusionRanges(),
3213                     descriptor.getExclusionRangeLimits(),
3214                     true,
3215                     altNameCache);
3216             } else {
3217                 registerCompositeFont(descriptor.getFaceName(),
3218                                       componentFileNames, componentFaceNames,
3219                                       descriptor.getCoreComponentCount(),
3220                                       descriptor.getExclusionRanges(),
3221                                       descriptor.getExclusionRangeLimits(),
3222                                       true);
3223             }
3224             if (FontUtilities.debugFonts()) {
3225                 FontUtilities.logInfo("registered " + descriptor.getFaceName());
3226             }
3227         }
3228     }
3229 
3230     /**
3231      * Notifies graphics environment that the logical font configuration
3232      * uses the given platform font name. The graphics environment may
3233      * use this for platform specific initialization.
3234      */
3235     protected void addFontToPlatformFontPath(String platformFontName) {
3236     }
3237 
3238     protected void registerFontFile(String fontFileName, String[] nativeNames,
3239                                     int fontRank, boolean defer) {
3240 //      REMIND: case compare depends on platform
3241         if (registeredFontFiles.contains(fontFileName)) {
3242             return;
3243         }
3244         int fontFormat;
3245         if (ttFilter.accept(null, fontFileName)) {
3246             fontFormat = FONTFORMAT_TRUETYPE;
3247         } else if (t1Filter.accept(null, fontFileName)) {
3248             fontFormat = FONTFORMAT_TYPE1;
3249         } else {
3250             fontFormat = FONTFORMAT_NATIVE;
3251         }
3252         registeredFontFiles.add(fontFileName);
3253         if (defer) {
3254             registerDeferredFont(fontFileName, fontFileName, nativeNames,
3255                                  fontFormat, false, fontRank);
3256         } else {
3257             registerFontFile(fontFileName, nativeNames, fontFormat, false,
3258                              fontRank);
3259         }
3260     }
3261 
3262     protected void registerPlatformFontsUsedByFontConfiguration() {
3263     }
3264 
3265     /*
3266      * A GE may verify whether a font file used in a fontconfiguration
3267      * exists. If it doesn't then either we may substitute the default
3268      * font, or perhaps elide it altogether from the composite font.
3269      * This makes some sense on windows where the font file is only
3270      * likely to be in one place. But on other OSes, eg Linux, the file
3271      * can move around depending. So there we probably don't want to assume
3272      * its missing and so won't add it to this list.
3273      * If this list - missingFontFiles - is non-null then the composite
3274      * font initialisation logic tests to see if a font file is in that
3275      * set.
3276      * Only one thread should be able to add to this set so we don't
3277      * synchronize.
3278      */
3279     protected void addToMissingFontFileList(String fileName) {
3280         if (missingFontFiles == null) {
3281             missingFontFiles = new HashSet<>();
3282         }
3283         missingFontFiles.add(fileName);
3284     }
3285 
3286     /*
3287      * This is for use only within getAllFonts().
3288      * Fonts listed in the fontconfig files for windows were all
3289      * on the "deferred" initialisation list. They were registered
3290      * either in the course of the application, or in the call to
3291      * loadFonts() within getAllFonts(). The fontconfig file specifies
3292      * the names of the fonts using the English names. If there's a
3293      * different name in the execution locale, then the platform will
3294      * report that, and we will construct the font with both names, and
3295      * thereby enumerate it twice. This happens for Japanese fonts listed
3296      * in the windows fontconfig, when run in the JA locale. The solution
3297      * is to rely (in this case) on the platform's font->file mapping to
3298      * determine that this name corresponds to a file we already registered.
3299      * This works because
3300      * - we know when we get here all deferred fonts are already initialised
3301      * - when we register a font file, we register all fonts in it.
3302      * - we know the fontconfig fonts are all in the windows registry
3303      */
3304     private boolean isNameForRegisteredFile(String fontName) {
3305         String fileName = getFileNameForFontName(fontName);
3306         if (fileName == null) {
3307             return false;
3308         }
3309         return registeredFontFiles.contains(fileName);
3310     }
3311 
3312     /*
3313      * This invocation is not in a privileged block because
3314      * all privileged operations (reading files and properties)
3315      * was conducted on the creation of the GE
3316      */
3317     public void
3318         createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache,
3319                              boolean preferLocale,
3320                              boolean preferProportional) {
3321 
3322         FontConfiguration fontConfig =
3323             createFontConfiguration(preferLocale, preferProportional);
3324         initCompositeFonts(fontConfig, altNameCache);
3325     }
3326 
3327     /**
3328      * Returns all fonts installed in this environment.
3329      */
3330     public Font[] getAllInstalledFonts() {
3331         if (allFonts == null) {
3332             loadFonts();
3333             TreeMap<String, Font2D> fontMapNames = new TreeMap<>();
3334             /* warning: the number of composite fonts could change dynamically
3335              * if applications are allowed to create them. "allfonts" could
3336              * then be stale.
3337              */
3338             Font2D[] allfonts = getRegisteredFonts();
3339             for (int i=0; i < allfonts.length; i++) {
3340                 if (!(allfonts[i] instanceof NativeFont)) {
3341                     fontMapNames.put(allfonts[i].getFontName(null),
3342                                      allfonts[i]);
3343                 }
3344             }
3345 
3346             String[] platformNames = getFontNamesFromPlatform();
3347             if (platformNames != null) {
3348                 for (int i=0; i<platformNames.length; i++) {
3349                     if (!isNameForRegisteredFile(platformNames[i])) {
3350                         fontMapNames.put(platformNames[i], null);
3351                     }
3352                 }
3353             }
3354 
3355             String[] fontNames = null;
3356             if (fontMapNames.size() > 0) {
3357                 fontNames = new String[fontMapNames.size()];
3358                 Object [] keyNames = fontMapNames.keySet().toArray();
3359                 for (int i=0; i < keyNames.length; i++) {
3360                     fontNames[i] = (String)keyNames[i];
3361                 }
3362             }
3363             Font[] fonts = new Font[fontNames.length];
3364             for (int i=0; i < fontNames.length; i++) {
3365                 fonts[i] = new Font(fontNames[i], Font.PLAIN, 1);
3366                 Font2D f2d = fontMapNames.get(fontNames[i]);
3367                 if (f2d  != null) {
3368                     FontAccess.getFontAccess().setFont2D(fonts[i], f2d.handle);
3369                 }
3370             }
3371             allFonts = fonts;
3372         }
3373 
3374         Font []copyFonts = new Font[allFonts.length];
3375         System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length);
3376         return copyFonts;
3377     }
3378 
3379     /**
3380      * Get a list of installed fonts in the requested {@link Locale}.
3381      * The list contains the fonts Family Names.
3382      * If Locale is null, the default locale is used.
3383      *
3384      * @param requestedLocale, if null the default locale is used.
3385      * @return list of installed fonts in the system.
3386      */
3387     public String[] getInstalledFontFamilyNames(Locale requestedLocale) {
3388         if (requestedLocale == null) {
3389             requestedLocale = Locale.getDefault();
3390         }
3391         if (allFamilies != null && lastDefaultLocale != null &&
3392             requestedLocale.equals(lastDefaultLocale)) {
3393                 String[] copyFamilies = new String[allFamilies.length];
3394                 System.arraycopy(allFamilies, 0, copyFamilies,
3395                                  0, allFamilies.length);
3396                 return copyFamilies;
3397         }
3398 
3399         TreeMap<String,String> familyNames = new TreeMap<String,String>();
3400         //  these names are always there and aren't localised
3401         String str;
3402         str = Font.SERIF;         familyNames.put(str.toLowerCase(), str);
3403         str = Font.SANS_SERIF;    familyNames.put(str.toLowerCase(), str);
3404         str = Font.MONOSPACED;    familyNames.put(str.toLowerCase(), str);
3405         str = Font.DIALOG;        familyNames.put(str.toLowerCase(), str);
3406         str = Font.DIALOG_INPUT;  familyNames.put(str.toLowerCase(), str);
3407 
3408         /* Platform APIs may be used to get the set of available family
3409          * names for the current default locale so long as it is the same
3410          * as the start-up system locale, rather than loading all fonts.
3411          */
3412         if (requestedLocale.equals(getSystemStartupLocale()) &&
3413             getFamilyNamesFromPlatform(familyNames, requestedLocale)) {
3414             /* Augment platform names with JRE font family names */
3415             getJREFontFamilyNames(familyNames, requestedLocale);
3416         } else {
3417             loadFontFiles();
3418             Font2D[] physicalfonts = getPhysicalFonts();
3419             for (int i=0; i < physicalfonts.length; i++) {
3420                 if (!(physicalfonts[i] instanceof NativeFont)) {
3421                     String name =
3422                         physicalfonts[i].getFamilyName(requestedLocale);
3423                     familyNames.put(name.toLowerCase(requestedLocale), name);
3424                 }
3425             }
3426         }
3427 
3428         // Add any native font family names here
3429         addNativeFontFamilyNames(familyNames, requestedLocale);
3430 
3431         String[] retval =  new String[familyNames.size()];
3432         Object [] keyNames = familyNames.keySet().toArray();
3433         for (int i=0; i < keyNames.length; i++) {
3434             retval[i] = familyNames.get(keyNames[i]);
3435         }
3436         if (requestedLocale.equals(Locale.getDefault())) {
3437             lastDefaultLocale = requestedLocale;
3438             allFamilies = new String[retval.length];
3439             System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length);
3440         }
3441         return retval;
3442     }
3443 
3444     // Provides an aperture to add native font family names to the map
3445     protected void addNativeFontFamilyNames(TreeMap<String, String> familyNames, Locale requestedLocale) { }
3446 
3447     @SuppressWarnings("removal")
3448     public void register1dot0Fonts() {
3449         AccessController.doPrivileged(new PrivilegedAction<Void>() {
3450             public Void run() {
3451                 String type1Dir = "/usr/openwin/lib/X11/fonts/Type1";
3452                 registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK,
3453                                    false, false);
3454                 return null;
3455             }
3456         });
3457     }
3458 
3459     /* Really we need only the JRE fonts family names, but there's little
3460      * overhead in doing this the easy way by adding all the currently
3461      * known fonts.
3462      */
3463     protected void getJREFontFamilyNames(TreeMap<String,String> familyNames,
3464                                          Locale requestedLocale) {
3465         registerDeferredJREFonts(jreFontDirName);
3466         Font2D[] physicalfonts = getPhysicalFonts();
3467         for (int i=0; i < physicalfonts.length; i++) {
3468             if (!(physicalfonts[i] instanceof NativeFont)) {
3469                 String name =
3470                     physicalfonts[i].getFamilyName(requestedLocale);
3471                 familyNames.put(name.toLowerCase(requestedLocale), name);
3472             }
3473         }
3474     }
3475 
3476     /**
3477      * Default locale can be changed but we need to know the initial locale
3478      * as that is what is used by native code. Changing Java default locale
3479      * doesn't affect that.
3480      * Returns the locale in use when using native code to communicate
3481      * with platform APIs. On windows this is known as the "system" locale,
3482      * and it is usually the same as the platform locale, but not always,
3483      * so this method also checks an implementation property used only
3484      * on windows and uses that if set.
3485      */
3486     private static Locale systemLocale = null;
3487     @SuppressWarnings("removal")
3488     private static Locale getSystemStartupLocale() {
3489         if (systemLocale == null) {
3490             systemLocale = AccessController.doPrivileged(new PrivilegedAction<Locale>() {
3491                 public Locale run() {
3492                     /* On windows the system locale may be different than the
3493                      * user locale. This is an unsupported configuration, but
3494                      * in that case we want to return a dummy locale that will
3495                      * never cause a match in the usage of this API. This is
3496                      * important because Windows documents that the family
3497                      * names of fonts are enumerated using the language of
3498                      * the system locale. BY returning a dummy locale in that
3499                      * case we do not use the platform API which would not
3500                      * return us the names we want.
3501                      */
3502                     String fileEncoding = System.getProperty("file.encoding", "");
3503                     String sysEncoding = System.getProperty("sun.jnu.encoding");
3504                     if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) {
3505                         return Locale.ROOT;
3506                     }
3507 
3508                     String language = System.getProperty("user.language", "en");
3509                     String country  = System.getProperty("user.country","");
3510                     String variant  = System.getProperty("user.variant","");
3511                     return new Locale(language, country, variant);
3512                 }
3513             });
3514         }
3515         return systemLocale;
3516     }
3517 
3518     void addToPool(FileFont font) {
3519 
3520         FileFont fontFileToClose = null;
3521         int freeSlot = -1;
3522 
3523         synchronized (fontFileCache) {
3524             /* Avoid duplicate entries in the pool, and don't close() it,
3525              * since this method is called only from within open().
3526              * Seeing a duplicate is most likely to happen if the thread
3527              * was interrupted during a read, forcing perhaps repeated
3528              * close and open calls and it eventually it ends up pointing
3529              * at the same slot.
3530              */
3531             for (int i=0;i<CHANNELPOOLSIZE;i++) {
3532                 if (fontFileCache[i] == font) {
3533                     return;
3534                 }
3535                 if (fontFileCache[i] == null && freeSlot < 0) {
3536                     freeSlot = i;
3537                 }
3538             }
3539             if (freeSlot >= 0) {
3540                 fontFileCache[freeSlot] = font;
3541                 return;
3542             } else {
3543                 /* replace with new font. */
3544                 fontFileToClose = fontFileCache[lastPoolIndex];
3545                 fontFileCache[lastPoolIndex] = font;
3546                 /* lastPoolIndex is updated so that the least recently opened
3547                  * file will be closed next.
3548                  */
3549                 lastPoolIndex = (lastPoolIndex+1) % CHANNELPOOLSIZE;
3550             }
3551         }
3552         /* Need to close the font file outside of the synchronized block,
3553          * since its possible some other thread is in an open() call on
3554          * this font file, and could be holding its lock and the pool lock.
3555          * Releasing the pool lock allows that thread to continue, so it can
3556          * then release the lock on this font, allowing the close() call
3557          * below to proceed.
3558          * Also, calling close() is safe because any other thread using
3559          * the font we are closing() synchronizes all reading, so we
3560          * will not close the file while its in use.
3561          */
3562         if (fontFileToClose != null) {
3563             fontFileToClose.close();
3564         }
3565     }
3566 
3567     protected FontUIResource getFontConfigFUIR(String family, int style,
3568                                                int size)
3569     {
3570         return new FontUIResource(family, style, size);
3571     }
3572 }
3573