1 /*
   2  * Copyright (c) 2011, 2017, 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 Apple Emoji Symbols to Mac OS X fallback list
 991             fontRegInfo[0].add("/System/Library/Fonts/Apple Color Emoji.ttc");
 992             fontRegInfo[1].add("Apple Color Emoji");
 993 
 994             // Add CJK Ext B supplementary characters.
 995             fontRegInfo[0].add("/System/Library/Fonts/STHeiti Light.ttf");
 996             fontRegInfo[1].add("Heiti SC Light");
 997 
 998             return fontRegInfo;
 999         }
1000         if (!isWindows) {
1001             return fontRegInfo;
1002         }
1003 
1004         if (addSearchFont) {
1005             fontRegInfo[0].add(null);
1006             fontRegInfo[1].add(searchFont);
1007         }
1008 
1009         String fontRegBuf = regReadFontLink(searchFont);
1010         if (fontRegBuf != null && fontRegBuf.length() > 0) {
1011             // split registry data into null terminated strings
1012             String[] fontRegList = fontRegBuf.split("\u0000");
1013             int linkListLen = fontRegList.length;
1014             for (int i=0; i < linkListLen; i++) {
1015                 String[] splitFontData = fontRegList[i].split(",");
1016                 int len = splitFontData.length;
1017                 String file = getPathNameWindows(splitFontData[0]);
1018                 String name = (len > 1) ? splitFontData[1] : null;
1019                 if (name != null && fontRegInfo[1].contains(name)) {
1020                     continue;
1021                 } else if (name == null && fontRegInfo[0].contains(file)) {
1022                     continue;
1023                 }
1024                 fontRegInfo[0].add(file);
1025                 fontRegInfo[1].add(name);
1026             }
1027         }
1028 
1029         String eudcFontFile = getEUDCFontFile();
1030         if (eudcFontFile != null) {
1031             fontRegInfo[0].add(eudcFontFile);
1032             fontRegInfo[1].add(null);
1033         }
1034 
1035         // Add Lucida Sans Regular to Windows fallback list
1036         fontRegInfo[0].add(jreFontDir + jreDefaultFontFile);
1037         fontRegInfo[1].add(jreDefaultFont);
1038 
1039         if (PlatformUtil.isWinVistaOrLater()) {
1040             // CJK Ext B Supplementary character fallbacks.
1041             fontRegInfo[0].add(getPathNameWindows("mingliub.ttc"));
1042             fontRegInfo[1].add("MingLiU-ExtB");
1043 
1044             if (PlatformUtil.isWin7OrLater()) {
1045                 // Add Segoe UI Symbol to Windows 7 or later fallback list
1046                 fontRegInfo[0].add(getPathNameWindows("seguisym.ttf"));
1047                 fontRegInfo[1].add("Segoe UI Symbol");
1048             } else {
1049                 // Add Cambria Math to Windows Vista fallback list
1050                 fontRegInfo[0].add(getPathNameWindows("cambria.ttc"));
1051                 fontRegInfo[1].add("Cambria Math");
1052             }
1053         }
1054         return fontRegInfo;
1055     }
1056 
1057     /* This is needed since some windows registry names don't match
1058      * the font names.
1059      * - UPC styled font names have a double space, but the
1060      * registry entry mapping to a file doesn't.
1061      * - Marlett is in a hidden file not listed in the registry
1062      * - The registry advertises that the file david.ttf contains a
1063      * font with the full name "David Regular" when in fact its
1064      * just "David".
1065      * Directly fix up these known cases as this is faster.
1066      * If a font which doesn't match these known cases has no file,
1067      * it may be a font that has been temporarily added to the known set
1068      * or it may be an installed font with a missing registry entry.
1069      * Installed fonts are those in the windows font directories.
1070      * Make a best effort attempt to locate these.
1071      * We obtain the list of TrueType fonts in these directories and
1072      * filter out all the font files we already know about from the registry.
1073      * What remains may be "bad" fonts, duplicate fonts, or perhaps the
1074      * missing font(s) we are looking for.
1075      * Open each of these files to find out.
1076      */
1077     private void resolveWindowsFonts
1078         (HashMap<String,String> fontToFileMap,
1079          HashMap<String,String> fontToFamilyNameMap,
1080          HashMap<String,ArrayList<String>> familyToFontListMap) {
1081 
1082         ArrayList<String> unmappedFontNames = null;
1083         for (String font : fontToFamilyNameMap.keySet()) {
1084             String file = fontToFileMap.get(font);
1085             if (file == null) {
1086                 int dsi = font.indexOf("  ");
1087                 if (dsi > 0) {
1088                     String newName = font.substring(0, dsi);
1089                     newName = newName.concat(font.substring(dsi+1));
1090                     file = fontToFileMap.get(newName);
1091                     /* If this name exists and isn't for a valid name
1092                      * replace the mapping to the file with this font
1093                      */
1094                     if (file != null &&
1095                         !fontToFamilyNameMap.containsKey(newName)) {
1096                         fontToFileMap.remove(newName);
1097                         fontToFileMap.put(font, file);
1098                     }
1099                 } else if (font.equals("marlett")) {
1100                     fontToFileMap.put(font, "marlett.ttf");
1101                 } else if (font.equals("david")) {
1102                     file = fontToFileMap.get("david regular");
1103                     if (file != null) {
1104                         fontToFileMap.remove("david regular");
1105                         fontToFileMap.put("david", file);
1106                     }
1107                 } else {
1108                     if (unmappedFontNames == null) {
1109                         unmappedFontNames = new ArrayList<String>();
1110                     }
1111                     unmappedFontNames.add(font);
1112                 }
1113             }
1114         }
1115 
1116         if (unmappedFontNames != null) {
1117             HashSet<String> unmappedFontFiles = new HashSet<String>();
1118 
1119             // Used HashMap.clone() on SE but TV didn't support it.
1120             HashMap<String,String> ffmapCopy = new HashMap<String,String>();
1121             ffmapCopy.putAll(fontToFileMap);
1122             for (String key : fontToFamilyNameMap.keySet()) {
1123                 ffmapCopy.remove(key);
1124             }
1125             for (String key : ffmapCopy.keySet()) {
1126                 unmappedFontFiles.add(ffmapCopy.get(key));
1127                 fontToFileMap.remove(key);
1128             }
1129             resolveFontFiles(unmappedFontFiles,
1130                              unmappedFontNames,
1131                              fontToFileMap,
1132                              fontToFamilyNameMap,
1133                              familyToFontListMap);
1134 
1135             /* remove from the set of names that will be returned to the
1136              * user any fonts that can't be mapped to files.
1137              */
1138             if (unmappedFontNames.size() > 0) {
1139                 int sz = unmappedFontNames.size();
1140                 for (int i=0; i<sz; i++) {
1141                     String name = unmappedFontNames.get(i);
1142                     String familyName = fontToFamilyNameMap.get(name);
1143                     if (familyName != null) {
1144                         ArrayList<String> family = familyToFontListMap.get(familyName);
1145                         if (family != null) {
1146                             if (family.size() <= 1) {
1147                                 familyToFontListMap.remove(familyName);
1148                             }
1149                         }
1150                     }
1151                     fontToFamilyNameMap.remove(name);
1152                 }
1153             }
1154         }
1155     }
1156 
1157     private void resolveFontFiles(HashSet<String> unmappedFiles,
1158          ArrayList<String> unmappedFonts,
1159          HashMap<String,String> fontToFileMap,
1160          HashMap<String,String> fontToFamilyNameMap,
1161          HashMap<String,ArrayList<String>> familyToFontListMap) {
1162 
1163         for (String file : unmappedFiles) {
1164             try {
1165                 int fn = 0;
1166                 PrismFontFile ttf;
1167                 String fullPath = getPathNameWindows(file);
1168                 do {
1169                     ttf = createFontResource(fullPath, fn++);
1170                     if (ttf == null) {
1171                         break;
1172                     }
1173                     String fontNameLC = ttf.getFullName().toLowerCase();
1174                     String localeNameLC =ttf.getLocaleFullName().toLowerCase();
1175                     if (unmappedFonts.contains(fontNameLC) ||
1176                         unmappedFonts.contains(localeNameLC)) {
1177                         fontToFileMap.put(fontNameLC, file);
1178                         unmappedFonts.remove(fontNameLC);
1179                         /* If GDI reported names using locale specific style
1180                          * strings we'll have those as the unmapped keys in
1181                          * the font to family list and also in the value
1182                          * array list mapped by the family.
1183                          * We can spot these if the localeName is what is
1184                          * actually in the unmapped font list, and we'll
1185                          * then replace all occurrences of the locale name with
1186                          * the English name.
1187                          */
1188                         if (unmappedFonts.contains(localeNameLC)) {
1189                             unmappedFonts.remove(localeNameLC);
1190                             String family = ttf.getFamilyName();
1191                             String familyLC = family.toLowerCase();
1192                             fontToFamilyNameMap.remove(localeNameLC);
1193                             fontToFamilyNameMap.put(fontNameLC, family);
1194                             ArrayList<String> familylist =
1195                                 familyToFontListMap.get(familyLC);
1196                             if (familylist != null) {
1197                                 familylist.remove(ttf.getLocaleFullName());
1198                             } else {
1199                                 /* The family name was not English.
1200                                  * Remove the non-English family list
1201                                  * and replace it with the English one
1202                                  */
1203                                 String localeFamilyLC =
1204                                     ttf.getLocaleFamilyName().toLowerCase();
1205                                 familylist =
1206                                     familyToFontListMap.get(localeFamilyLC);
1207                                 if (familylist != null) {
1208                                     familyToFontListMap.remove(localeFamilyLC);
1209                                 }
1210                                 familylist = new ArrayList<String>();
1211                                 familyToFontListMap.put(familyLC, familylist);
1212                             }
1213                             familylist.add(ttf.getFullName());
1214                         }
1215                     }
1216 
1217                 }
1218                 while (fn < ttf.getFontCount());
1219             } catch (Exception e) {
1220                 if (debugFonts) {
1221                     e.printStackTrace();
1222                 }
1223             }
1224         }
1225     }
1226 
1227     static native void
1228         populateFontFileNameMap(HashMap<String,String> fontToFileMap,
1229                                  HashMap<String,String> fontToFamilyNameMap,
1230                                  HashMap<String,ArrayList<String>>
1231                                      familyToFontListMap,
1232                                  Locale locale);
1233 
1234     static String getPathNameWindows(final String filename) {
1235         if (filename == null) {
1236             return null;
1237         }
1238 
1239         getPlatformFontDirs();
1240         File f = new File(filename);
1241         if (f.isAbsolute()) {
1242             return filename;
1243         }
1244         if (userFontDir == null) {
1245             return sysFontDir+"\\"+filename;
1246         }
1247 
1248         String path = AccessController.doPrivileged(
1249             new PrivilegedAction<String>() {
1250                 public String run() {
1251                     File f = new File(sysFontDir+"\\"+filename);
1252                     if (f.exists()) {
1253                         return f.getAbsolutePath();
1254                     }
1255                     else {
1256                         return userFontDir+"\\"+filename;
1257                     }
1258                 }
1259             });
1260 
1261             if (path != null) {
1262                 return path;
1263             }
1264         return null; //  shouldn't happen.
1265     }
1266 
1267     private static ArrayList<String> allFamilyNames;
1268     public String[] getFontFamilyNames() {
1269         if (allFamilyNames == null) {
1270             /* Create an array list and add the families for :
1271              * - logical fonts
1272              * - Embedded fonts
1273              * - Fonts found on the platform (includes JRE fonts)..
1274              */
1275             ArrayList<String> familyNames = new ArrayList<String>();
1276             LogicalFont.addFamilies(familyNames);
1277             //  Putting this in here is dependendent on the FontLoader
1278             // loading embedded fonts before calling into here. If
1279             // embedded fonts can be added then we need to add these
1280             // dynamically for each call to this method.
1281 
1282             if (embeddedFonts != null) {
1283                 for (PrismFontFile embeddedFont : embeddedFonts.values()) {
1284                     if (!familyNames.contains(embeddedFont.getFamilyName()))
1285                         familyNames.add(embeddedFont.getFamilyName());
1286                 }
1287             }
1288             getFullNameToFileMap();
1289             for (String f : fontToFamilyNameMap.values()) {
1290                 if (!familyNames.contains(f)) {
1291                     familyNames.add(f);
1292                 }
1293             }
1294             Collections.sort(familyNames);
1295             allFamilyNames = new ArrayList<String>(familyNames);
1296         }
1297         return allFamilyNames.toArray(STR_ARRAY);
1298     }
1299 
1300     private static ArrayList<String> allFontNames;
1301     public String[] getFontFullNames() {
1302         if (allFontNames == null) {
1303             /* Create an array list and add
1304              * - logical fonts
1305              * - Embedded fonts
1306              * - Fonts found on the platform (includes JRE fonts).
1307              */
1308             ArrayList<String> fontNames = new ArrayList<String>();
1309             LogicalFont.addFullNames(fontNames);
1310             if (embeddedFonts != null) {
1311                 for (PrismFontFile embeddedFont : embeddedFonts.values()) {
1312                     if (!fontNames.contains(embeddedFont.getFullName())) {
1313                         fontNames.add(embeddedFont.getFullName());
1314                     }
1315                 }
1316             }
1317             getFullNameToFileMap();
1318             for (ArrayList<String> a : familyToFontListMap.values()) {
1319                 for (String s : a) {
1320                     fontNames.add(s);
1321                 }
1322             }
1323             Collections.sort(fontNames);
1324             allFontNames = fontNames;
1325         }
1326         return allFontNames.toArray(STR_ARRAY);
1327     }
1328 
1329     public String[] getFontFullNames(String family) {
1330 
1331         // First check if its a logical font family.
1332         String[] logFonts = LogicalFont.getFontsInFamily(family);
1333         if (logFonts != null) {
1334             // Caller will clone/wrap this before returning it to API
1335             return logFonts;
1336         }
1337         // Next check if its an embedded font family
1338         if (embeddedFonts != null) {
1339             ArrayList<String> embeddedFamily = null;
1340             for (PrismFontFile embeddedFont : embeddedFonts.values()) {
1341                 if (embeddedFont.getFamilyName().equalsIgnoreCase(family)) {
1342                     if (embeddedFamily == null) {
1343                         embeddedFamily = new ArrayList<String>();
1344                     }
1345                     embeddedFamily.add(embeddedFont.getFullName());
1346                 }
1347             }
1348             if (embeddedFamily != null) {
1349                 return embeddedFamily.toArray(STR_ARRAY);
1350             }
1351         }
1352 
1353         getFullNameToFileMap();
1354         family = family.toLowerCase();
1355         ArrayList<String> familyFonts = familyToFontListMap.get(family);
1356         if (familyFonts != null) {
1357             return familyFonts.toArray(STR_ARRAY);
1358         } else {
1359             return STR_ARRAY; // zero-length therefore immutable.
1360         }
1361     }
1362 
1363     public final int getSubPixelMode() {
1364         return subPixelMode;
1365     }
1366 
1367     public boolean isLCDTextSupported() {
1368         return lcdEnabled;
1369     }
1370 
1371     @Override
1372     public boolean isPlatformFont(String name) {
1373         if (name == null) return false;
1374         /* Using String#startsWith as name can be either a fullName or a family name */
1375         String lcName = name.toLowerCase();
1376         if (LogicalFont.isLogicalFont(lcName)) return true;
1377         if (lcName.startsWith("lucida sans")) return true;
1378         String systemFamily = getSystemFont(LogicalFont.SYSTEM).toLowerCase();
1379         if (lcName.startsWith(systemFamily)) return true;
1380         return false;
1381     }
1382 
1383     public static boolean isJreFont(FontResource fr) {
1384         String file = fr.getFileName();
1385         return file.startsWith(jreFontDir);
1386     }
1387 
1388     public static float getLCDContrast() {
1389         if (lcdContrast == -1) {
1390             if (isWindows) {
1391                 lcdContrast = getLCDContrastWin32() / 1000f;
1392             } else {
1393                 /* REMIND: When using CoreText it likely already applies gamma
1394                  * correction to the glyph images. The current implementation does
1395                  * not take this into account when rasterizing the glyph. Thus,
1396                  * it is possible gamma correction is been applied twice to the
1397                  * final result.
1398                  * Consider using "1" for lcdContrast possibly produces visually
1399                  * more appealing results (although not strictly correct).
1400                  */
1401                 lcdContrast = 1.3f;
1402             }
1403         }
1404         return lcdContrast;
1405     }
1406 
1407     private static Thread fileCloser = null;
1408 
1409     private synchronized void addFileCloserHook() {
1410         if (fileCloser == null) {
1411             final Runnable fileCloserRunnable = () -> {
1412                 if (embeddedFonts != null) {
1413                     for (PrismFontFile font : embeddedFonts.values()) {
1414                         font.disposeOnShutdown();
1415                     }
1416                 }
1417                 if (tmpFonts != null) {
1418                     for (WeakReference<PrismFontFile> ref : tmpFonts) {
1419                         PrismFontFile font = ref.get();
1420                         if (font != null) {
1421                             font.disposeOnShutdown();
1422                         }
1423                     }
1424                 }
1425             };
1426             java.security.AccessController.doPrivileged(
1427                     (PrivilegedAction<Object>) () -> {
1428                         /* The thread must be a member of a thread group
1429                          * which will not get GCed before VM exit.
1430                          * Make its parent the top-level thread group.
1431                          */
1432                         ThreadGroup tg = Thread.currentThread().getThreadGroup();
1433                         for (ThreadGroup tgn = tg;
1434                              tgn != null; tg = tgn, tgn = tg.getParent());
1435                         fileCloser = new Thread(tg, fileCloserRunnable);
1436                         fileCloser.setContextClassLoader(null);
1437                         Runtime.getRuntime().addShutdownHook(fileCloser);
1438                         return null;
1439                     }
1440             );
1441         }
1442     }
1443 
1444     private HashMap<String, PrismFontFile> embeddedFonts;
1445 
1446     public PGFont[] loadEmbeddedFont(String name, InputStream fontStream,
1447                                      float size,
1448                                      boolean register,
1449                                      boolean loadAll) {
1450         if (!hasPermission()) {
1451             return new PGFont[] { createFont(DEFAULT_FULLNAME, size) } ;
1452         }
1453         if (FontFileWriter.hasTempPermission()) {
1454             return loadEmbeddedFont0(name, fontStream, size, register, loadAll);
1455         }
1456 
1457         // Otherwise, be extra conscious of pending temp file creation and
1458         // resourcefully handle the temp file resources, among other things.
1459         FontFileWriter.FontTracker tracker =
1460             FontFileWriter.FontTracker.getTracker();
1461         boolean acquired = false;
1462         try {
1463             acquired = tracker.acquirePermit();
1464             if (!acquired) {
1465                 // Timed out waiting for resources.
1466                 return null;
1467             }
1468             return loadEmbeddedFont0(name, fontStream, size, register, loadAll);
1469         } catch (InterruptedException e) {
1470             // Interrupted while waiting to acquire a permit.
1471             return null;
1472         } finally {
1473             if (acquired) {
1474                 tracker.releasePermit();
1475             }
1476         }
1477     }
1478 
1479     private PGFont[] loadEmbeddedFont0(String name, InputStream fontStream,
1480                                        float size,
1481                                        boolean register,
1482                                        boolean loadAll) {
1483         PrismFontFile[] fr = null;
1484         FontFileWriter fontWriter = new FontFileWriter();
1485         try {
1486             // We use a shutdown hook to close all open tmp files
1487             // created via this API and delete them.
1488             final File tFile = fontWriter.openFile();
1489             byte[] buf = new byte[8192];
1490             for (;;) {
1491                 int bytesRead = fontStream.read(buf);
1492                 if (bytesRead < 0) {
1493                     break;
1494                 }
1495                 fontWriter.writeBytes(buf, 0, bytesRead);
1496             }
1497             fontWriter.closeFile();
1498 
1499             fr = loadEmbeddedFont1(name, tFile.getPath(), register, true,
1500                                    fontWriter.isTracking(), loadAll);
1501 
1502             if (fr != null && fr.length > 0) {
1503                 /* Delete the file downloaded if it was decoded
1504                  * to another file */
1505                 if (fr[0].isDecoded()) {
1506                     fontWriter.deleteFile();
1507                 }
1508             }
1509 
1510             /* We don't want to leave the temp files around after exit.
1511              * Also in a shared applet-type context, after all references to
1512              * the applet and therefore the font are dropped, the file
1513              * should be removed. This isn't so much an issue so long as
1514              * the VM exists to serve a single FX app, but will be
1515              * important in an app-context model.
1516              * But also fonts that are over-written by new versions
1517              * need to be cleaned up and that applies even in the single
1518              * context.
1519              * We also need to decrement the byte count by the size
1520              * of the file.
1521              */
1522             addFileCloserHook();
1523         } catch (Exception e) {
1524             fontWriter.deleteFile();
1525         } finally {
1526             /* If the data isn't a valid font, so that registering it
1527              * returns null, or we didn't get so far as copying the data,
1528              * delete the tmp file and decrement the byte count
1529              * in the tracker object before returning.
1530              */
1531             if (fr == null) {
1532                 fontWriter.deleteFile();
1533             }
1534         }
1535         if (fr != null && fr.length > 0) {
1536             if (size <= 0) size = getSystemFontSize();
1537             int num = fr.length;
1538             PrismFont[] pFonts = new PrismFont[num];
1539             for (int i=0; i<num; i++) {
1540                 pFonts[i] = new PrismFont(fr[i], fr[i].getFullName(), size);
1541             }
1542             return pFonts;
1543         }
1544         return null;
1545     }
1546 
1547     /**
1548      * registerEmbeddedFont(String name, String path) is a small subset of
1549      * registerEmbeddedFont(String name, InputStream fontStream)
1550      * It does not attempt to create a temporary file and has different
1551      * parameters.
1552      *
1553      * @param name font name
1554      * @param path Path name to system file
1555      * @param size font size
1556      * @param register whether the font should be registered.
1557      * @param loadAll whether to load all fonts if it is a TTC
1558      * @return font name extracted from font file
1559      */
1560     public PGFont[] loadEmbeddedFont(String name, String path,
1561                                      float size,
1562                                      boolean register,
1563                                      boolean loadAll) {
1564         if (!hasPermission()) {
1565             return new PGFont[] { createFont(DEFAULT_FULLNAME, size) };
1566         }
1567         addFileCloserHook();
1568         FontResource[] frArr =
1569           loadEmbeddedFont1(name, path, register, false, false, loadAll);
1570         if (frArr != null && frArr.length > 0) {
1571             if (size <= 0) size = getSystemFontSize();
1572             int num = frArr.length;
1573             PGFont[] pgFonts = new PGFont[num];
1574             for (int i=0; i<num; i++) {
1575                 pgFonts[i] =
1576                     new PrismFont(frArr[i], frArr[i].getFullName(), size);
1577             }
1578             return pgFonts;
1579         }
1580         return null;
1581     }
1582 
1583     /* This should make the embedded font eligible for reclaimation
1584      * and subsequently, disposal of native resources, once any existing
1585      * strong refs by the application are released.
1586      */
1587     private void removeEmbeddedFont(String name) {
1588         PrismFontFile font = embeddedFonts.get(name);
1589         if (font == null) {
1590             return;
1591         }
1592         embeddedFonts.remove(name);
1593         String lcName = name.toLowerCase();
1594         fontResourceMap.remove(lcName);
1595         compResourceMap.remove(lcName);
1596         // The following looks tedious, but if the compMap could have
1597         // the font referenced via some lookup name that applies a style
1598         // or used the family name, we need to find it and remove all
1599         // references to it, so it can be collected.
1600         Iterator<CompositeFontResource> fi = compResourceMap.values().iterator();
1601             while (fi.hasNext()) {
1602             CompositeFontResource compFont = fi.next();
1603             if (compFont.getSlotResource(0) == font) {
1604                 fi.remove();
1605             }
1606         }
1607     }
1608 
1609     protected boolean registerEmbeddedFont(String path) {
1610         return true;
1611     }
1612 
1613     // Used for testing
1614     private int numEmbeddedFonts = 0;
1615     public int test_getNumEmbeddedFonts() {
1616         return numEmbeddedFonts;
1617     }
1618 
1619     private synchronized
1620         PrismFontFile[] loadEmbeddedFont1(String name, String path,
1621                                           boolean register, boolean copy,
1622                                           boolean tracked, boolean loadAll) {
1623 
1624         ++numEmbeddedFonts;
1625         /*
1626          * Fonts that aren't platform installed include those in the
1627          * application jar, WOFF fonts that are downloaded, and fonts
1628          * created via Font.loadFont. If copy==true, we can infer its
1629          * one of these, but its also possible for a font to be file-system
1630          * installed as part of the application but not known to the
1631          * platform. In this case copy==false, but we still need to flag
1632          * to the system its not a platform font so that other pipelines
1633          * know to reference the file directly.
1634          */
1635         PrismFontFile[] frArr = createFontResources(name, path, register,
1636                                                     true, copy, tracked,
1637                                                     loadAll);
1638         if (frArr == null || frArr.length == 0) {
1639             return null; // yes, this means the caller needs to handle null.
1640         }
1641 
1642         /* Before we return or register, make sure names are present
1643          * check whether any of the fonts duplicate an OS font.
1644          */
1645 
1646         if (embeddedFonts == null) {
1647             embeddedFonts = new HashMap<String, PrismFontFile>();
1648         }
1649 
1650         boolean registerEmbedded = true;
1651         for (int i=0; i<frArr.length; i++) {
1652             PrismFontFile fr = frArr[i];
1653             String family = fr.getFamilyName();
1654             if (family == null || family.length() == 0) return null;
1655             String fullname = fr.getFullName();
1656             if (fullname == null || fullname.length() == 0) return null;
1657             String psname = fr.getPSName();
1658             if (psname == null || psname.length() == 0) return null;
1659 
1660             FontResource resource = embeddedFonts.get(fullname);
1661             if (resource != null && fr.equals(resource)) {
1662                 /* Do not register the same font twice in the OS */
1663                 registerEmbedded = false;
1664             }
1665         }
1666 
1667         if (registerEmbedded) {
1668             /* Use filename from the resource so woff fonts are handled */
1669             if (!registerEmbeddedFont(frArr[0].getFileName())) {
1670                 /* This font file can't be used by the underlying rasterizer */
1671                 return null;
1672             }
1673         }
1674 
1675         /* If a temporary font is a copy but it is not decoded then it
1676          * will not be anywhere the shutdown hook can see.
1677          * That means if the font is keep for the entire life of the VM
1678          * its file will not be deleted.
1679          * The fix is to add this font to the list of temporary fonts.
1680          */
1681         if (copy && !frArr[0].isDecoded()) {
1682             addTmpFont(frArr[0]);
1683         }
1684 
1685         if (!register) {
1686             return frArr;
1687         }
1688 
1689         /* If a font name is provided then we will also store that in the
1690          * map as an alias, otherwise should use the only the real name,
1691          * REMIND: its possible that either name may hide some installed
1692          * version of the font, possibly one we haven't seen yet. But
1693          * without loading all the platform fonts (expensive) this is
1694          * difficult to ascertain. A contains() check here is therefore
1695          * probably mostly futile.
1696          */
1697         if (name != null && !name.isEmpty()) {
1698             embeddedFonts.put(name, frArr[0]);
1699             storeInMap(name, frArr[0]);
1700         }
1701 
1702         for (int i=0; i<frArr.length; i++) {
1703             PrismFontFile fr = frArr[i];
1704             String family = fr.getFamilyName();
1705             String fullname = fr.getFullName();
1706             removeEmbeddedFont(fullname);
1707             embeddedFonts.put(fullname, fr);
1708             storeInMap(fullname, fr);
1709             family = family + dotStyleStr(fr.isBold(), fr.isItalic());
1710             storeInMap(family, fr);
1711             /* The remove call is to assist the case where we have
1712              * previously mapped into the composite map a different style
1713              * in this family as a partial match for the application request.
1714              * This can occur when an application requested a bold font before
1715              * it called Font.loadFont to register the bold font. It won't
1716              * fix the cases that already happened, but will fix the future ones.
1717              */
1718             compResourceMap.remove(family.toLowerCase());
1719         }
1720         return frArr;
1721     }
1722 
1723     private void
1724         logFontInfo(String message,
1725                     HashMap<String,String> fontToFileMap,
1726                     HashMap<String,String> fontToFamilyNameMap,
1727                     HashMap<String,ArrayList<String>> familyToFontListMap) {
1728 
1729         System.err.println(message);
1730         for (String keyName : fontToFileMap.keySet()) {
1731             System.err.println("font="+keyName+" file="+
1732                                fontToFileMap.get(keyName));
1733         }
1734         for (String keyName : fontToFamilyNameMap.keySet()) {
1735             System.err.println("font="+keyName+" family="+
1736                                fontToFamilyNameMap.get(keyName));
1737         }
1738         for (String keyName : familyToFontListMap.keySet()) {
1739             System.err.println("family="+keyName+ " fonts="+
1740                                familyToFontListMap.get(keyName));
1741         }
1742     }
1743 
1744     private synchronized HashMap<String,String> getFullNameToFileMap() {
1745         if (fontToFileMap == null) {
1746 
1747             HashMap<String, String>
1748                 tmpFontToFileMap = new HashMap<String,String>(100);
1749             fontToFamilyNameMap = new HashMap<String,String>(100);
1750             familyToFontListMap = new HashMap<String,ArrayList<String>>(50);
1751             fileToFontMap = new HashMap<String,String>(100);
1752 
1753             if (isWindows) {
1754                 getPlatformFontDirs();
1755                 populateFontFileNameMap(tmpFontToFileMap,
1756                                         fontToFamilyNameMap,
1757                                         familyToFontListMap,
1758                                         Locale.ENGLISH);
1759 
1760                 if (debugFonts) {
1761                     System.err.println("Windows Locale ID=" + getSystemLCID());
1762                     logFontInfo(" *** WINDOWS FONTS BEFORE RESOLVING",
1763                                 tmpFontToFileMap,
1764                                 fontToFamilyNameMap,
1765                                 familyToFontListMap);
1766                 }
1767 
1768                 resolveWindowsFonts(tmpFontToFileMap,
1769                                     fontToFamilyNameMap,
1770                                     familyToFontListMap);
1771 
1772                 if (debugFonts) {
1773                     logFontInfo(" *** WINDOWS FONTS AFTER RESOLVING",
1774                                 tmpFontToFileMap,
1775                                 fontToFamilyNameMap,
1776                                 familyToFontListMap);
1777                 }
1778 
1779             } else if (isMacOSX || isIOS) {
1780                 MacFontFinder.populateFontFileNameMap(tmpFontToFileMap,
1781                                                       fontToFamilyNameMap,
1782                                                       familyToFontListMap,
1783                                                       Locale.ENGLISH);
1784 
1785             } else if (isLinux) {
1786                 FontConfigManager.populateMaps(tmpFontToFileMap,
1787                                                fontToFamilyNameMap,
1788                                                familyToFontListMap,
1789                                                Locale.getDefault());
1790                 if (debugFonts) {
1791                     logFontInfo(" *** FONTCONFIG LOCATED FONTS:",
1792                                 tmpFontToFileMap,
1793                                 fontToFamilyNameMap,
1794                                 familyToFontListMap);
1795                 }
1796             } else if (isAndroid) {
1797                AndroidFontFinder.populateFontFileNameMap(tmpFontToFileMap,
1798                         fontToFamilyNameMap,
1799                         familyToFontListMap,
1800                         Locale.ENGLISH);
1801            } else { /* unrecognised OS */
1802                 fontToFileMap = tmpFontToFileMap;
1803                 return fontToFileMap;
1804             }
1805 
1806             /* Reverse map from file to font. file name is base name
1807              * not a full path.
1808              */
1809             for (String font : tmpFontToFileMap.keySet()) {
1810                 String file = tmpFontToFileMap.get(font);
1811                 fileToFontMap.put(file.toLowerCase(), font);
1812             }
1813 
1814             fontToFileMap = tmpFontToFileMap;
1815             if (isAndroid) {
1816                 populateFontFileNameMapGeneric(
1817                        AndroidFontFinder.getSystemFontsDir());
1818             }
1819             populateFontFileNameMapGeneric(jreFontDir);
1820 
1821 //             for (String keyName : fontToFileMap.keySet()) {
1822 //               System.out.println("font="+keyName+" file="+ fontToFileMap.get(keyName));
1823 //             }
1824 
1825 //             for (String keyName : familyToFontListMap.keySet()) {
1826 //               System.out.println("family="+keyName);
1827 //             }
1828         }
1829         return fontToFileMap;
1830     }
1831 
1832     public final boolean hasPermission() {
1833         try {
1834             SecurityManager sm = System.getSecurityManager();
1835             if (sm != null) {
1836                 sm.checkPermission(LOAD_FONT_PERMISSION);
1837             }
1838             return true;
1839         } catch (SecurityException ex) {
1840             return false;
1841         }
1842     }
1843 
1844     private static class TTFilter implements FilenameFilter {
1845         public boolean accept(File dir,String name) {
1846             /* all conveniently have the same suffix length */
1847             int offset = name.length()-4;
1848             if (offset <= 0) { /* must be at least A.ttf */
1849                 return false;
1850             } else {
1851                 return(name.startsWith(".ttf", offset) ||
1852                        name.startsWith(".TTF", offset) ||
1853                        name.startsWith(".ttc", offset) ||
1854                        name.startsWith(".TTC", offset) ||
1855                        name.startsWith(".otf", offset) ||
1856                        name.startsWith(".OTF", offset));
1857             }
1858         }
1859 
1860         private TTFilter() {
1861         }
1862 
1863         static TTFilter ttFilter;
1864         static TTFilter getInstance() {
1865             if (ttFilter == null) {
1866                 ttFilter = new TTFilter();
1867             }
1868             return ttFilter;
1869         }
1870     }
1871 
1872     void addToMaps(PrismFontFile fr) {
1873 
1874         if (fr == null) {
1875             return;
1876         }
1877 
1878         String fullName = fr.getFullName();
1879         String familyName = fr.getFamilyName();
1880 
1881         if (fullName == null || familyName == null) {
1882             return;
1883         }
1884 
1885         String lcFullName = fullName.toLowerCase();
1886         String lcFamilyName = familyName.toLowerCase();
1887 
1888         fontToFileMap.put(lcFullName, fr.getFileName());
1889         fontToFamilyNameMap.put(lcFullName, familyName);
1890         ArrayList<String> familyList = familyToFontListMap.get(lcFamilyName);
1891         if (familyList == null) {
1892             familyList = new ArrayList<String>();
1893             familyToFontListMap.put(lcFamilyName, familyList);
1894         }
1895         familyList.add(fullName);
1896     }
1897 
1898     void populateFontFileNameMapGeneric(String fontDir) {
1899         final File dir = new File(fontDir);
1900         String[] files = null;
1901         try {
1902             files = AccessController.doPrivileged(
1903                     (PrivilegedExceptionAction<String[]>) () -> dir.list(TTFilter.getInstance())
1904             );
1905         } catch (Exception e) {
1906         }
1907 
1908         if (files == null) {
1909             return;
1910         }
1911 
1912         for (int i=0;i<files.length;i++) {
1913             try {
1914                 String path = fontDir+File.separator+files[i];
1915 
1916                 /* Use filename from the resource so woff fonts are handled */
1917                 if (!registerEmbeddedFont(path)) {
1918                     /* This font file can't be used by the underlying rasterizer */
1919                     continue;
1920                 }
1921 
1922                 int index = 0;
1923                 PrismFontFile fr = createFontResource(path, index++);
1924                 if (fr == null) {
1925                     continue;
1926                 }
1927                 addToMaps(fr);
1928                 while (index < fr.getFontCount()) {
1929                     fr = createFontResource(path, index++);
1930                     if (fr == null) {
1931                         break;
1932                     }
1933                     addToMaps(fr);
1934                 }
1935             } catch (Exception e) {
1936                 /* Keep going if anything bad happens with a font */
1937             }
1938         }
1939     }
1940 
1941     static native int getLCDContrastWin32();
1942     private static native float getSystemFontSizeNative();
1943     private static native String getSystemFontNative();
1944     private static float systemFontSize;
1945     private static String systemFontFamily = null;
1946     private static String monospaceFontFamily = null;
1947 
1948     public static float getSystemFontSize() {
1949         if (systemFontSize == -1) {
1950             if (isWindows) {
1951                 systemFontSize = getSystemFontSizeNative();
1952             } else if (isMacOSX || isIOS) {
1953                 systemFontSize = MacFontFinder.getSystemFontSize();
1954             } else if (isAndroid) {
1955                systemFontSize = AndroidFontFinder.getSystemFontSize();
1956             } else if (isEmbedded) {
1957                 try {
1958                     int screenDPI = Screen.getMainScreen().getResolutionY();
1959                     systemFontSize = ((float) screenDPI) / 6f; // 12 points
1960                 } catch (NullPointerException npe) {
1961                     // if no screen is defined
1962                     systemFontSize = 13f; // same as desktop Linux
1963                 }
1964             } else {
1965                 systemFontSize = 13f; // Gnome uses 13.
1966             }
1967         }
1968         return systemFontSize;
1969     }
1970 
1971     /* Applies to Windows and Mac. Not used on Linux */
1972     public static String getSystemFont(String name) {
1973         if (name.equals(LogicalFont.SYSTEM)) {
1974             if (systemFontFamily == null) {
1975                 if (isWindows) {
1976                     systemFontFamily = getSystemFontNative();
1977                     if (systemFontFamily == null) {
1978                         systemFontFamily = "Arial"; // play it safe.
1979                     }
1980                 } else if (isMacOSX || isIOS) {
1981                     systemFontFamily = MacFontFinder.getSystemFont();
1982                     if (systemFontFamily == null) {
1983                         systemFontFamily = "Lucida Grande";
1984                     }
1985                 } else if (isAndroid) {
1986                    systemFontFamily = AndroidFontFinder.getSystemFont();
1987                 } else {
1988                     systemFontFamily = "Lucida Sans"; // for now.
1989                 }
1990             }
1991             return systemFontFamily;
1992         } else if (name.equals(LogicalFont.SANS_SERIF)) {
1993             return "Arial";
1994         } else if (name.equals(LogicalFont.SERIF)) {
1995             return "Times New Roman";
1996         } else /* if (name.equals(LogicalFont.MONOSPACED)) */ {
1997             if (monospaceFontFamily == null) {
1998                 if (isMacOSX) {
1999                     /* This code is intentionally commented:
2000                      * On the OS X the preferred monospaced font is Monaco,
2001                      * although this can be a good choice for most Mac application
2002                      * it is not suitable for JavaFX because Monaco does not
2003                      * have neither bold nor italic.
2004                      */
2005 //                    monospaceFontFamily = MacFontFinder.getMonospacedFont();
2006                 }
2007             }
2008             if (monospaceFontFamily == null) {
2009                 monospaceFontFamily = "Courier New";
2010             }
2011             return monospaceFontFamily;
2012         }
2013     }
2014 
2015     /* Called from PrismFontFile which caches the return value */
2016     static native short getSystemLCID();
2017 }