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