1 /*
   2  * Copyright (c) 2011, 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 com.sun.javafx.font;
  27 
  28 import java.security.AccessController;
  29 import java.security.PrivilegedAction;
  30 import java.security.PrivilegedExceptionAction;
  31 import java.io.File;
  32 import java.io.FilenameFilter;
  33 import java.io.InputStream;
  34 import java.lang.ref.WeakReference;
  35 import java.lang.reflect.Method;
  36 import java.util.ArrayList;
  37 import java.util.Collections;
  38 import java.util.HashMap;
  39 import java.util.HashSet;
  40 import java.util.Iterator;
  41 import java.util.Locale;
  42 
  43 import com.sun.glass.ui.Screen;
  44 import com.sun.glass.utils.NativeLibLoader;
  45 import com.sun.javafx.PlatformUtil;
  46 import com.sun.javafx.text.GlyphLayout;
  47 import static com.sun.javafx.FXPermissions.LOAD_FONT_PERMISSION;
  48 
  49 public abstract class PrismFontFactory implements FontFactory {
  50 
  51     public static final boolean debugFonts;
  52     public static final boolean isWindows;
  53     public static final boolean isLinux;
  54     public static final boolean isMacOSX;
  55     public static final boolean isIOS;
  56     public static final boolean isAndroid;
  57     public static final boolean isEmbedded;
  58     public static final int cacheLayoutSize;
  59     private static int subPixelMode;
  60     public static final int SUB_PIXEL_ON = 1;
  61     public static final int SUB_PIXEL_Y = 2;
  62     public static final int SUB_PIXEL_NATIVE = 4;
  63     private static float fontSizeLimit = 80f;
  64 
  65     private static boolean lcdEnabled;
  66     private static float lcdContrast = -1;
  67     private static String jreFontDir;
  68     private static final String jreDefaultFont   = "Lucida Sans Regular";
  69     private static final String jreDefaultFontLC = "lucida sans regular";
  70     private static final String jreDefaultFontFile = "LucidaSansRegular.ttf";
  71     private static final String CT_FACTORY = "com.sun.javafx.font.coretext.CTFactory";
  72     private static final String DW_FACTORY = "com.sun.javafx.font.directwrite.DWFactory";
  73     private static final String FT_FACTORY = "com.sun.javafx.font.freetype.FTFactory";
  74 
  75     /* We need two maps. One to hold pointers to the raw fonts, another
  76      * to hold pointers to the composite resources. Top level look ups
  77      * to createFont() will look first in the compResourceMap, and
  78      * only go to the second map to create a wrapped resource.
  79      * Logical Fonts are handled separately.
  80      */
  81     HashMap<String, FontResource> fontResourceMap =
  82         new HashMap<String, FontResource>();
  83 
  84     HashMap<String, CompositeFontResource> compResourceMap =
  85         new HashMap<String, CompositeFontResource>();
  86 
  87     static {
  88         isWindows = PlatformUtil.isWindows();
  89         isMacOSX  = PlatformUtil.isMac();
  90         isLinux   = PlatformUtil.isLinux();
  91         isIOS     = PlatformUtil.isIOS();
  92         isAndroid = PlatformUtil.isAndroid();
  93         isEmbedded = PlatformUtil.isEmbedded();
  94         int[] tempCacheLayoutSize = {0x10000};
  95 
  96         debugFonts = AccessController.doPrivileged(
  97                 (PrivilegedAction<Boolean>) () -> {
  98                     NativeLibLoader.loadLibrary("javafx_font");
  99                     String dbg = System.getProperty("prism.debugfonts", "");
 100                     boolean debug = "true".equals(dbg);
 101                     jreFontDir = getJDKFontDir();
 102                     String s = System.getProperty("com.sun.javafx.fontSize");
 103                     systemFontSize = -1f;
 104                     if (s != null) {
 105                         try {
 106                             systemFontSize = Float.parseFloat(s);
 107                         } catch (NumberFormatException nfe) {
 108                             System.err.println("Cannot parse font size '"
 109                                     + s + "'");
 110                         }
 111                     }
 112                     s = System.getProperty("prism.subpixeltext", "on");
 113                     if (s.indexOf("on") != -1 || s.indexOf("true") != -1) {
 114                         subPixelMode = SUB_PIXEL_ON;
 115                     }
 116                     if (s.indexOf("native") != -1) {
 117                         subPixelMode |= SUB_PIXEL_NATIVE | SUB_PIXEL_ON;
 118                     }
 119                     if (s.indexOf("vertical") != -1) {
 120                         subPixelMode |= SUB_PIXEL_Y | SUB_PIXEL_NATIVE | SUB_PIXEL_ON;
 121                     }
 122 
 123                     s = System.getProperty("prism.fontSizeLimit");
 124                     if (s != null) {
 125                         try {
 126                             fontSizeLimit = Float.parseFloat(s);
 127                             if (fontSizeLimit <= 0) {
 128                                 fontSizeLimit = Float.POSITIVE_INFINITY;
 129                             }
 130                         } catch (NumberFormatException nfe) {
 131                             System.err.println("Cannot parse fontSizeLimit '" + s + "'");
 132                         }
 133                     }
 134 
 135                     boolean lcdTextOff = isIOS || isAndroid || isEmbedded;
 136                     String defLCDProp = lcdTextOff ? "false" : "true";
 137                     String lcdProp = System.getProperty("prism.lcdtext", defLCDProp);
 138                     lcdEnabled = lcdProp.equals("true");
 139 
 140                     s = System.getProperty("prism.cacheLayoutSize");
 141                     if (s != null) {
 142                         try {
 143                             tempCacheLayoutSize[0] = Integer.parseInt(s);
 144                             if (tempCacheLayoutSize[0] < 0) {
 145                                 tempCacheLayoutSize[0] = 0;
 146                             }
 147                         } catch (NumberFormatException nfe) {
 148                             System.err.println("Cannot parse cache layout size '"
 149                                     + s + "'");
 150                         }
 151                     }
 152 
 153                     return debug;
 154                 }
 155         );
 156         cacheLayoutSize = tempCacheLayoutSize[0];
 157     }
 158 
 159     private static String getJDKFontDir() {
 160         return System.getProperty("java.home","") + File.separator +
 161                 "lib" + File.separator + "fonts";
 162     }
 163 
 164     private static String getNativeFactoryName() {
 165         if (isWindows) return DW_FACTORY;
 166         if (isMacOSX || isIOS) return CT_FACTORY;
 167         if (isLinux || isAndroid) return FT_FACTORY;
 168         return null;
 169     }
 170 
 171     public static float getFontSizeLimit() {
 172         return fontSizeLimit;
 173     }
 174 
 175     private static PrismFontFactory theFontFactory = null;
 176     public static synchronized PrismFontFactory getFontFactory() {
 177         if (theFontFactory != null) {
 178             return theFontFactory;
 179         }
 180         String factoryClass = getNativeFactoryName();
 181         if (factoryClass == null) {
 182             throw new InternalError("cannot find a native font factory");
 183         }
 184         if (debugFonts) {
 185             System.err.println("Loading FontFactory " + factoryClass);
 186             if (subPixelMode != 0) {
 187                 String s = "Subpixel: enabled";
 188                 if ((subPixelMode & SUB_PIXEL_Y) != 0) {
 189                     s += ", vertical";
 190                 }
 191                 if ((subPixelMode & SUB_PIXEL_NATIVE) != 0) {
 192                     s += ", native";
 193                 }
 194                 System.err.println(s);
 195             }
 196         }
 197         theFontFactory = getFontFactory(factoryClass);
 198         if (theFontFactory == null) {
 199             throw new InternalError("cannot load font factory: "+ factoryClass);
 200         }
 201         return theFontFactory;
 202     }
 203 
 204     private static synchronized PrismFontFactory getFontFactory(String factoryClass) {
 205         try {
 206             Class<?> clazz = Class.forName(factoryClass);
 207             Method mid = clazz.getMethod("getFactory", (Class[])null);
 208             return (PrismFontFactory)mid.invoke(null);
 209         } catch (Throwable t) {
 210             if (debugFonts) {
 211                 System.err.println("Loading font factory failed "+ factoryClass);
 212             }
 213         }
 214         return null;
 215     }
 216 
 217     private HashMap<String, PrismFontFile>
 218         fileNameToFontResourceMap = new HashMap<String, PrismFontFile>();
 219 
 220     protected abstract PrismFontFile
 221           createFontFile(String name, String filename,
 222                          int fIndex, boolean register,
 223                          boolean embedded,
 224                          boolean copy, boolean tracked)
 225                          throws Exception;
 226 
 227     public abstract GlyphLayout createGlyphLayout();
 228 
 229     // For an caller who has recognised a TTC file and wants to create
 230     // the instances one at a time so as to have visibility into the
 231     // contents of the TTC. Onus is on caller to enumerate all the fonts.
 232     private PrismFontFile createFontResource(String filename, int index) {
 233         return createFontResource(null, filename, index,
 234                                   true, false, false, false);
 235     }
 236 
 237     private PrismFontFile createFontResource(String name,
 238                                              String filename, int index,
 239                                              boolean register, boolean embedded,
 240                                              boolean copy, boolean tracked) {
 241         String key = (filename+index).toLowerCase();
 242         PrismFontFile fr = fileNameToFontResourceMap.get(key);
 243         if (fr != null) {
 244             return fr;
 245         }
 246 
 247         try {
 248             fr = createFontFile(name, filename, index, register,
 249                                 embedded, copy, tracked);
 250             if (register) {
 251                 storeInMap(fr.getFullName(), fr);
 252                 fileNameToFontResourceMap.put(key, fr);
 253             }
 254             return fr;
 255         } catch (Exception e) {
 256             if (PrismFontFactory.debugFonts) {
 257                 e.printStackTrace();
 258             }
 259             return null;
 260         }
 261     }
 262 
 263     private PrismFontFile createFontResource(String name, String filename) {
 264         PrismFontFile[] pffArr =
 265             createFontResources(name, filename,
 266                                 true, false, false, false, false);
 267         if (pffArr == null || pffArr.length == 0) {
 268            return null;
 269         } else {
 270            return pffArr[0];
 271         }
 272     }
 273 
 274     private PrismFontFile[] createFontResources(String name, String filename,
 275                                                 boolean register,
 276                                                 boolean embedded,
 277                                                 boolean copy,
 278                                                 boolean tracked,
 279                                                 boolean loadAll) {
 280 
 281         PrismFontFile[] fArr = null;
 282         if (filename == null) {
 283             return null;
 284         }
 285         PrismFontFile fr = createFontResource(name, filename, 0, register,
 286                                               embedded, copy, tracked);
 287         if (fr == null) {
 288             return null;
 289         }
 290         int cnt = (!loadAll) ? 1 : fr.getFontCount();
 291         fArr = new PrismFontFile[cnt];
 292         fArr[0] = fr;
 293         if (cnt == 1) { // Not a TTC, or only requesting one font.
 294             return fArr;
 295         }
 296         PrismFontFile.FileRefCounter rc = null;
 297         if (copy) {
 298             rc = fr.createFileRefCounter();
 299         }
 300         int index = 1;
 301         do {
 302             String key = (filename+index).toLowerCase();
 303             try {
 304                 fr = fileNameToFontResourceMap.get(key);
 305                 if (fr != null) {
 306                     fArr[index] = fr;
 307                     continue;
 308                 } else {
 309                     fr = createFontFile(null, filename, index,
 310                                         register, embedded,
 311                                         copy, tracked);
 312                     if (fr == null) {
 313                         return null;
 314                     }
 315                     if (rc != null) {
 316                         fr.setAndIncFileRefCounter(rc);
 317                     }
 318                     fArr[index] = fr;
 319                     String fontname = fr.getFullName();
 320                     if (register) {
 321                         storeInMap(fontname, fr);
 322                         fileNameToFontResourceMap.put(key, fr);
 323                     }
 324                 }
 325             } catch (Exception e) {
 326                 if (PrismFontFactory.debugFonts) {
 327                     e.printStackTrace();
 328                 }
 329                 return null;
 330             }
 331 
 332         } while (++index < cnt);
 333         return fArr;
 334     }
 335 
 336     private String dotStyleStr(boolean bold, boolean italic) {
 337         if (!bold) {
 338             if (!italic) {
 339                 return "";
 340             }
 341             else {
 342                 return ".italic";
 343             }
 344         } else {
 345             if (!italic) {
 346                 return ".bold";
 347             }
 348             else {
 349                 return ".bolditalic";
 350             }
 351         }
 352     }
 353 
 354     private void storeInMap(String name, FontResource resource) {
 355         if (name == null || resource == null) {
 356             return;
 357         }
 358         if (resource instanceof PrismCompositeFontResource) {
 359             System.err.println(name + " is a composite " +
 360                                             resource);
 361             Thread.dumpStack();
 362             return;
 363         }
 364         fontResourceMap.put(name.toLowerCase(), resource);
 365     }
 366 
 367     private ArrayList<WeakReference<PrismFontFile>> tmpFonts;
 368     synchronized void addDecodedFont(PrismFontFile fr) {
 369         fr.setIsDecoded(true);
 370         addTmpFont(fr);
 371     }
 372 
 373     private synchronized void addTmpFont(PrismFontFile fr) {
 374         if (tmpFonts == null) {
 375             tmpFonts = new ArrayList<WeakReference<PrismFontFile>>();
 376         }
 377         WeakReference<PrismFontFile> ref;
 378         /* Registered fonts are enumerable by the application and are
 379          * expected to persist until VM shutdown.
 380          * Other fonts - notably ones temporarily loaded in a web page via
 381          * webview - should be eligible to be collected and have their
 382          * temp files deleted at any time.
 383          */
 384         if (fr.isRegistered()) {
 385             ref = new WeakReference<PrismFontFile>(fr);
 386         } else {
 387             ref = fr.createFileDisposer(this, fr.getFileRefCounter());
 388         }
 389         tmpFonts.add(ref);
 390         addFileCloserHook();
 391     }
 392 
 393     synchronized void removeTmpFont(WeakReference<PrismFontFile> ref) {
 394         if (tmpFonts != null) {
 395             tmpFonts.remove(ref);
 396         }
 397     }
 398 
 399     /* familyName is expected to be a physical font family name.
 400      */
 401     public synchronized FontResource getFontResource(String familyName,
 402                                                      boolean bold,
 403                                                      boolean italic,
 404                                                      boolean wantComp) {
 405 
 406         if (familyName == null || familyName.isEmpty()) {
 407             return null;
 408         }
 409 
 410         String lcFamilyName = familyName.toLowerCase();
 411         String styleStr = dotStyleStr(bold, italic);
 412         FontResource fr;
 413 
 414         fr = lookupResource(lcFamilyName+styleStr, wantComp);
 415         if (fr != null) {
 416             return fr;
 417         }
 418 
 419 
 420         /* We may have registered this as an embedded font.
 421          * In which case we should also try to locate it in
 422          * the non-composite map before looking elsewhere.
 423          * First look for a font with the exact styles specified.
 424          * If that fails, look for any font in the family.
 425          * Later on this should be a lot smarter about finding the best
 426          * match, but that can wait until we have better style matching
 427          * for all cases.
 428          */
 429         if (embeddedFonts != null && wantComp) {
 430             fr = lookupResource(lcFamilyName+styleStr, false);
 431             if (fr != null) {
 432                 return new PrismCompositeFontResource(fr, lcFamilyName+styleStr);
 433             }
 434             for (PrismFontFile embeddedFont : embeddedFonts.values()) {
 435                 String lcEmFamily = embeddedFont.getFamilyName().toLowerCase();
 436                 if (lcEmFamily.equals(lcFamilyName)) {
 437                     return new PrismCompositeFontResource(embeddedFont,
 438                                                         lcFamilyName+styleStr);
 439                 }
 440             }
 441         }
 442 
 443         /* We have hard coded some of the most commonly used Windows fonts
 444          * so as to avoid the overhead of doing a lookup via GDI.
 445          */
 446         if (isWindows) {
 447             int style = ((bold ? 1 : 0)) + ((italic) ? 2 : 0);
 448             String fontFile = WindowsFontMap.findFontFile(lcFamilyName, style);
 449             if (fontFile != null) {
 450                 fr = createFontResource(null, fontFile);
 451                 if (fr != null) {
 452                     if (bold == fr.isBold() && italic == fr.isItalic() &&
 453                         !styleStr.isEmpty())
 454                     {
 455                         storeInMap(lcFamilyName+styleStr, fr);
 456                     }
 457                     if (wantComp) {  // wrap with fallback support
 458                         fr = new PrismCompositeFontResource(fr,
 459                                                        lcFamilyName+styleStr);
 460                     }
 461                     return fr;
 462                 }
 463             }
 464         }
 465 
 466         getFullNameToFileMap();
 467         ArrayList<String> family = familyToFontListMap.get(lcFamilyName);
 468         if (family == null) {
 469             return null;
 470         }
 471 
 472         FontResource plainFR = null, boldFR = null,
 473             italicFR = null, boldItalicFR = null;
 474         for (String fontName : family) {
 475             String lcFontName = fontName.toLowerCase();
 476             fr = fontResourceMap.get(lcFontName);
 477             if (fr == null) {
 478                 String file = findFile(lcFontName);
 479                 if (file != null) {
 480                     fr = getFontResource(fontName, file);
 481                 }
 482                 if (fr == null) {
 483                     continue;
 484                 }
 485                 storeInMap(lcFontName, fr);
 486             }
 487             if (bold == fr.isBold() && italic == fr.isItalic()) {
 488                 storeInMap(lcFamilyName+styleStr, fr);
 489                 if (wantComp) {  // wrap with fallback support
 490                     fr = new PrismCompositeFontResource(fr,
 491                                                       lcFamilyName+styleStr);
 492                 }
 493                 return fr;
 494             }
 495             if (!fr.isBold()) {
 496                 if (!fr.isItalic()) {
 497                     plainFR = fr;
 498                 } else {
 499                     italicFR = fr;
 500                 }
 501             } else {
 502                 if (!fr.isItalic()) {
 503                     boldFR = fr;
 504                 } else {
 505                     boldItalicFR = fr;
 506                 }
 507             }
 508         }
 509 
 510         /* If get here, no perfect match in family. Substitute the
 511          * closest one we found.
 512          */
 513         if (!bold && !italic) {
 514             if (boldFR != null) {
 515                 fr = boldFR;
 516             } else if (italicFR != null) {
 517                 fr = italicFR;
 518             } else {
 519                 fr = boldItalicFR;
 520             }
 521         } else if (bold && !italic) {
 522             if (plainFR != null) {
 523                 fr = plainFR;
 524             } else if (boldItalicFR != null) {
 525                 fr = boldItalicFR;
 526             } else {
 527                 fr = italicFR;
 528             }
 529         } else if (!bold && italic) {
 530             if (boldItalicFR != null) {
 531                 fr =  boldItalicFR;
 532             } else if (plainFR != null) {
 533                 fr = plainFR;
 534             } else {
 535                 fr = boldFR;
 536             }
 537         } else /* (bold && italic) */ {
 538             if (italicFR != null) {
 539                 fr = italicFR;
 540             } else if (boldFR != null) {
 541                 fr = boldFR;
 542             } else {
 543                 fr = plainFR;
 544             }
 545         }
 546         if (fr != null) {
 547             storeInMap(lcFamilyName+styleStr, fr);
 548             if (wantComp) {  // wrap with fallback support
 549                 fr = new PrismCompositeFontResource(fr, lcFamilyName+styleStr);
 550             }
 551         }
 552         return fr;
 553     }
 554 
 555     public synchronized PGFont createFont(String familyName, boolean bold,
 556                                           boolean italic, float size) {
 557         FontResource fr = null;
 558         if (familyName != null && !familyName.isEmpty()) {
 559             PGFont logFont =
 560                 LogicalFont.getLogicalFont(familyName, bold, italic, size);
 561             if (logFont != null) {
 562                 return logFont;
 563             }
 564             fr = getFontResource(familyName, bold, italic, true);
 565         }
 566 
 567         if (fr == null) {
 568             // "System" is the default if we didn't recognise the family
 569             return LogicalFont.getLogicalFont("System", bold, italic, size);
 570         }
 571         return new PrismFont(fr, fr.getFullName(), size);
 572     }
 573 
 574     public synchronized PGFont createFont(String name, float size) {
 575 
 576         FontResource fr = null;
 577         if (name != null && !name.isEmpty()) {
 578             PGFont logFont =
 579                 LogicalFont.getLogicalFont(name, size);
 580             if (logFont != null) {
 581                 return logFont;
 582             }
 583 
 584             fr = getFontResource(name, null, true);
 585         }
 586         if (fr == null) {
 587             return LogicalFont.getLogicalFont(DEFAULT_FULLNAME, size);
 588         }
 589         return new PrismFont(fr, fr.getFullName(), size);
 590     }
 591 
 592     private PrismFontFile getFontResource(String name, String file) {
 593         /* caller assures file not null */
 594         PrismFontFile fr = null;
 595         /* Still need decode the dfont (event when coretext is used)
 596          * so that JFXFontFont can read it */
 597         if (isMacOSX) {
 598             DFontDecoder decoder = null;
 599             if (name != null) {
 600                 if (file.endsWith(".dfont")) {
 601                     decoder = new DFontDecoder();
 602                     try {
 603                         decoder.openFile();
 604                         decoder.decode(name);
 605                         decoder.closeFile();
 606                         file = decoder.getFile().getPath();
 607                     } catch (Exception e) {
 608                         file = null;
 609                         decoder.deleteFile();
 610                         decoder = null;
 611                         if (PrismFontFactory.debugFonts) {
 612                             e.printStackTrace();
 613                         }
 614                     }
 615                 }
 616             }
 617             if (file != null) {
 618                 fr = createFontResource(name, file);
 619             }
 620             if (decoder != null) {
 621                 if (fr != null) {
 622                     addDecodedFont(fr);
 623                 } else {
 624                     decoder.deleteFile();
 625                 }
 626             }
 627         } else {
 628             fr = createFontResource(name, file);
 629         }
 630         return fr;
 631     }
 632 
 633     public synchronized PGFont deriveFont(PGFont font, boolean bold,
 634                                           boolean italic, float size) {
 635         FontResource fr = font.getFontResource();
 636         //TODO honor bold and italic
 637         return new PrismFont(fr, fr.getFullName(), size);
 638     }
 639 
 640     private FontResource lookupResource(String lcName, boolean wantComp) {
 641         if (wantComp) {
 642             return compResourceMap.get(lcName);
 643         } else {
 644             return fontResourceMap.get(lcName);
 645         }
 646     }
 647 
 648     public synchronized FontResource getFontResource(String name, String file,
 649                                                      boolean wantComp) {
 650         FontResource fr = null;
 651 
 652         // First check if the font is already known.
 653         if (name != null) {
 654             String lcName = name.toLowerCase();
 655 
 656             // if requesting a wrapped resource, look in the composite map
 657             // else look in the physical resource map
 658             FontResource fontResource = lookupResource(lcName, wantComp);
 659             if (fontResource != null) {
 660                 return fontResource;
 661             }
 662 
 663             /* We may have registered this as an embedded font.
 664              * In which case we should also try to locate it in
 665              * the non-composite map before looking elsewhere.
 666              */
 667             if (embeddedFonts != null && wantComp) {
 668                 fr = lookupResource(lcName, false);
 669                 if (fr != null) {
 670                     fr = new PrismCompositeFontResource(fr, lcName);
 671                 }
 672                 if (fr != null) {
 673                     return fr;
 674                 }
 675             }
 676         }
 677 
 678         /* We have hard coded some of the most commonly used Windows fonts
 679          * so as to avoid the overhead of doing a lookup via GDI.
 680          */
 681         if (isWindows && name != null) {
 682             String lcName = name.toLowerCase();
 683             String fontFile = WindowsFontMap.findFontFile(lcName, -1);
 684             if (fontFile != null) {
 685                 fr = createFontResource(null, fontFile);
 686                 if (fr != null) {
 687                     if (wantComp) {
 688                         fr = new PrismCompositeFontResource(fr, lcName);
 689                     }
 690                     return fr;
 691                 }
 692             }
 693         }
 694 
 695         getFullNameToFileMap(); // init maps
 696 
 697         if (name != null && file != null) {
 698             // Typically the TTC case used in font linking.
 699             // The called method adds the resources to the physical
 700             // map so no need  to do it here.
 701             fr = getFontResource(name, file);
 702             if (fr != null) {
 703                 if (wantComp) {
 704                     fr = new PrismCompositeFontResource(fr, name.toLowerCase());
 705                 }
 706                 return fr;
 707             }
 708         }
 709 
 710         if (name != null) { // Typically normal application lookup
 711             fr = getFontResourceByFullName(name, wantComp);
 712             if (fr != null) {
 713                 return fr;
 714             }
 715         }
 716 
 717         if (file != null) { // Typically the TTF case used in font linking
 718             fr = getFontResourceByFileName(file, wantComp);
 719             if (fr != null) {
 720                 return fr;
 721             }
 722         }
 723 
 724         /* can't find the requested font, caller will fall back to default */
 725         return null;
 726     }
 727 
 728     boolean isInstalledFont(String fileName) {
 729         // avoid loading the full windows map. Ignore drive letter
 730         // as its common to install on D: too in multi-boot.
 731         String fileKey;
 732         if (isWindows) {
 733             if (fileName.toLowerCase().contains("\\windows\\fonts")) {
 734                 return true;
 735             }
 736             File f = new File(fileName);
 737             fileKey = f.getName();
 738         } else {
 739             if (isMacOSX && fileName.toLowerCase().contains("/library/fonts")) {
 740                 // Most fonts are installed in either /System/Library/Fonts/
 741                 // or /Library/Fonts/
 742                 return true;
 743             }
 744             File f = new File(fileName);
 745             // fileToFontMap key is the full path on non-windows
 746             fileKey = f.getPath();
 747         }
 748 
 749         getFullNameToFileMap();
 750         return fileToFontMap.get(fileKey.toLowerCase()) != null;
 751     }
 752 
 753     /* To be called only by methods that already inited the maps
 754      */
 755     synchronized private FontResource
 756         getFontResourceByFileName(String file, boolean wantComp) {
 757 
 758         if (fontToFileMap.size() <= 1) {
 759             return null;
 760         }
 761 
 762         /* This is a little tricky: we know the file we want but we need
 763          * to check if its already a loaded resource. The maps are set up
 764          * to check if a font is loaded by its name, not file.
 765          * To help I added a map from file->font for all the windows fonts
 766          * but that is valid only for TTF fonts (not TTC). So it should only
 767          * be used in a context where we know its a TTF (or OTF) file.
 768          */
 769         String name = fileToFontMap.get(file.toLowerCase()); // basename
 770         FontResource fontResource = null;
 771         if (name == null) {
 772             // We should not normally get here with a name that we did
 773             // not find from the platform but any EUDC font is in the
 774             // list of linked fonts but it is not enumerated by Windows.
 775             // So we need to open the file and load it as requested.
 776            fontResource = createFontResource(file, 0);
 777            if (fontResource != null) {
 778                String lcName = fontResource.getFullName().toLowerCase();
 779                storeInMap(lcName, fontResource);
 780                // Checking wantComp, alhough the linked/fallback font
 781                // case doesn't use this.
 782                 if (wantComp) {
 783                     fontResource =
 784                         new PrismCompositeFontResource(fontResource, lcName);
 785                 }
 786            }
 787         } else {
 788             String lcName = name.toLowerCase();
 789             fontResource = lookupResource(lcName, wantComp);
 790 
 791             if (fontResource == null) {
 792                 String fullPath = findFile(lcName);
 793                 if (fullPath != null) {
 794                     fontResource = getFontResource(name, fullPath);
 795                     if (fontResource != null) {
 796                         storeInMap(lcName, fontResource);
 797                     }
 798                     if (wantComp) {
 799                         // wrap with fallback support
 800                         fontResource =
 801                                 new PrismCompositeFontResource(fontResource, lcName);
 802                     }
 803                 }
 804             }
 805         }
 806         return fontResource; // maybe null
 807     }
 808 
 809     /* To be called only by methods that already inited the maps
 810      * and checked the font is not already loaded.
 811      */
 812     synchronized private FontResource
 813         getFontResourceByFullName(String name, boolean wantComp) {
 814 
 815         String lcName = name.toLowerCase();
 816 
 817         if (fontToFileMap.size() <= 1) {
 818             // Do this even though findFile also fails over to Lucida, as
 819             // without this step, we'd create new instances.
 820             name = jreDefaultFont;
 821         }
 822 
 823         FontResource fontResource = null;
 824         String file = findFile(lcName);
 825         if (file != null) {
 826             fontResource = getFontResource(name, file);
 827             if (fontResource != null) {
 828                 storeInMap(lcName, fontResource);
 829                 if (wantComp) {
 830                     // wrap with fallback support
 831                     fontResource =
 832                         new PrismCompositeFontResource(fontResource, lcName);
 833                 }
 834             }
 835         }
 836         return fontResource;
 837     }
 838 
 839     FontResource getDefaultFontResource(boolean wantComp) {
 840         FontResource fontResource = lookupResource(jreDefaultFontLC, wantComp);
 841         if (fontResource == null) {
 842             fontResource = createFontResource(jreDefaultFont,
 843                                               jreFontDir+jreDefaultFontFile);
 844             if (fontResource == null) {
 845                 // Normally use the JRE default font as the last fallback.
 846                 // If we can't find even that, use any platform font;
 847                 for (String font : fontToFileMap.keySet()) {
 848                     String file = findFile(font); // gets full path
 849                     fontResource = createFontResource(jreDefaultFontLC, file);
 850                     if (fontResource != null) {
 851                         break;
 852                     }
 853                 }
 854                 if (fontResource == null && isLinux) {
 855                     String path = FontConfigManager.getDefaultFontPath();
 856                     if (path != null) {
 857                         fontResource = createFontResource(jreDefaultFontLC,
 858                                                           path);
 859                     }
 860                 }
 861                 if (fontResource == null) {
 862                     return null; // We tried really hard!
 863                 }
 864             }
 865             storeInMap(jreDefaultFontLC, fontResource);
 866             if (wantComp) {  // wrap primary for map key
 867                 fontResource =
 868                     new PrismCompositeFontResource(fontResource,
 869                                                  jreDefaultFontLC);
 870             }
 871         }
 872         return fontResource;
 873     }
 874 
 875     private String findFile(String name) {
 876 
 877         if (name.equals(jreDefaultFontLC)) {
 878             return jreFontDir+jreDefaultFontFile;
 879         }
 880         getFullNameToFileMap();
 881         String filename = fontToFileMap.get(name);
 882         if (isWindows) {
 883             filename = getPathNameWindows(filename);
 884         }
 885 
 886         // Caller needs to check for null and explicitly request
 887         // the JRE default font, if that is what is needed.
 888         // since we don't want the JRE's Lucida font to be the
 889         // default for "unknown" fonts.
 890         return filename;
 891     }
 892 
 893     /* Used to indicate required return type from toArray(..); */
 894     private static final String[] STR_ARRAY = new String[0];
 895 
 896     /* Obtained from Platform APIs (windows only)
 897      * Map from lower-case font full name to basename of font file.
 898      * Eg "arial bold" -> ARIALBD.TTF.
 899      * For TTC files, there is a mapping for each font in the file.
 900      */
 901     private volatile HashMap<String,String> fontToFileMap = null;
 902 
 903     /*  TTF/OTF Font File to Font Full Name */
 904     private HashMap<String,String> fileToFontMap = null;
 905 
 906     /* Obtained from Platform APIs (windows only)
 907      * Map from lower-case font full name to the name of its font family
 908      * Eg "arial bold" -> "Arial"
 909      */
 910     private HashMap<String,String> fontToFamilyNameMap = null;
 911 
 912     /* Obtained from Platform APIs (windows only)
 913      * Map from a lower-case family name to a list of full names of
 914      * the member fonts, eg:
 915      * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"]
 916      */
 917     private HashMap<String,ArrayList<String>> familyToFontListMap= null;
 918 
 919 
 920     /* For a terminal server there may be two font directories */
 921     private static String sysFontDir = null;
 922     private static String userFontDir = null;
 923 
 924     private static native byte[] getFontPath();
 925     private static native String regReadFontLink(String searchfont);
 926     private static native String getEUDCFontFile();
 927 
 928     private static void getPlatformFontDirs() {
 929 
 930         if (userFontDir != null || sysFontDir != null) {
 931             return;
 932         }
 933         byte [] pathBytes = getFontPath();
 934         String path = new String(pathBytes);
 935 
 936         int scIdx = path.indexOf(';');
 937         if (scIdx < 0) {
 938             sysFontDir = path;
 939         } else {
 940             sysFontDir = path.substring(0, scIdx);
 941             userFontDir = path.substring(scIdx+1, path.length());
 942         }
 943     }
 944 
 945     /**
 946       * This will return an array of size 2, each element being an array
 947       * list of <code>String</code>. The first (zeroth) array holds file
 948       * names, and, the second array holds the corresponding fontnames.
 949       * If the file does not have a corresponding font name, its corresponding
 950       * name is assigned an empty string "".
 951       * As a further complication, Windows 7 frequently lists a font twice,
 952       * once with some scaling values, and again without. We don't use this
 953       * so find these and exclude duplicates.
 954       */
 955     static ArrayList<String> [] getLinkedFonts(String searchFont,
 956                                                boolean addSearchFont) {
 957 
 958 
 959         ArrayList<String> [] fontRegInfo = new ArrayList[2];
 960         // index 0 = file names, 1 = font name.
 961         // the name is only specified for TTC files.
 962         fontRegInfo[0] = new ArrayList<String>();
 963         fontRegInfo[1] = new ArrayList<String>();
 964 
 965         if (isMacOSX) {
 966             // Hotkey implementation of fallback font on Mac
 967             fontRegInfo[0].add("/Library/Fonts/Arial Unicode.ttf");
 968             fontRegInfo[1].add("Arial Unicode MS");
 969 
 970             // Add Lucida Sans Regular to Mac OS X fallback list
 971             fontRegInfo[0].add(jreFontDir + jreDefaultFontFile);
 972             fontRegInfo[1].add(jreDefaultFont);
 973 
 974             // Add Apple Symbols to Mac OS X fallback list
 975             fontRegInfo[0].add("/System/Library/Fonts/Apple Symbols.ttf");
 976             fontRegInfo[1].add("Apple Symbols");
 977 
 978             // Add Apple Emoji Symbols to Mac OS X fallback list
 979             fontRegInfo[0].add("/System/Library/Fonts/Apple Color Emoji.ttc");
 980             fontRegInfo[1].add("Apple Color Emoji");
 981 
 982             // Add CJK Ext B supplementary characters.
 983             fontRegInfo[0].add("/System/Library/Fonts/STHeiti Light.ttf");
 984             fontRegInfo[1].add("Heiti SC Light");
 985 
 986             return fontRegInfo;
 987         }
 988         if (!isWindows) {
 989             return fontRegInfo;
 990         }
 991 
 992         if (addSearchFont) {
 993             fontRegInfo[0].add(null);
 994             fontRegInfo[1].add(searchFont);
 995         }
 996 
 997         String fontRegBuf = regReadFontLink(searchFont);
 998         if (fontRegBuf != null && fontRegBuf.length() > 0) {
 999             // split registry data into null terminated strings
1000             String[] fontRegList = fontRegBuf.split("\u0000");
1001             int linkListLen = fontRegList.length;
1002             for (int i=0; i < linkListLen; i++) {
1003                 String[] splitFontData = fontRegList[i].split(",");
1004                 int len = splitFontData.length;
1005                 String file = getPathNameWindows(splitFontData[0]);
1006                 String name = (len > 1) ? splitFontData[1] : null;
1007                 if (name != null && fontRegInfo[1].contains(name)) {
1008                     continue;
1009                 } else if (name == null && fontRegInfo[0].contains(file)) {
1010                     continue;
1011                 }
1012                 fontRegInfo[0].add(file);
1013                 fontRegInfo[1].add(name);
1014             }
1015         }
1016 
1017         String eudcFontFile = getEUDCFontFile();
1018         if (eudcFontFile != null) {
1019             fontRegInfo[0].add(eudcFontFile);
1020             fontRegInfo[1].add(null);
1021         }
1022 
1023         // Add Lucida Sans Regular to Windows fallback list
1024         fontRegInfo[0].add(jreFontDir + jreDefaultFontFile);
1025         fontRegInfo[1].add(jreDefaultFont);
1026 
1027         if (PlatformUtil.isWinVistaOrLater()) {
1028             // CJK Ext B Supplementary character fallbacks.
1029             fontRegInfo[0].add(getPathNameWindows("mingliub.ttc"));
1030             fontRegInfo[1].add("MingLiU-ExtB");
1031 
1032             if (PlatformUtil.isWin7OrLater()) {
1033                 // Add Segoe UI Symbol to Windows 7 or later fallback list
1034                 fontRegInfo[0].add(getPathNameWindows("seguisym.ttf"));
1035                 fontRegInfo[1].add("Segoe UI Symbol");
1036             } else {
1037                 // Add Cambria Math to Windows Vista fallback list
1038                 fontRegInfo[0].add(getPathNameWindows("cambria.ttc"));
1039                 fontRegInfo[1].add("Cambria Math");
1040             }
1041         }
1042         return fontRegInfo;
1043     }
1044 
1045     /* This is needed since some windows registry names don't match
1046      * the font names.
1047      * - UPC styled font names have a double space, but the
1048      * registry entry mapping to a file doesn't.
1049      * - Marlett is in a hidden file not listed in the registry
1050      * - The registry advertises that the file david.ttf contains a
1051      * font with the full name "David Regular" when in fact its
1052      * just "David".
1053      * Directly fix up these known cases as this is faster.
1054      * If a font which doesn't match these known cases has no file,
1055      * it may be a font that has been temporarily added to the known set
1056      * or it may be an installed font with a missing registry entry.
1057      * Installed fonts are those in the windows font directories.
1058      * Make a best effort attempt to locate these.
1059      * We obtain the list of TrueType fonts in these directories and
1060      * filter out all the font files we already know about from the registry.
1061      * What remains may be "bad" fonts, duplicate fonts, or perhaps the
1062      * missing font(s) we are looking for.
1063      * Open each of these files to find out.
1064      */
1065     private void resolveWindowsFonts
1066         (HashMap<String,String> fontToFileMap,
1067          HashMap<String,String> fontToFamilyNameMap,
1068          HashMap<String,ArrayList<String>> familyToFontListMap) {
1069 
1070         ArrayList<String> unmappedFontNames = null;
1071         for (String font : fontToFamilyNameMap.keySet()) {
1072             String file = fontToFileMap.get(font);
1073             if (file == null) {
1074                 int dsi = font.indexOf("  ");
1075                 if (dsi > 0) {
1076                     String newName = font.substring(0, dsi);
1077                     newName = newName.concat(font.substring(dsi+1));
1078                     file = fontToFileMap.get(newName);
1079                     /* If this name exists and isn't for a valid name
1080                      * replace the mapping to the file with this font
1081                      */
1082                     if (file != null &&
1083                         !fontToFamilyNameMap.containsKey(newName)) {
1084                         fontToFileMap.remove(newName);
1085                         fontToFileMap.put(font, file);
1086                     }
1087                 } else if (font.equals("marlett")) {
1088                     fontToFileMap.put(font, "marlett.ttf");
1089                 } else if (font.equals("david")) {
1090                     file = fontToFileMap.get("david regular");
1091                     if (file != null) {
1092                         fontToFileMap.remove("david regular");
1093                         fontToFileMap.put("david", file);
1094                     }
1095                 } else {
1096                     if (unmappedFontNames == null) {
1097                         unmappedFontNames = new ArrayList<String>();
1098                     }
1099                     unmappedFontNames.add(font);
1100                 }
1101             }
1102         }
1103 
1104         if (unmappedFontNames != null) {
1105             HashSet<String> unmappedFontFiles = new HashSet<String>();
1106 
1107             // Used HashMap.clone() on SE but TV didn't support it.
1108             HashMap<String,String> ffmapCopy = new HashMap<String,String>();
1109             ffmapCopy.putAll(fontToFileMap);
1110             for (String key : fontToFamilyNameMap.keySet()) {
1111                 ffmapCopy.remove(key);
1112             }
1113             for (String key : ffmapCopy.keySet()) {
1114                 unmappedFontFiles.add(ffmapCopy.get(key));
1115                 fontToFileMap.remove(key);
1116             }
1117             resolveFontFiles(unmappedFontFiles,
1118                              unmappedFontNames,
1119                              fontToFileMap,
1120                              fontToFamilyNameMap,
1121                              familyToFontListMap);
1122 
1123             /* remove from the set of names that will be returned to the
1124              * user any fonts that can't be mapped to files.
1125              */
1126             if (unmappedFontNames.size() > 0) {
1127                 int sz = unmappedFontNames.size();
1128                 for (int i=0; i<sz; i++) {
1129                     String name = unmappedFontNames.get(i);
1130                     String familyName = fontToFamilyNameMap.get(name);
1131                     if (familyName != null) {
1132                         ArrayList<String> family = familyToFontListMap.get(familyName);
1133                         if (family != null) {
1134                             if (family.size() <= 1) {
1135                                 familyToFontListMap.remove(familyName);
1136                             }
1137                         }
1138                     }
1139                     fontToFamilyNameMap.remove(name);
1140                 }
1141             }
1142         }
1143     }
1144 
1145     private void resolveFontFiles(HashSet<String> unmappedFiles,
1146          ArrayList<String> unmappedFonts,
1147          HashMap<String,String> fontToFileMap,
1148          HashMap<String,String> fontToFamilyNameMap,
1149          HashMap<String,ArrayList<String>> familyToFontListMap) {
1150 
1151         for (String file : unmappedFiles) {
1152             try {
1153                 int fn = 0;
1154                 PrismFontFile ttf;
1155                 String fullPath = getPathNameWindows(file);
1156                 do {
1157                     ttf = createFontResource(fullPath, fn++);
1158                     if (ttf == null) {
1159                         break;
1160                     }
1161                     String fontNameLC = ttf.getFullName().toLowerCase();
1162                     String localeNameLC =ttf.getLocaleFullName().toLowerCase();
1163                     if (unmappedFonts.contains(fontNameLC) ||
1164                         unmappedFonts.contains(localeNameLC)) {
1165                         fontToFileMap.put(fontNameLC, file);
1166                         unmappedFonts.remove(fontNameLC);
1167                         /* If GDI reported names using locale specific style
1168                          * strings we'll have those as the unmapped keys in
1169                          * the font to family list and also in the value
1170                          * array list mapped by the family.
1171                          * We can spot these if the localeName is what is
1172                          * actually in the unmapped font list, and we'll
1173                          * then replace all occurrences of the locale name with
1174                          * the English name.
1175                          */
1176                         if (unmappedFonts.contains(localeNameLC)) {
1177                             unmappedFonts.remove(localeNameLC);
1178                             String family = ttf.getFamilyName();
1179                             String familyLC = family.toLowerCase();
1180                             fontToFamilyNameMap.remove(localeNameLC);
1181                             fontToFamilyNameMap.put(fontNameLC, family);
1182                             ArrayList<String> familylist =
1183                                 familyToFontListMap.get(familyLC);
1184                             if (familylist != null) {
1185                                 familylist.remove(ttf.getLocaleFullName());
1186                             } else {
1187                                 /* The family name was not English.
1188                                  * Remove the non-English family list
1189                                  * and replace it with the English one
1190                                  */
1191                                 String localeFamilyLC =
1192                                     ttf.getLocaleFamilyName().toLowerCase();
1193                                 familylist =
1194                                     familyToFontListMap.get(localeFamilyLC);
1195                                 if (familylist != null) {
1196                                     familyToFontListMap.remove(localeFamilyLC);
1197                                 }
1198                                 familylist = new ArrayList<String>();
1199                                 familyToFontListMap.put(familyLC, familylist);
1200                             }
1201                             familylist.add(ttf.getFullName());
1202                         }
1203                     }
1204 
1205                 }
1206                 while (fn < ttf.getFontCount());
1207             } catch (Exception e) {
1208                 if (debugFonts) {
1209                     e.printStackTrace();
1210                 }
1211             }
1212         }
1213     }
1214 
1215     static native void
1216         populateFontFileNameMap(HashMap<String,String> fontToFileMap,
1217                                  HashMap<String,String> fontToFamilyNameMap,
1218                                  HashMap<String,ArrayList<String>>
1219                                      familyToFontListMap,
1220                                  Locale locale);
1221 
1222     static String getPathNameWindows(final String filename) {
1223         if (filename == null) {
1224             return null;
1225         }
1226 
1227         getPlatformFontDirs();
1228         File f = new File(filename);
1229         if (f.isAbsolute()) {
1230             return filename;
1231         }
1232         if (userFontDir == null) {
1233             return sysFontDir+"\\"+filename;
1234         }
1235 
1236         String path = AccessController.doPrivileged(
1237             new PrivilegedAction<String>() {
1238                 public String run() {
1239                     File f = new File(sysFontDir+"\\"+filename);
1240                     if (f.exists()) {
1241                         return f.getAbsolutePath();
1242                     }
1243                     else {
1244                         return userFontDir+"\\"+filename;
1245                     }
1246                 }
1247             });
1248 
1249             if (path != null) {
1250                 return path;
1251             }
1252         return null; //  shouldn't happen.
1253     }
1254 
1255     private static ArrayList<String> allFamilyNames;
1256     public String[] getFontFamilyNames() {
1257         if (allFamilyNames == null) {
1258             /* Create an array list and add the families for :
1259              * - logical fonts
1260              * - Embedded fonts
1261              * - Fonts found on the platform (includes JRE fonts)..
1262              */
1263             ArrayList<String> familyNames = new ArrayList<String>();
1264             LogicalFont.addFamilies(familyNames);
1265             //  Putting this in here is dependendent on the FontLoader
1266             // loading embedded fonts before calling into here. If
1267             // embedded fonts can be added then we need to add these
1268             // dynamically for each call to this method.
1269 
1270             if (embeddedFonts != null) {
1271                 for (PrismFontFile embeddedFont : embeddedFonts.values()) {
1272                     if (!familyNames.contains(embeddedFont.getFamilyName()))
1273                         familyNames.add(embeddedFont.getFamilyName());
1274                 }
1275             }
1276             getFullNameToFileMap();
1277             for (String f : fontToFamilyNameMap.values()) {
1278                 if (!familyNames.contains(f)) {
1279                     familyNames.add(f);
1280                 }
1281             }
1282             Collections.sort(familyNames);
1283             allFamilyNames = new ArrayList<String>(familyNames);
1284         }
1285         return allFamilyNames.toArray(STR_ARRAY);
1286     }
1287 
1288     private static ArrayList<String> allFontNames;
1289     public String[] getFontFullNames() {
1290         if (allFontNames == null) {
1291             /* Create an array list and add
1292              * - logical fonts
1293              * - Embedded fonts
1294              * - Fonts found on the platform (includes JRE fonts).
1295              */
1296             ArrayList<String> fontNames = new ArrayList<String>();
1297             LogicalFont.addFullNames(fontNames);
1298             if (embeddedFonts != null) {
1299                 for (PrismFontFile embeddedFont : embeddedFonts.values()) {
1300                     if (!fontNames.contains(embeddedFont.getFullName())) {
1301                         fontNames.add(embeddedFont.getFullName());
1302                     }
1303                 }
1304             }
1305             getFullNameToFileMap();
1306             for (ArrayList<String> a : familyToFontListMap.values()) {
1307                 for (String s : a) {
1308                     fontNames.add(s);
1309                 }
1310             }
1311             Collections.sort(fontNames);
1312             allFontNames = fontNames;
1313         }
1314         return allFontNames.toArray(STR_ARRAY);
1315     }
1316 
1317     public String[] getFontFullNames(String family) {
1318 
1319         // First check if its a logical font family.
1320         String[] logFonts = LogicalFont.getFontsInFamily(family);
1321         if (logFonts != null) {
1322             // Caller will clone/wrap this before returning it to API
1323             return logFonts;
1324         }
1325         // Next check if its an embedded font family
1326         if (embeddedFonts != null) {
1327             ArrayList<String> embeddedFamily = null;
1328             for (PrismFontFile embeddedFont : embeddedFonts.values()) {
1329                 if (embeddedFont.getFamilyName().equalsIgnoreCase(family)) {
1330                     if (embeddedFamily == null) {
1331                         embeddedFamily = new ArrayList<String>();
1332                     }
1333                     embeddedFamily.add(embeddedFont.getFullName());
1334                 }
1335             }
1336             if (embeddedFamily != null) {
1337                 return embeddedFamily.toArray(STR_ARRAY);
1338             }
1339         }
1340 
1341         getFullNameToFileMap();
1342         family = family.toLowerCase();
1343         ArrayList<String> familyFonts = familyToFontListMap.get(family);
1344         if (familyFonts != null) {
1345             return familyFonts.toArray(STR_ARRAY);
1346         } else {
1347             return STR_ARRAY; // zero-length therefore immutable.
1348         }
1349     }
1350 
1351     public final int getSubPixelMode() {
1352         return subPixelMode;
1353     }
1354 
1355     public boolean isLCDTextSupported() {
1356         return lcdEnabled;
1357     }
1358 
1359     @Override
1360     public boolean isPlatformFont(String name) {
1361         if (name == null) return false;
1362         /* Using String#startsWith as name can be either a fullName or a family name */
1363         String lcName = name.toLowerCase();
1364         if (LogicalFont.isLogicalFont(lcName)) return true;
1365         if (lcName.startsWith("lucida sans")) return true;
1366         String systemFamily = getSystemFont(LogicalFont.SYSTEM).toLowerCase();
1367         if (lcName.startsWith(systemFamily)) return true;
1368         return false;
1369     }
1370 
1371     public static boolean isJreFont(FontResource fr) {
1372         String file = fr.getFileName();
1373         return file.startsWith(jreFontDir);
1374     }
1375 
1376     public static float getLCDContrast() {
1377         if (lcdContrast == -1) {
1378             if (isWindows) {
1379                 lcdContrast = getLCDContrastWin32() / 1000f;
1380             } else {
1381                 /* REMIND: When using CoreText it likely already applies gamma
1382                  * correction to the glyph images. The current implementation does
1383                  * not take this into account when rasterizing the glyph. Thus,
1384                  * it is possible gamma correction is been applied twice to the
1385                  * final result.
1386                  * Consider using "1" for lcdContrast possibly produces visually
1387                  * more appealing results (although not strictly correct).
1388                  */
1389                 lcdContrast = 1.3f;
1390             }
1391         }
1392         return lcdContrast;
1393     }
1394 
1395     private static Thread fileCloser = null;
1396 
1397     private synchronized void addFileCloserHook() {
1398         if (fileCloser == null) {
1399             final Runnable fileCloserRunnable = () -> {
1400                 if (embeddedFonts != null) {
1401                     for (PrismFontFile font : embeddedFonts.values()) {
1402                         font.disposeOnShutdown();
1403                     }
1404                 }
1405                 if (tmpFonts != null) {
1406                     for (WeakReference<PrismFontFile> ref : tmpFonts) {
1407                         PrismFontFile font = ref.get();
1408                         if (font != null) {
1409                             font.disposeOnShutdown();
1410                         }
1411                     }
1412                 }
1413             };
1414             java.security.AccessController.doPrivileged(
1415                     (PrivilegedAction<Object>) () -> {
1416                         /* The thread must be a member of a thread group
1417                          * which will not get GCed before VM exit.
1418                          * Make its parent the top-level thread group.
1419                          */
1420                         ThreadGroup tg = Thread.currentThread().getThreadGroup();
1421                         for (ThreadGroup tgn = tg;
1422                              tgn != null; tg = tgn, tgn = tg.getParent());
1423                         fileCloser = new Thread(tg, fileCloserRunnable);
1424                         fileCloser.setContextClassLoader(null);
1425                         Runtime.getRuntime().addShutdownHook(fileCloser);
1426                         return null;
1427                     }
1428             );
1429         }
1430     }
1431 
1432     private HashMap<String, PrismFontFile> embeddedFonts;
1433 
1434     public PGFont[] loadEmbeddedFont(String name, InputStream fontStream,
1435                                      float size,
1436                                      boolean register,
1437                                      boolean loadAll) {
1438         if (!hasPermission()) {
1439             return new PGFont[] { createFont(DEFAULT_FULLNAME, size) } ;
1440         }
1441         if (FontFileWriter.hasTempPermission()) {
1442             return loadEmbeddedFont0(name, fontStream, size, register, loadAll);
1443         }
1444 
1445         // Otherwise, be extra conscious of pending temp file creation and
1446         // resourcefully handle the temp file resources, among other things.
1447         FontFileWriter.FontTracker tracker =
1448             FontFileWriter.FontTracker.getTracker();
1449         boolean acquired = false;
1450         try {
1451             acquired = tracker.acquirePermit();
1452             if (!acquired) {
1453                 // Timed out waiting for resources.
1454                 return null;
1455             }
1456             return loadEmbeddedFont0(name, fontStream, size, register, loadAll);
1457         } catch (InterruptedException e) {
1458             // Interrupted while waiting to acquire a permit.
1459             return null;
1460         } finally {
1461             if (acquired) {
1462                 tracker.releasePermit();
1463             }
1464         }
1465     }
1466 
1467     private PGFont[] loadEmbeddedFont0(String name, InputStream fontStream,
1468                                        float size,
1469                                        boolean register,
1470                                        boolean loadAll) {
1471         PrismFontFile[] fr = null;
1472         FontFileWriter fontWriter = new FontFileWriter();
1473         try {
1474             // We use a shutdown hook to close all open tmp files
1475             // created via this API and delete them.
1476             final File tFile = fontWriter.openFile();
1477             byte[] buf = new byte[8192];
1478             for (;;) {
1479                 int bytesRead = fontStream.read(buf);
1480                 if (bytesRead < 0) {
1481                     break;
1482                 }
1483                 fontWriter.writeBytes(buf, 0, bytesRead);
1484             }
1485             fontWriter.closeFile();
1486 
1487             fr = loadEmbeddedFont1(name, tFile.getPath(), register, true,
1488                                    fontWriter.isTracking(), loadAll);
1489 
1490             if (fr != null && fr.length > 0) {
1491                 /* Delete the file downloaded if it was decoded
1492                  * to another file */
1493                 if (fr[0].isDecoded()) {
1494                     fontWriter.deleteFile();
1495                 }
1496             }
1497 
1498             /* We don't want to leave the temp files around after exit.
1499              * Also in a shared applet-type context, after all references to
1500              * the applet and therefore the font are dropped, the file
1501              * should be removed. This isn't so much an issue so long as
1502              * the VM exists to serve a single FX app, but will be
1503              * important in an app-context model.
1504              * But also fonts that are over-written by new versions
1505              * need to be cleaned up and that applies even in the single
1506              * context.
1507              * We also need to decrement the byte count by the size
1508              * of the file.
1509              */
1510             addFileCloserHook();
1511         } catch (Exception e) {
1512             fontWriter.deleteFile();
1513         } finally {
1514             /* If the data isn't a valid font, so that registering it
1515              * returns null, or we didn't get so far as copying the data,
1516              * delete the tmp file and decrement the byte count
1517              * in the tracker object before returning.
1518              */
1519             if (fr == null) {
1520                 fontWriter.deleteFile();
1521             }
1522         }
1523         if (fr != null && fr.length > 0) {
1524             if (size <= 0) size = getSystemFontSize();
1525             int num = fr.length;
1526             PrismFont[] pFonts = new PrismFont[num];
1527             for (int i=0; i<num; i++) {
1528                 pFonts[i] = new PrismFont(fr[i], fr[i].getFullName(), size);
1529             }
1530             return pFonts;
1531         }
1532         return null;
1533     }
1534 
1535     /**
1536      * registerEmbeddedFont(String name, String path) is a small subset of
1537      * registerEmbeddedFont(String name, InputStream fontStream)
1538      * It does not attempt to create a temporary file and has different
1539      * parameters.
1540      *
1541      * @param name font name
1542      * @param path Path name to system file
1543      * @param size font size
1544      * @param register whether the font should be registered.
1545      * @param loadAll whether to load all fonts if it is a TTC
1546      * @return font name extracted from font file
1547      */
1548     public PGFont[] loadEmbeddedFont(String name, String path,
1549                                      float size,
1550                                      boolean register,
1551                                      boolean loadAll) {
1552         if (!hasPermission()) {
1553             return new PGFont[] { createFont(DEFAULT_FULLNAME, size) };
1554         }
1555         addFileCloserHook();
1556         FontResource[] frArr =
1557           loadEmbeddedFont1(name, path, register, false, false, loadAll);
1558         if (frArr != null && frArr.length > 0) {
1559             if (size <= 0) size = getSystemFontSize();
1560             int num = frArr.length;
1561             PGFont[] pgFonts = new PGFont[num];
1562             for (int i=0; i<num; i++) {
1563                 pgFonts[i] =
1564                     new PrismFont(frArr[i], frArr[i].getFullName(), size);
1565             }
1566             return pgFonts;
1567         }
1568         return null;
1569     }
1570 
1571     /* This should make the embedded font eligible for reclaimation
1572      * and subsequently, disposal of native resources, once any existing
1573      * strong refs by the application are released.
1574      */
1575     private void removeEmbeddedFont(String name) {
1576         PrismFontFile font = embeddedFonts.get(name);
1577         if (font == null) {
1578             return;
1579         }
1580         embeddedFonts.remove(name);
1581         String lcName = name.toLowerCase();
1582         fontResourceMap.remove(lcName);
1583         compResourceMap.remove(lcName);
1584         // The following looks tedious, but if the compMap could have
1585         // the font referenced via some lookup name that applies a style
1586         // or used the family name, we need to find it and remove all
1587         // references to it, so it can be collected.
1588         Iterator<CompositeFontResource> fi = compResourceMap.values().iterator();
1589             while (fi.hasNext()) {
1590             CompositeFontResource compFont = fi.next();
1591             if (compFont.getSlotResource(0) == font) {
1592                 fi.remove();
1593             }
1594         }
1595     }
1596 
1597     protected boolean registerEmbeddedFont(String path) {
1598         return true;
1599     }
1600 
1601     // Used for testing
1602     private int numEmbeddedFonts = 0;
1603     public int test_getNumEmbeddedFonts() {
1604         return numEmbeddedFonts;
1605     }
1606 
1607     private synchronized
1608         PrismFontFile[] loadEmbeddedFont1(String name, String path,
1609                                           boolean register, boolean copy,
1610                                           boolean tracked, boolean loadAll) {
1611 
1612         ++numEmbeddedFonts;
1613         /*
1614          * Fonts that aren't platform installed include those in the
1615          * application jar, WOFF fonts that are downloaded, and fonts
1616          * created via Font.loadFont. If copy==true, we can infer its
1617          * one of these, but its also possible for a font to be file-system
1618          * installed as part of the application but not known to the
1619          * platform. In this case copy==false, but we still need to flag
1620          * to the system its not a platform font so that other pipelines
1621          * know to reference the file directly.
1622          */
1623         PrismFontFile[] frArr = createFontResources(name, path, register,
1624                                                     true, copy, tracked,
1625                                                     loadAll);
1626         if (frArr == null || frArr.length == 0) {
1627             return null; // yes, this means the caller needs to handle null.
1628         }
1629 
1630         /* Before we return or register, make sure names are present
1631          * check whether any of the fonts duplicate an OS font.
1632          */
1633 
1634         if (embeddedFonts == null) {
1635             embeddedFonts = new HashMap<String, PrismFontFile>();
1636         }
1637 
1638         boolean registerEmbedded = true;
1639         for (int i=0; i<frArr.length; i++) {
1640             PrismFontFile fr = frArr[i];
1641             String family = fr.getFamilyName();
1642             if (family == null || family.length() == 0) return null;
1643             String fullname = fr.getFullName();
1644             if (fullname == null || fullname.length() == 0) return null;
1645             String psname = fr.getPSName();
1646             if (psname == null || psname.length() == 0) return null;
1647 
1648             FontResource resource = embeddedFonts.get(fullname);
1649             if (resource != null && fr.equals(resource)) {
1650                 /* Do not register the same font twice in the OS */
1651                 registerEmbedded = false;
1652             }
1653         }
1654 
1655         if (registerEmbedded) {
1656             /* Use filename from the resource so woff fonts are handled */
1657             if (!registerEmbeddedFont(frArr[0].getFileName())) {
1658                 /* This font file can't be used by the underlying rasterizer */
1659                 return null;
1660             }
1661         }
1662 
1663         /* If a temporary font is a copy but it is not decoded then it
1664          * will not be anywhere the shutdown hook can see.
1665          * That means if the font is keep for the entire life of the VM
1666          * its file will not be deleted.
1667          * The fix is to add this font to the list of temporary fonts.
1668          */
1669         if (copy && !frArr[0].isDecoded()) {
1670             addTmpFont(frArr[0]);
1671         }
1672 
1673         if (!register) {
1674             return frArr;
1675         }
1676 
1677         /* If a font name is provided then we will also store that in the
1678          * map as an alias, otherwise should use the only the real name,
1679          * REMIND: its possible that either name may hide some installed
1680          * version of the font, possibly one we haven't seen yet. But
1681          * without loading all the platform fonts (expensive) this is
1682          * difficult to ascertain. A contains() check here is therefore
1683          * probably mostly futile.
1684          */
1685         if (name != null && !name.isEmpty()) {
1686             embeddedFonts.put(name, frArr[0]);
1687             storeInMap(name, frArr[0]);
1688         }
1689 
1690         for (int i=0; i<frArr.length; i++) {
1691             PrismFontFile fr = frArr[i];
1692             String family = fr.getFamilyName();
1693             String fullname = fr.getFullName();
1694             removeEmbeddedFont(fullname);
1695             embeddedFonts.put(fullname, fr);
1696             storeInMap(fullname, fr);
1697             family = family + dotStyleStr(fr.isBold(), fr.isItalic());
1698             storeInMap(family, fr);
1699             /* The remove call is to assist the case where we have
1700              * previously mapped into the composite map a different style
1701              * in this family as a partial match for the application request.
1702              * This can occur when an application requested a bold font before
1703              * it called Font.loadFont to register the bold font. It won't
1704              * fix the cases that already happened, but will fix the future ones.
1705              */
1706             compResourceMap.remove(family.toLowerCase());
1707         }
1708         return frArr;
1709     }
1710 
1711     private void
1712         logFontInfo(String message,
1713                     HashMap<String,String> fontToFileMap,
1714                     HashMap<String,String> fontToFamilyNameMap,
1715                     HashMap<String,ArrayList<String>> familyToFontListMap) {
1716 
1717         System.err.println(message);
1718         for (String keyName : fontToFileMap.keySet()) {
1719             System.err.println("font="+keyName+" file="+
1720                                fontToFileMap.get(keyName));
1721         }
1722         for (String keyName : fontToFamilyNameMap.keySet()) {
1723             System.err.println("font="+keyName+" family="+
1724                                fontToFamilyNameMap.get(keyName));
1725         }
1726         for (String keyName : familyToFontListMap.keySet()) {
1727             System.err.println("family="+keyName+ " fonts="+
1728                                familyToFontListMap.get(keyName));
1729         }
1730     }
1731 
1732     private synchronized HashMap<String,String> getFullNameToFileMap() {
1733         if (fontToFileMap == null) {
1734 
1735             HashMap<String, String>
1736                 tmpFontToFileMap = new HashMap<String,String>(100);
1737             fontToFamilyNameMap = new HashMap<String,String>(100);
1738             familyToFontListMap = new HashMap<String,ArrayList<String>>(50);
1739             fileToFontMap = new HashMap<String,String>(100);
1740 
1741             if (isWindows) {
1742                 getPlatformFontDirs();
1743                 populateFontFileNameMap(tmpFontToFileMap,
1744                                         fontToFamilyNameMap,
1745                                         familyToFontListMap,
1746                                         Locale.ENGLISH);
1747 
1748                 if (debugFonts) {
1749                     System.err.println("Windows Locale ID=" + getSystemLCID());
1750                     logFontInfo(" *** WINDOWS FONTS BEFORE RESOLVING",
1751                                 tmpFontToFileMap,
1752                                 fontToFamilyNameMap,
1753                                 familyToFontListMap);
1754                 }
1755 
1756                 resolveWindowsFonts(tmpFontToFileMap,
1757                                     fontToFamilyNameMap,
1758                                     familyToFontListMap);
1759 
1760                 if (debugFonts) {
1761                     logFontInfo(" *** WINDOWS FONTS AFTER RESOLVING",
1762                                 tmpFontToFileMap,
1763                                 fontToFamilyNameMap,
1764                                 familyToFontListMap);
1765                 }
1766 
1767             } else if (isMacOSX || isIOS) {
1768                 MacFontFinder.populateFontFileNameMap(tmpFontToFileMap,
1769                                                       fontToFamilyNameMap,
1770                                                       familyToFontListMap,
1771                                                       Locale.ENGLISH);
1772 
1773             } else if (isLinux) {
1774                 FontConfigManager.populateMaps(tmpFontToFileMap,
1775                                                fontToFamilyNameMap,
1776                                                familyToFontListMap,
1777                                                Locale.getDefault());
1778                 if (debugFonts) {
1779                     logFontInfo(" *** FONTCONFIG LOCATED FONTS:",
1780                                 tmpFontToFileMap,
1781                                 fontToFamilyNameMap,
1782                                 familyToFontListMap);
1783                 }
1784             } else if (isAndroid) {
1785                AndroidFontFinder.populateFontFileNameMap(tmpFontToFileMap,
1786                         fontToFamilyNameMap,
1787                         familyToFontListMap,
1788                         Locale.ENGLISH);
1789            } else { /* unrecognised OS */
1790                 fontToFileMap = tmpFontToFileMap;
1791                 return fontToFileMap;
1792             }
1793 
1794             /* Reverse map from file to font. file name is base name
1795              * not a full path.
1796              */
1797             for (String font : tmpFontToFileMap.keySet()) {
1798                 String file = tmpFontToFileMap.get(font);
1799                 fileToFontMap.put(file.toLowerCase(), font);
1800             }
1801 
1802             fontToFileMap = tmpFontToFileMap;
1803             if (isAndroid) {
1804                 populateFontFileNameMapGeneric(
1805                        AndroidFontFinder.getSystemFontsDir());
1806             }
1807             populateFontFileNameMapGeneric(jreFontDir);
1808 
1809 //             for (String keyName : fontToFileMap.keySet()) {
1810 //               System.out.println("font="+keyName+" file="+ fontToFileMap.get(keyName));
1811 //             }
1812 
1813 //             for (String keyName : familyToFontListMap.keySet()) {
1814 //               System.out.println("family="+keyName);
1815 //             }
1816         }
1817         return fontToFileMap;
1818     }
1819 
1820     public final boolean hasPermission() {
1821         try {
1822             SecurityManager sm = System.getSecurityManager();
1823             if (sm != null) {
1824                 sm.checkPermission(LOAD_FONT_PERMISSION);
1825             }
1826             return true;
1827         } catch (SecurityException ex) {
1828             return false;
1829         }
1830     }
1831 
1832     private static class TTFilter implements FilenameFilter {
1833         public boolean accept(File dir,String name) {
1834             /* all conveniently have the same suffix length */
1835             int offset = name.length()-4;
1836             if (offset <= 0) { /* must be at least A.ttf */
1837                 return false;
1838             } else {
1839                 return(name.startsWith(".ttf", offset) ||
1840                        name.startsWith(".TTF", offset) ||
1841                        name.startsWith(".ttc", offset) ||
1842                        name.startsWith(".TTC", offset) ||
1843                        name.startsWith(".otf", offset) ||
1844                        name.startsWith(".OTF", offset));
1845             }
1846         }
1847 
1848         private TTFilter() {
1849         }
1850 
1851         static TTFilter ttFilter;
1852         static TTFilter getInstance() {
1853             if (ttFilter == null) {
1854                 ttFilter = new TTFilter();
1855             }
1856             return ttFilter;
1857         }
1858     }
1859 
1860     void addToMaps(PrismFontFile fr) {
1861 
1862         if (fr == null) {
1863             return;
1864         }
1865 
1866         String fullName = fr.getFullName();
1867         String familyName = fr.getFamilyName();
1868 
1869         if (fullName == null || familyName == null) {
1870             return;
1871         }
1872 
1873         String lcFullName = fullName.toLowerCase();
1874         String lcFamilyName = familyName.toLowerCase();
1875 
1876         fontToFileMap.put(lcFullName, fr.getFileName());
1877         fontToFamilyNameMap.put(lcFullName, familyName);
1878         ArrayList<String> familyList = familyToFontListMap.get(lcFamilyName);
1879         if (familyList == null) {
1880             familyList = new ArrayList<String>();
1881             familyToFontListMap.put(lcFamilyName, familyList);
1882         }
1883         familyList.add(fullName);
1884     }
1885 
1886     void populateFontFileNameMapGeneric(String fontDir) {
1887         final File dir = new File(fontDir);
1888         String[] files = null;
1889         try {
1890             files = AccessController.doPrivileged(
1891                     (PrivilegedExceptionAction<String[]>) () -> dir.list(TTFilter.getInstance())
1892             );
1893         } catch (Exception e) {
1894         }
1895 
1896         if (files == null) {
1897             return;
1898         }
1899 
1900         for (int i=0;i<files.length;i++) {
1901             try {
1902                 String path = fontDir+File.separator+files[i];
1903 
1904                 /* Use filename from the resource so woff fonts are handled */
1905                 if (!registerEmbeddedFont(path)) {
1906                     /* This font file can't be used by the underlying rasterizer */
1907                     continue;
1908                 }
1909 
1910                 int index = 0;
1911                 PrismFontFile fr = createFontResource(path, index++);
1912                 if (fr == null) {
1913                     continue;
1914                 }
1915                 addToMaps(fr);
1916                 while (index < fr.getFontCount()) {
1917                     fr = createFontResource(path, index++);
1918                     if (fr == null) {
1919                         break;
1920                     }
1921                     addToMaps(fr);
1922                 }
1923             } catch (Exception e) {
1924                 /* Keep going if anything bad happens with a font */
1925             }
1926         }
1927     }
1928 
1929     static native int getLCDContrastWin32();
1930     private static native float getSystemFontSizeNative();
1931     private static native String getSystemFontNative();
1932     private static float systemFontSize;
1933     private static String systemFontFamily = null;
1934     private static String monospaceFontFamily = null;
1935 
1936     public static float getSystemFontSize() {
1937         if (systemFontSize == -1) {
1938             if (isWindows) {
1939                 systemFontSize = getSystemFontSizeNative();
1940             } else if (isMacOSX || isIOS) {
1941                 systemFontSize = MacFontFinder.getSystemFontSize();
1942             } else if (isAndroid) {
1943                systemFontSize = AndroidFontFinder.getSystemFontSize();
1944             } else if (isEmbedded) {
1945                 try {
1946                     int screenDPI = Screen.getMainScreen().getResolutionY();
1947                     systemFontSize = ((float) screenDPI) / 6f; // 12 points
1948                 } catch (NullPointerException npe) {
1949                     // if no screen is defined
1950                     systemFontSize = 13f; // same as desktop Linux
1951                 }
1952             } else {
1953                 systemFontSize = 13f; // Gnome uses 13.
1954             }
1955         }
1956         return systemFontSize;
1957     }
1958 
1959     /* Applies to Windows and Mac. Not used on Linux */
1960     public static String getSystemFont(String name) {
1961         if (name.equals(LogicalFont.SYSTEM)) {
1962             if (systemFontFamily == null) {
1963                 if (isWindows) {
1964                     systemFontFamily = getSystemFontNative();
1965                     if (systemFontFamily == null) {
1966                         systemFontFamily = "Arial"; // play it safe.
1967                     }
1968                 } else if (isMacOSX || isIOS) {
1969                     systemFontFamily = MacFontFinder.getSystemFont();
1970                     if (systemFontFamily == null) {
1971                         systemFontFamily = "Lucida Grande";
1972                     }
1973                 } else if (isAndroid) {
1974                    systemFontFamily = AndroidFontFinder.getSystemFont();
1975                 } else {
1976                     systemFontFamily = "Lucida Sans"; // for now.
1977                 }
1978             }
1979             return systemFontFamily;
1980         } else if (name.equals(LogicalFont.SANS_SERIF)) {
1981             return "Arial";
1982         } else if (name.equals(LogicalFont.SERIF)) {
1983             return "Times New Roman";
1984         } else /* if (name.equals(LogicalFont.MONOSPACED)) */ {
1985             if (monospaceFontFamily == null) {
1986                 if (isMacOSX) {
1987                     /* This code is intentionally commented:
1988                      * On the OS X the preferred monospaced font is Monaco,
1989                      * although this can be a good choice for most Mac application
1990                      * it is not suitable for JavaFX because Monaco does not
1991                      * have neither bold nor italic.
1992                      */
1993 //                    monospaceFontFamily = MacFontFinder.getMonospacedFont();
1994                 }
1995             }
1996             if (monospaceFontFamily == null) {
1997                 monospaceFontFamily = "Courier New";
1998             }
1999             return monospaceFontFamily;
2000         }
2001     }
2002 
2003     /* Called from PrismFontFile which caches the return value */
2004     static native short getSystemLCID();
2005 }