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