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