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