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