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