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