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