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