All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.javafx.font.PrismFontFactory Maven / Gradle / Ivy

There is a newer version: 24-ea+15
Show newest version
/*
 * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.font;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;

import com.sun.glass.ui.Screen;
import com.sun.glass.utils.NativeLibLoader;
import com.sun.javafx.PlatformUtil;
import com.sun.javafx.text.GlyphLayout;
import static com.sun.javafx.FXPermissions.LOAD_FONT_PERMISSION;

public abstract class PrismFontFactory implements FontFactory {

    public static final boolean debugFonts;
    public static final boolean isWindows;
    public static final boolean isLinux;
    public static final boolean isMacOSX;
    public static final boolean isIOS;
    public static final boolean isAndroid;
    public static final boolean isEmbedded;
    public static final int cacheLayoutSize;
    private static int subPixelMode;
    public static final int SUB_PIXEL_ON = 1;
    public static final int SUB_PIXEL_Y = 2;
    public static final int SUB_PIXEL_NATIVE = 4;
    private static float fontSizeLimit = 80f;

    private static boolean lcdEnabled;
    private static float lcdContrast = -1;
    private static String jreFontDir;
    private static final String jreDefaultFont   = "Lucida Sans Regular";
    private static final String jreDefaultFontLC = "lucida sans regular";
    private static final String jreDefaultFontFile = "LucidaSansRegular.ttf";
    private static final String CT_FACTORY = "com.sun.javafx.font.coretext.CTFactory";
    private static final String DW_FACTORY = "com.sun.javafx.font.directwrite.DWFactory";
    private static final String FT_FACTORY = "com.sun.javafx.font.freetype.FTFactory";

    /* We need two maps. One to hold pointers to the raw fonts, another
     * to hold pointers to the composite resources. Top level look ups
     * to createFont() will look first in the compResourceMap, and
     * only go to the second map to create a wrapped resource.
     * Logical Fonts are handled separately.
     */
    HashMap fontResourceMap = new HashMap<>();

    HashMap compResourceMap = new HashMap<>();

    static {
        isWindows = PlatformUtil.isWindows();
        isMacOSX  = PlatformUtil.isMac();
        isLinux   = PlatformUtil.isLinux();
        isIOS     = PlatformUtil.isIOS();
        isAndroid = PlatformUtil.isAndroid();
        isEmbedded = PlatformUtil.isEmbedded();
        int[] tempCacheLayoutSize = {0x10000};

        @SuppressWarnings("removal")
        boolean tmp = AccessController.doPrivileged(
                (PrivilegedAction) () -> {
                    NativeLibLoader.loadLibrary("javafx_font");
                    String dbg = System.getProperty("prism.debugfonts", "");
                    boolean debug = "true".equals(dbg);
                    jreFontDir = getJDKFontDir();
                    String s = System.getProperty("com.sun.javafx.fontSize");
                    systemFontSize = -1f;
                    if (s != null) {
                        try {
                            systemFontSize = Float.parseFloat(s);
                        } catch (NumberFormatException nfe) {
                            System.err.println("Cannot parse font size '"
                                    + s + "'");
                        }
                    }
                    s = System.getProperty("prism.subpixeltext", "on");
                    if (s.indexOf("on") != -1 || s.indexOf("true") != -1) {
                        subPixelMode = SUB_PIXEL_ON;
                    }
                    if (s.indexOf("native") != -1) {
                        subPixelMode |= SUB_PIXEL_NATIVE | SUB_PIXEL_ON;
                    }
                    if (s.indexOf("vertical") != -1) {
                        subPixelMode |= SUB_PIXEL_Y | SUB_PIXEL_NATIVE | SUB_PIXEL_ON;
                    }

                    s = System.getProperty("prism.fontSizeLimit");
                    if (s != null) {
                        try {
                            fontSizeLimit = Float.parseFloat(s);
                            if (fontSizeLimit <= 0) {
                                fontSizeLimit = Float.POSITIVE_INFINITY;
                            }
                        } catch (NumberFormatException nfe) {
                            System.err.println("Cannot parse fontSizeLimit '" + s + "'");
                        }
                    }

                    boolean lcdTextOff = isMacOSX || isIOS || isAndroid || isEmbedded;
                    String defLCDProp = lcdTextOff ? "false" : "true";
                    String lcdProp = System.getProperty("prism.lcdtext", defLCDProp);
                    lcdEnabled = lcdProp.equals("true");

                    s = System.getProperty("prism.cacheLayoutSize");
                    if (s != null) {
                        try {
                            tempCacheLayoutSize[0] = Integer.parseInt(s);
                            if (tempCacheLayoutSize[0] < 0) {
                                tempCacheLayoutSize[0] = 0;
                            }
                        } catch (NumberFormatException nfe) {
                            System.err.println("Cannot parse cache layout size '"
                                    + s + "'");
                        }
                    }

                    return debug;
                }
        );
        debugFonts = tmp;
        cacheLayoutSize = tempCacheLayoutSize[0];
    }

    private static String getJDKFontDir() {
        return System.getProperty("java.home","") + File.separator +
                "lib" + File.separator + "fonts";
    }

    private static String getNativeFactoryName() {
        if (isWindows) return DW_FACTORY;
        if (isMacOSX || isIOS) return CT_FACTORY;
        if (isLinux || isAndroid) return FT_FACTORY;
        return null;
    }

    public static float getFontSizeLimit() {
        return fontSizeLimit;
    }

    private static PrismFontFactory theFontFactory = null;
    public static synchronized PrismFontFactory getFontFactory() {
        if (theFontFactory != null) {
            return theFontFactory;
        }
        String factoryClass = getNativeFactoryName();
        if (factoryClass == null) {
            throw new InternalError("cannot find a native font factory");
        }
        if (debugFonts) {
            System.err.println("Loading FontFactory " + factoryClass);
            if (subPixelMode != 0) {
                String s = "Subpixel: enabled";
                if ((subPixelMode & SUB_PIXEL_Y) != 0) {
                    s += ", vertical";
                }
                if ((subPixelMode & SUB_PIXEL_NATIVE) != 0) {
                    s += ", native";
                }
                System.err.println(s);
            }
        }
        theFontFactory = getFontFactory(factoryClass);
        if (theFontFactory == null) {
            throw new InternalError("cannot load font factory: "+ factoryClass);
        }
        return theFontFactory;
    }

    private static synchronized PrismFontFactory getFontFactory(String factoryClass) {
        try {
            Class clazz = Class.forName(factoryClass);
            Method mid = clazz.getMethod("getFactory", (Class[])null);
            return (PrismFontFactory)mid.invoke(null);
        } catch (Throwable t) {
            if (debugFonts) {
                System.err.println("Loading font factory failed "+ factoryClass);
            }
        }
        return null;
    }

    private HashMap fileNameToFontResourceMap = new HashMap<>();

    protected abstract PrismFontFile
          createFontFile(String name, String filename,
                         int fIndex, boolean register,
                         boolean embedded,
                         boolean copy, boolean tracked)
                         throws Exception;

    public abstract GlyphLayout createGlyphLayout();

    // For an caller who has recognised a TTC file and wants to create
    // the instances one at a time so as to have visibility into the
    // contents of the TTC. Onus is on caller to enumerate all the fonts.
    private PrismFontFile createFontResource(String filename, int index) {
        return createFontResource(null, filename, index,
                                  true, false, false, false);
    }

    private PrismFontFile createFontResource(String name,
                                             String filename, int index,
                                             boolean register, boolean embedded,
                                             boolean copy, boolean tracked) {
        String key = (filename+index).toLowerCase();
        /*
         * macOS: we need to load unique fonts for regular and bold.
         * Probably this should be handled elsewhere
         */
        if (isMacOSX && (name != null) && name.startsWith("System ")) {
            key += name;
        }
        PrismFontFile fr = fileNameToFontResourceMap.get(key);
        if (fr != null) {
            return fr;
        }

        try {
            fr = createFontFile(name, filename, index, register,
                                embedded, copy, tracked);
            if (register) {
                // Although it looks tempting here to store the lookup name
                // as what we matched, it isn't safe to do so.
                // We may be iterating over fonts in a TTC, or (some day)
                // a TTF with font variations. So we don't want to just store the name
                // But there also times (ie "System Font" on macOS - that's a macOS name
                // not JavaFX System font) when the name in the font still does
                // not match what macOS told us but we need to register it anyway
                // That needs to be handled by the caller of that case - see
                // createFontResource() below.
                storeInMap(fr.getFullName(), fr);
                fileNameToFontResourceMap.put(key, fr);
            }
            return fr;
        } catch (Exception e) {
            if (PrismFontFactory.debugFonts) {
                e.printStackTrace();
            }
            return null;
        }
    }

   /*
    * Whether a ttf or ttc this will find the index of the named font.
    */
   public int findFontIndex(String targetName, String filename) {

         try {
             PrismFontFile fr = createFontFile(null, filename, 0, false, false, false, false);

             int cnt = fr.getFontCount();
             if (cnt == 1 || fr.getFullName().equalsIgnoreCase(targetName)) {
                 return 0;
             }
             int index = 1;
             do {
                 fr = createFontFile(null, filename, index, false, false, false, false);
                 String name = fr.getFullName();
                 if (name.equalsIgnoreCase(targetName)) {
                     return index;
                 }
             } while (++index < cnt);
         } catch (Exception e) {
             if (PrismFontFactory.debugFonts) {
                 e.printStackTrace();
             }
         }
         return -1;
   }

    private PrismFontFile createFontResource(String name, String filename) {
        PrismFontFile[] pffArr =
            createFontResources(name, filename,
                                true, false, false, false, true);
        if (pffArr == null || pffArr.length == 0) {
           return null;
        } else {
           for (int i = 0; i < pffArr.length; i++) {
               if (pffArr[i].getFullName().equalsIgnoreCase(name)) {
                   return pffArr[i];
               }
           }
        }
        // No exact match - we don't want to repeat the look up so
        // log this, but store the specified name as the key to
        // match first font in this file
        storeInMap(name, pffArr[0]);
        if (PrismFontFactory.debugFonts) {
            System.err.println("No match for name " + name + " in " + filename);
        }
        return pffArr[0];
    }

    private PrismFontFile[] createFontResources(String name, String filename,
                                                boolean register,
                                                boolean embedded,
                                                boolean copy,
                                                boolean tracked,
                                                boolean loadAll) {

        PrismFontFile[] fArr = null;
        if (filename == null) {
            return null;
        }
        PrismFontFile fr = createFontResource(name, filename, 0, register,
                                              embedded, copy, tracked);
        if (fr == null) {
            return null;
        }
        int cnt = (!loadAll) ? 1 : fr.getFontCount();
        fArr = new PrismFontFile[cnt];
        fArr[0] = fr;
        if (cnt == 1) { // Not a TTC, or only requesting one font.
            return fArr;
        }
        PrismFontFile.FileRefCounter rc = null;
        if (copy) {
            rc = fr.createFileRefCounter();
        }
        int index = 1;
        do {
            String key = (filename+index).toLowerCase();
            try {
                fr = fileNameToFontResourceMap.get(key);
                if (fr != null) {
                    fArr[index] = fr;
                    continue;
                } else {
                    fr = createFontFile(null, filename, index,
                                        register, embedded,
                                        copy, tracked);
                    if (fr == null) {
                        return null;
                    }
                    if (rc != null) {
                        fr.setAndIncFileRefCounter(rc);
                    }
                    fArr[index] = fr;
                    String fontname = fr.getFullName();
                    if (register) {
                        storeInMap(fontname, fr);
                        fileNameToFontResourceMap.put(key, fr);
                    }
                }
            } catch (Exception e) {
                if (PrismFontFactory.debugFonts) {
                    e.printStackTrace();
                }
                return null;
            }

        } while (++index < cnt);
        return fArr;
    }

    private String dotStyleStr(boolean bold, boolean italic) {
        if (!bold) {
            if (!italic) {
                return "";
            }
            else {
                return ".italic";
            }
        } else {
            if (!italic) {
                return ".bold";
            }
            else {
                return ".bolditalic";
            }
        }
    }

    protected void storeInMap(String name, FontResource resource) {
        if (name == null || resource == null) {
            return;
        }
        if (resource instanceof PrismCompositeFontResource) {
            System.err.println(name + " is a composite " +
                                            resource);
            Thread.dumpStack();
            return;
        }
        fontResourceMap.put(name.toLowerCase(), resource);
    }

    private ArrayList> tmpFonts;
    synchronized void addDecodedFont(PrismFontFile fr) {
        fr.setIsDecoded(true);
        addTmpFont(fr);
    }

    private synchronized void addTmpFont(PrismFontFile fr) {
        if (tmpFonts == null) {
            tmpFonts = new ArrayList<>();
        }
        WeakReference ref;
        /* Registered fonts are enumerable by the application and are
         * expected to persist until VM shutdown.
         * Other fonts - notably ones temporarily loaded in a web page via
         * webview - should be eligible to be collected and have their
         * temp files deleted at any time.
         */
        if (fr.isRegistered()) {
            ref = new WeakReference<>(fr);
        } else {
            ref = fr.createFileDisposer(this, fr.getFileRefCounter());
        }
        tmpFonts.add(ref);
        addFileCloserHook();
    }

    synchronized void removeTmpFont(WeakReference ref) {
        if (tmpFonts != null) {
            tmpFonts.remove(ref);
        }
    }

    /* familyName is expected to be a physical font family name.
     */
    public synchronized FontResource getFontResource(String familyName,
                                                     boolean bold,
                                                     boolean italic,
                                                     boolean wantComp) {

        if (familyName == null || familyName.isEmpty()) {
            return null;
        }

        String lcFamilyName = familyName.toLowerCase();
        String styleStr = dotStyleStr(bold, italic);
        FontResource fr;

        fr = lookupResource(lcFamilyName+styleStr, wantComp);
        if (fr != null) {
            return fr;
        }


        /* We may have registered this as an embedded font.
         * In which case we should also try to locate it in
         * the non-composite map before looking elsewhere.
         * First look for a font with the exact styles specified.
         * If that fails, look for any font in the family.
         * Later on this should be a lot smarter about finding the best
         * match, but that can wait until we have better style matching
         * for all cases.
         */
        if (embeddedFonts != null && wantComp) {
            fr = lookupResource(lcFamilyName+styleStr, false);
            if (fr != null) {
                return new PrismCompositeFontResource(fr, lcFamilyName+styleStr);
            }
            for (PrismFontFile embeddedFont : embeddedFonts.values()) {
                String lcEmFamily = embeddedFont.getFamilyName().toLowerCase();
                if (lcEmFamily.equals(lcFamilyName)) {
                    return new PrismCompositeFontResource(embeddedFont,
                                                        lcFamilyName+styleStr);
                }
            }
        }

        /* We have hard coded some of the most commonly used Windows fonts
         * so as to avoid the overhead of doing a lookup via GDI.
         */
        if (isWindows) {
            int style = ((bold ? 1 : 0)) + ((italic) ? 2 : 0);
            String fontFile = WindowsFontMap.findFontFile(lcFamilyName, style);
            if (fontFile != null) {
                fr = createFontResource(null, fontFile);
                if (fr != null) {
                    if (bold == fr.isBold() && italic == fr.isItalic() &&
                        !styleStr.isEmpty())
                    {
                        storeInMap(lcFamilyName+styleStr, fr);
                    }
                    if (wantComp) {  // wrap with fallback support
                        fr = new PrismCompositeFontResource(fr,
                                                       lcFamilyName+styleStr);
                    }
                    return fr;
                }
            }
        }

        getFullNameToFileMap();
        ArrayList family = familyToFontListMap.get(lcFamilyName);
        if (family == null) {
            return null;
        }

        FontResource plainFR = null, boldFR = null,
            italicFR = null, boldItalicFR = null;
        for (String fontName : family) {
            String lcFontName = fontName.toLowerCase();
            fr = fontResourceMap.get(lcFontName);
            if (fr == null) {
                String file = findFile(lcFontName);
                if (file != null) {
                    fr = getFontResource(fontName, file);
                }
                if (fr == null) {
                    continue;
                }
                storeInMap(lcFontName, fr);
            }
            if (bold == fr.isBold() && italic == fr.isItalic()) {
                storeInMap(lcFamilyName+styleStr, fr);
                if (wantComp) {  // wrap with fallback support
                    fr = new PrismCompositeFontResource(fr,
                                                      lcFamilyName+styleStr);
                }
                return fr;
            }
            if (!fr.isBold()) {
                if (!fr.isItalic()) {
                    plainFR = fr;
                } else {
                    italicFR = fr;
                }
            } else {
                if (!fr.isItalic()) {
                    boldFR = fr;
                } else {
                    boldItalicFR = fr;
                }
            }
        }

        /* If get here, no perfect match in family. Substitute the
         * closest one we found.
         */
        if (!bold && !italic) {
            if (boldFR != null) {
                fr = boldFR;
            } else if (italicFR != null) {
                fr = italicFR;
            } else {
                fr = boldItalicFR;
            }
        } else if (bold && !italic) {
            if (plainFR != null) {
                fr = plainFR;
            } else if (boldItalicFR != null) {
                fr = boldItalicFR;
            } else {
                fr = italicFR;
            }
        } else if (!bold && italic) {
            if (boldItalicFR != null) {
                fr =  boldItalicFR;
            } else if (plainFR != null) {
                fr = plainFR;
            } else {
                fr = boldFR;
            }
        } else /* (bold && italic) */ {
            if (italicFR != null) {
                fr = italicFR;
            } else if (boldFR != null) {
                fr = boldFR;
            } else {
                fr = plainFR;
            }
        }
        if (fr != null) {
            storeInMap(lcFamilyName+styleStr, fr);
            if (wantComp) {  // wrap with fallback support
                fr = new PrismCompositeFontResource(fr, lcFamilyName+styleStr);
            }
        }
        return fr;
    }

    @Override
    public synchronized PGFont createFont(String familyName, boolean bold,
                                          boolean italic, float size) {
        FontResource fr = null;
        if (familyName != null && !familyName.isEmpty() && !isExcluded(familyName)) {
            PGFont logFont =
                LogicalFont.getLogicalFont(familyName, bold, italic, size);
            if (logFont != null) {
                return logFont;
            }
            fr = getFontResource(familyName, bold, italic, true);
        }

        if (fr == null) {
            // "System" is the default if we didn't recognise the family
            return LogicalFont.getLogicalFont("System", bold, italic, size);
        }
        return new PrismFont(fr, fr.getFullName(), size);
    }

    @Override
    public synchronized PGFont createFont(String name, float size) {

        FontResource fr = null;
        if (name != null && !name.isEmpty() && !isExcluded(name)) {
            PGFont logFont =
                LogicalFont.getLogicalFont(name, size);
            if (logFont != null) {
                return logFont;
            }

            fr = getFontResource(name, null, true);
        }
        if (fr == null) {
            return LogicalFont.getLogicalFont(DEFAULT_FULLNAME, size);
        }
        return new PrismFont(fr, fr.getFullName(), size);
    }

    private PrismFontFile getFontResource(String name, String file) {
        /* caller assures file not null */
        PrismFontFile fr = null;
        /* Still need decode the dfont (event when coretext is used)
         * so that JFXFontFont can read it */
        if (isMacOSX) {
            DFontDecoder decoder = null;
            if (name != null) {
                if (file.endsWith(".dfont")) {
                    decoder = new DFontDecoder();
                    try {
                        decoder.openFile();
                        decoder.decode(name);
                        decoder.closeFile();
                        file = decoder.getFile().getPath();
                    } catch (Exception e) {
                        file = null;
                        decoder.deleteFile();
                        decoder = null;
                        if (PrismFontFactory.debugFonts) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            if (file != null) {
                fr = createFontResource(name, file);
            }
            if (decoder != null) {
                if (fr != null) {
                    addDecodedFont(fr);
                } else {
                    decoder.deleteFile();
                }
            }
        } else {
            fr = createFontResource(name, file);
        }
        return fr;
    }

    @Override
    public synchronized PGFont deriveFont(PGFont font, boolean bold,
                                          boolean italic, float size) {
        FontResource fr = font.getFontResource();
        //TODO honor bold and italic
        return new PrismFont(fr, fr.getFullName(), size);
    }

    private FontResource lookupResource(String lcName, boolean wantComp) {
        if (wantComp) {
            return compResourceMap.get(lcName);
        } else {
            return fontResourceMap.get(lcName);
        }
    }

    public synchronized FontResource getFontResource(String name, String file,
                                                     boolean wantComp) {
        FontResource fr = null;

        // First check if the font is already known.
        if (name != null) {
            String lcName = name.toLowerCase();

            // if requesting a wrapped resource, look in the composite map
            // else look in the physical resource map
            FontResource fontResource = lookupResource(lcName, wantComp);
            if (fontResource != null) {
                return fontResource;
            }

            /* We may have registered this as an embedded font.
             * In which case we should also try to locate it in
             * the non-composite map before looking elsewhere.
             */
            if (embeddedFonts != null && wantComp) {
                fr = lookupResource(lcName, false);
                if (fr != null) {
                    fr = new PrismCompositeFontResource(fr, lcName);
                }
                if (fr != null) {
                    return fr;
                }
            }
        }

        /* We have hard coded some of the most commonly used Windows fonts
         * so as to avoid the overhead of doing a lookup via GDI.
         */
        if (isWindows && name != null) {
            String lcName = name.toLowerCase();
            String fontFile = WindowsFontMap.findFontFile(lcName, -1);
            if (fontFile != null) {
                fr = createFontResource(null, fontFile);
                if (fr != null) {
                    if (wantComp) {
                        fr = new PrismCompositeFontResource(fr, lcName);
                    }
                    return fr;
                }
            }
        }

        getFullNameToFileMap(); // init maps

        if (name != null && file != null) {
            // Typically the TTC case used in font linking.
            // The called method adds the resources to the physical
            // map so no need  to do it here.
            fr = getFontResource(name, file);
            if (fr != null) {
                if (wantComp) {
                    fr = new PrismCompositeFontResource(fr, name.toLowerCase());
                }
                return fr;
            }
        }

        if (name != null) { // Typically normal application lookup
            fr = getFontResourceByFullName(name, wantComp);
            if (fr != null) {
                return fr;
            }
        }

        if (file != null) { // Typically the TTF case used in font linking
            fr = getFontResourceByFileName(file, wantComp);
            if (fr != null) {
                return fr;
            }
        }

        /* can't find the requested font, caller will fall back to default */
        return null;
    }

    /* To be called only by methods that already inited the maps
     */
    synchronized private FontResource
        getFontResourceByFileName(String file, boolean wantComp) {

        if (fontToFileMap.size() <= 1) {
            return null;
        }

        /* This is a little tricky: we know the file we want but we need
         * to check if its already a loaded resource. The maps are set up
         * to check if a font is loaded by its name, not file.
         * To help I added a map from file->font for all the windows fonts
         * but that is valid only for TTF fonts (not TTC). So it should only
         * be used in a context where we know its a TTF (or OTF) file.
         */
        String name = fileToFontMap.get(file.toLowerCase()); // basename
        FontResource fontResource = null;
        if (name == null) {
            // We should not normally get here with a name that we did
            // not find from the platform but any EUDC font is in the
            // list of linked fonts but it is not enumerated by Windows.
            // So we need to open the file and load it as requested.
           fontResource = createFontResource(file, 0);
           if (fontResource != null) {
               String lcName = fontResource.getFullName().toLowerCase();
               storeInMap(lcName, fontResource);
               // Checking wantComp, alhough the linked/fallback font
               // case doesn't use this.
                if (wantComp) {
                    fontResource =
                        new PrismCompositeFontResource(fontResource, lcName);
                }
           }
        } else {
            String lcName = name.toLowerCase();
            fontResource = lookupResource(lcName, wantComp);

            if (fontResource == null) {
                String fullPath = findFile(lcName);
                if (fullPath != null) {
                    fontResource = getFontResource(name, fullPath);
                    if (fontResource != null) {
                        storeInMap(lcName, fontResource);
                    }
                    if (wantComp) {
                        // wrap with fallback support
                        fontResource =
                                new PrismCompositeFontResource(fontResource, lcName);
                    }
                }
            }
        }
        return fontResource; // maybe null
    }

    /* To be called only by methods that already inited the maps
     * and checked the font is not already loaded.
     */
    synchronized private FontResource
        getFontResourceByFullName(String name, boolean wantComp) {

        String lcName = name.toLowerCase();

        if (fontToFileMap.size() <= 1) {
            // Do this even though findFile also fails over to Lucida, as
            // without this step, we'd create new instances.
            name = jreDefaultFont;
        }

        FontResource fontResource = null;
        String file = findFile(lcName);
        if (file != null) {
            fontResource = getFontResource(name, file);
            if (fontResource != null) {
                storeInMap(lcName, fontResource);
                if (wantComp) {
                    // wrap with fallback support
                    fontResource =
                        new PrismCompositeFontResource(fontResource, lcName);
                }
            }
        }
        return fontResource;
    }

    FontResource getDefaultFontResource(boolean wantComp) {
        FontResource fontResource = lookupResource(jreDefaultFontLC, wantComp);
        if (fontResource == null) {
            fontResource = createFontResource(jreDefaultFont,
                                              jreFontDir+jreDefaultFontFile);
            if (fontResource == null) {
                // Normally use the JRE default font as the last fallback.
                // If we can't find even that, use any platform font;
                for (String font : fontToFileMap.keySet()) {
                    String file = findFile(font); // gets full path
                    fontResource = createFontResource(jreDefaultFontLC, file);
                    if (fontResource != null) {
                        break;
                    }
                }
                if (fontResource == null && isLinux) {
                    String path = FontConfigManager.getDefaultFontPath();
                    if (path != null) {
                        fontResource = createFontResource(jreDefaultFontLC,
                                                          path);
                    }
                }
                if (fontResource == null) {
                    return null; // We tried really hard!
                }
            }
            storeInMap(jreDefaultFontLC, fontResource);
            if (wantComp) {  // wrap primary for map key
                fontResource =
                    new PrismCompositeFontResource(fontResource,
                                                 jreDefaultFontLC);
            }
        }
        return fontResource;
    }

    private String findFile(String name) {

        if (name.equals(jreDefaultFontLC)) {
            return jreFontDir+jreDefaultFontFile;
        }
        getFullNameToFileMap();
        String filename = fontToFileMap.get(name);
        if (isWindows) {
            filename = getPathNameWindows(filename);
        }

        // Caller needs to check for null and explicitly request
        // the JRE default font, if that is what is needed.
        // since we don't want the JRE's Lucida font to be the
        // default for "unknown" fonts.
        return filename;
    }

    /* Used to indicate required return type from toArray(..); */
    private static final String[] STR_ARRAY = new String[0];

    /* Obtained from Platform APIs (windows only)
     * Map from lower-case font full name to basename of font file.
     * Eg "arial bold" -> ARIALBD.TTF.
     * For TTC files, there is a mapping for each font in the file.
     */
    private volatile HashMap fontToFileMap = null;

    /*  TTF/OTF Font File to Font Full Name */
    private HashMap fileToFontMap = null;

    /* Obtained from Platform APIs (windows only)
     * Map from lower-case font full name to the name of its font family
     * Eg "arial bold" -> "Arial"
     */
    private HashMap fontToFamilyNameMap = null;

    /* Obtained from Platform APIs (windows only)
     * Map from a lower-case family name to a list of full names of
     * the member fonts, eg:
     * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"]
     */
    private HashMap> familyToFontListMap= null;


    /* For a terminal server there may be two font directories */
    private static String sysFontDir = null;
    private static String userFontDir = null;

    private static native String getFontPath();

    private static void getPlatformFontDirs() {

        if (userFontDir != null || sysFontDir != null) {
            return;
        }
        String path = getFontPath();

        int scIdx = path.indexOf(';');
        if (scIdx < 0) {
            sysFontDir = path;
        } else {
            sysFontDir = path.substring(0, scIdx);
            userFontDir = path.substring(scIdx+1, path.length());
        }
    }

    /* This is needed since some windows registry names don't match
     * the font names.
     * - UPC styled font names have a double space, but the
     * registry entry mapping to a file doesn't.
     * - Marlett is in a hidden file not listed in the registry
     * - The registry advertises that the file david.ttf contains a
     * font with the full name "David Regular" when in fact its
     * just "David".
     * Directly fix up these known cases as this is faster.
     * If a font which doesn't match these known cases has no file,
     * it may be a font that has been temporarily added to the known set
     * or it may be an installed font with a missing registry entry.
     * Installed fonts are those in the windows font directories.
     * Make a best effort attempt to locate these.
     * We obtain the list of TrueType fonts in these directories and
     * filter out all the font files we already know about from the registry.
     * What remains may be "bad" fonts, duplicate fonts, or perhaps the
     * missing font(s) we are looking for.
     * Open each of these files to find out.
     */
    private void resolveWindowsFonts
        (HashMap fontToFileMap,
         HashMap fontToFamilyNameMap,
         HashMap> familyToFontListMap) {

        ArrayList unmappedFontNames = null;
        for (String font : fontToFamilyNameMap.keySet()) {
            String file = fontToFileMap.get(font);
            if (file == null) {
                int dsi = font.indexOf("  ");
                if (dsi > 0) {
                    String newName = font.substring(0, dsi);
                    newName = newName.concat(font.substring(dsi+1));
                    file = fontToFileMap.get(newName);
                    /* If this name exists and isn't for a valid name
                     * replace the mapping to the file with this font
                     */
                    if (file != null &&
                        !fontToFamilyNameMap.containsKey(newName)) {
                        fontToFileMap.remove(newName);
                        fontToFileMap.put(font, file);
                    }
                } else if (font.equals("marlett")) {
                    fontToFileMap.put(font, "marlett.ttf");
                } else if (font.equals("david")) {
                    file = fontToFileMap.get("david regular");
                    if (file != null) {
                        fontToFileMap.remove("david regular");
                        fontToFileMap.put("david", file);
                    }
                } else {
                    if (unmappedFontNames == null) {
                        unmappedFontNames = new ArrayList<>();
                    }
                    unmappedFontNames.add(font);
                }
            }
        }

        if (unmappedFontNames != null) {
            HashSet unmappedFontFiles = new HashSet<>();

            // Used HashMap.clone() on SE but TV didn't support it.
            HashMap ffmapCopy = new HashMap<>();
            ffmapCopy.putAll(fontToFileMap);
            for (String key : fontToFamilyNameMap.keySet()) {
                ffmapCopy.remove(key);
            }
            for (String key : ffmapCopy.keySet()) {
                unmappedFontFiles.add(ffmapCopy.get(key));
                fontToFileMap.remove(key);
            }
            resolveFontFiles(unmappedFontFiles,
                             unmappedFontNames,
                             fontToFileMap,
                             fontToFamilyNameMap,
                             familyToFontListMap);

            /* remove from the set of names that will be returned to the
             * user any fonts that can't be mapped to files.
             */
            if (unmappedFontNames.size() > 0) {
                int sz = unmappedFontNames.size();
                for (int i=0; i family = familyToFontListMap.get(familyName);
                        if (family != null) {
                            if (family.size() <= 1) {
                                familyToFontListMap.remove(familyName);
                            }
                        }
                    }
                    fontToFamilyNameMap.remove(name);
                }
            }
        }
    }

    private void resolveFontFiles(HashSet unmappedFiles,
         ArrayList unmappedFonts,
         HashMap fontToFileMap,
         HashMap fontToFamilyNameMap,
         HashMap> familyToFontListMap) {

        for (String file : unmappedFiles) {
            try {
                int fn = 0;
                PrismFontFile ttf;
                String fullPath = getPathNameWindows(file);
                do {
                    ttf = createFontResource(fullPath, fn++);
                    if (ttf == null) {
                        break;
                    }
                    String fontNameLC = ttf.getFullName().toLowerCase();
                    String localeNameLC =ttf.getLocaleFullName().toLowerCase();
                    if (unmappedFonts.contains(fontNameLC) ||
                        unmappedFonts.contains(localeNameLC)) {
                        fontToFileMap.put(fontNameLC, file);
                        unmappedFonts.remove(fontNameLC);
                        /* If GDI reported names using locale specific style
                         * strings we'll have those as the unmapped keys in
                         * the font to family list and also in the value
                         * array list mapped by the family.
                         * We can spot these if the localeName is what is
                         * actually in the unmapped font list, and we'll
                         * then replace all occurrences of the locale name with
                         * the English name.
                         */
                        if (unmappedFonts.contains(localeNameLC)) {
                            unmappedFonts.remove(localeNameLC);
                            String family = ttf.getFamilyName();
                            String familyLC = family.toLowerCase();
                            fontToFamilyNameMap.remove(localeNameLC);
                            fontToFamilyNameMap.put(fontNameLC, family);
                            ArrayList familylist =
                                familyToFontListMap.get(familyLC);
                            if (familylist != null) {
                                familylist.remove(ttf.getLocaleFullName());
                            } else {
                                /* The family name was not English.
                                 * Remove the non-English family list
                                 * and replace it with the English one
                                 */
                                String localeFamilyLC =
                                    ttf.getLocaleFamilyName().toLowerCase();
                                familylist =
                                    familyToFontListMap.get(localeFamilyLC);
                                if (familylist != null) {
                                    familyToFontListMap.remove(localeFamilyLC);
                                }
                                familylist = new ArrayList<>();
                                familyToFontListMap.put(familyLC, familylist);
                            }
                            familylist.add(ttf.getFullName());
                        }
                    }

                }
                while (fn < ttf.getFontCount());
            } catch (Exception e) {
                if (debugFonts) {
                    e.printStackTrace();
                }
            }
        }
    }

    static native void
        populateFontFileNameMap(HashMap fontToFileMap,
                                 HashMap fontToFamilyNameMap,
                                 HashMap>
                                     familyToFontListMap,
                                 Locale locale);

    protected static String getPathNameWindows(final String filename) {
        if (filename == null) {
            return null;
        }

        getPlatformFontDirs();
        File f = new File(filename);
        if (f.isAbsolute()) {
            return filename;
        }
        if (userFontDir == null) {
            return sysFontDir+"\\"+filename;
        }

        @SuppressWarnings("removal")
        String path = AccessController.doPrivileged(
            new PrivilegedAction() {
                @Override
                public String run() {
                    File f = new File(sysFontDir+"\\"+filename);
                    if (f.exists()) {
                        return f.getAbsolutePath();
                    }
                    else {
                        return userFontDir+"\\"+filename;
                    }
                }
            });

            if (path != null) {
                return path;
            }
        return null; //  shouldn't happen.
    }

    /*
     * Do not return matching names via public API.
     * Some names may be needed for internal matching but
     * never exposed to application code.
     * Initially this only excludes certain names on macOS.
     */
    public boolean isExcluded(String name) {
        return false;
    }

    private static ArrayList allFamilyNames;
    @Override
    public String[] getFontFamilyNames() {
        if (allFamilyNames == null) {
            /* Create an array list and add the families for :
             * - logical fonts
             * - Embedded fonts
             * - Fonts found on the platform (includes JRE fonts)..
             */
            ArrayList familyNames = new ArrayList<>();
            LogicalFont.addFamilies(familyNames);
            //  Putting this in here is dependendent on the FontLoader
            // loading embedded fonts before calling into here. If
            // embedded fonts can be added then we need to add these
            // dynamically for each call to this method.

            if (embeddedFonts != null) {
                for (PrismFontFile embeddedFont : embeddedFonts.values()) {
                    if (!familyNames.contains(embeddedFont.getFamilyName()))
                        familyNames.add(embeddedFont.getFamilyName());
                }
            }
            getFullNameToFileMap();
            for (String f : fontToFamilyNameMap.values()) {
                if (!familyNames.contains(f)) {
                    if (!isExcluded(f)) {
                        familyNames.add(f);
                    }
                }
            }
            Collections.sort(familyNames);
            allFamilyNames = new ArrayList<>(familyNames);
        }
        return allFamilyNames.toArray(STR_ARRAY);
    }

    private static ArrayList allFontNames;
    @Override
    public String[] getFontFullNames() {
        if (allFontNames == null) {
            /* Create an array list and add
             * - logical fonts
             * - Embedded fonts
             * - Fonts found on the platform (includes JRE fonts).
             */
            ArrayList fontNames = new ArrayList<>();
            LogicalFont.addFullNames(fontNames);
            if (embeddedFonts != null) {
                for (PrismFontFile embeddedFont : embeddedFonts.values()) {
                    if (!fontNames.contains(embeddedFont.getFullName())) {
                        fontNames.add(embeddedFont.getFullName());
                    }
                }
            }
            getFullNameToFileMap();
            for (ArrayList a : familyToFontListMap.values()) {
                for (String s : a) {
                    if (!isExcluded(s)) {
                        fontNames.add(s);
                    }
                }
            }
            Collections.sort(fontNames);
            allFontNames = fontNames;
        }
        return allFontNames.toArray(STR_ARRAY);
    }

    @Override
    public String[] getFontFullNames(String family) {

        if (isExcluded(family)) {
            return STR_ARRAY;
        }
        // First check if its a logical font family.
        String[] logFonts = LogicalFont.getFontsInFamily(family);
        if (logFonts != null) {
            // Caller will clone/wrap this before returning it to API
            return logFonts;
        }
        // Next check if its an embedded font family
        if (embeddedFonts != null) {
            ArrayList embeddedFamily = null;
            for (PrismFontFile embeddedFont : embeddedFonts.values()) {
                if (embeddedFont.getFamilyName().equalsIgnoreCase(family)) {
                    if (embeddedFamily == null) {
                        embeddedFamily = new ArrayList<>();
                    }
                    embeddedFamily.add(embeddedFont.getFullName());
                }
            }
            if (embeddedFamily != null) {
                return embeddedFamily.toArray(STR_ARRAY);
            }
        }

        getFullNameToFileMap();
        family = family.toLowerCase();
        ArrayList familyFonts = familyToFontListMap.get(family);
        if (familyFonts != null) {
            return familyFonts.toArray(STR_ARRAY);
        } else {
            return STR_ARRAY; // zero-length therefore immutable.
        }
    }

    public final int getSubPixelMode() {
        return subPixelMode;
    }

    public boolean isLCDTextSupported() {
        return lcdEnabled;
    }

    @Override
    public boolean isPlatformFont(String name) {
        if (name == null) return false;
        /* Using String#startsWith as name can be either a fullName or a family name */
        String lcName = name.toLowerCase();
        if (LogicalFont.isLogicalFont(lcName)) return true;
        if (lcName.startsWith("lucida sans")) return true;
        String systemFamily = getSystemFont(LogicalFont.SYSTEM).toLowerCase();
        if (lcName.startsWith(systemFamily)) return true;
        return false;
    }

    public static boolean isJreFont(FontResource fr) {
        String file = fr.getFileName();
        return file.startsWith(jreFontDir);
    }

    public static float getLCDContrast() {
        if (lcdContrast == -1) {
            if (isWindows) {
                lcdContrast = getLCDContrastWin32() / 1000f;
            } else {
                /* REMIND: When using CoreText it likely already applies gamma
                 * correction to the glyph images. The current implementation does
                 * not take this into account when rasterizing the glyph. Thus,
                 * it is possible gamma correction is been applied twice to the
                 * final result.
                 * Consider using "1" for lcdContrast possibly produces visually
                 * more appealing results (although not strictly correct).
                 */
                lcdContrast = 1.3f;
            }
        }
        return lcdContrast;
    }

    private static Thread fileCloser = null;

    private synchronized void addFileCloserHook() {
        if (fileCloser == null) {
            final Runnable fileCloserRunnable = () -> {
                if (embeddedFonts != null) {
                    for (PrismFontFile font : embeddedFonts.values()) {
                        font.disposeOnShutdown();
                    }
                }
                if (tmpFonts != null) {
                    for (WeakReference ref : tmpFonts) {
                        PrismFontFile font = ref.get();
                        if (font != null) {
                            font.disposeOnShutdown();
                        }
                    }
                }
            };
            @SuppressWarnings("removal")
            var dummy = java.security.AccessController.doPrivileged(
                    (PrivilegedAction) () -> {
                        /* The thread must be a member of a thread group
                         * which will not get GCed before VM exit.
                         * Make its parent the top-level thread group.
                         */
                        ThreadGroup tg = Thread.currentThread().getThreadGroup();
                        for (ThreadGroup tgn = tg;
                             tgn != null; tg = tgn, tgn = tg.getParent());
                        fileCloser = new Thread(tg, fileCloserRunnable);
                        fileCloser.setContextClassLoader(null);
                        Runtime.getRuntime().addShutdownHook(fileCloser);
                        return null;
                    }
            );
        }
    }

    private HashMap embeddedFonts;

    @Override
    public PGFont[] loadEmbeddedFont(String name, InputStream fontStream,
                                     float size,
                                     boolean register,
                                     boolean loadAll) {
        if (!hasPermission()) {
            return new PGFont[] { createFont(DEFAULT_FULLNAME, size) } ;
        }
        if (FontFileWriter.hasTempPermission()) {
            return loadEmbeddedFont0(name, fontStream, size, register, loadAll);
        }

        // Otherwise, be extra conscious of pending temp file creation and
        // resourcefully handle the temp file resources, among other things.
        FontFileWriter.FontTracker tracker =
            FontFileWriter.FontTracker.getTracker();
        boolean acquired = false;
        try {
            acquired = tracker.acquirePermit();
            if (!acquired) {
                // Timed out waiting for resources.
                return null;
            }
            return loadEmbeddedFont0(name, fontStream, size, register, loadAll);
        } catch (InterruptedException e) {
            // Interrupted while waiting to acquire a permit.
            return null;
        } finally {
            if (acquired) {
                tracker.releasePermit();
            }
        }
    }

    private PGFont[] loadEmbeddedFont0(String name, InputStream fontStream,
                                       float size,
                                       boolean register,
                                       boolean loadAll) {
        PrismFontFile[] fr = null;
        FontFileWriter fontWriter = new FontFileWriter();
        try {
            // We use a shutdown hook to close all open tmp files
            // created via this API and delete them.
            final File tFile = fontWriter.openFile();
            byte[] buf = new byte[8192];
            for (;;) {
                int bytesRead = fontStream.read(buf);
                if (bytesRead < 0) {
                    break;
                }
                fontWriter.writeBytes(buf, 0, bytesRead);
            }
            fontWriter.closeFile();

            fr = loadEmbeddedFont1(name, tFile.getPath(), register, true,
                                   fontWriter.isTracking(), loadAll);

            if (fr != null && fr.length > 0) {
                /* Delete the file downloaded if it was decoded
                 * to another file */
                if (fr[0].isDecoded()) {
                    fontWriter.deleteFile();
                }
            }

            /* We don't want to leave the temp files around after exit.
             * Also fonts can be over-written by new versions and
             * need to be cleaned up.
             * We also need to decrement the byte count by the size
             * of the file.
             */
            addFileCloserHook();
        } catch (Exception e) {
            fontWriter.deleteFile();
        } finally {
            /* If the data isn't a valid font, so that registering it
             * returns null, or we didn't get so far as copying the data,
             * delete the tmp file and decrement the byte count
             * in the tracker object before returning.
             */
            if (fr == null) {
                fontWriter.deleteFile();
            }
        }
        if (fr != null && fr.length > 0) {
            if (size <= 0) size = getSystemFontSize();
            int num = fr.length;
            PrismFont[] pFonts = new PrismFont[num];
            for (int i=0; i 0) {
            if (size <= 0) size = getSystemFontSize();
            int num = frArr.length;
            PGFont[] pgFonts = new PGFont[num];
            for (int i=0; i fi = compResourceMap.values().iterator();
            while (fi.hasNext()) {
            CompositeFontResource compFont = fi.next();
            if (compFont.getSlotResource(0) == font) {
                fi.remove();
            }
        }
    }

    protected boolean registerEmbeddedFont(String path) {
        return true;
    }

    // Used for testing
    private int numEmbeddedFonts = 0;
    public int test_getNumEmbeddedFonts() {
        return numEmbeddedFonts;
    }

    private synchronized
        PrismFontFile[] loadEmbeddedFont1(String name, String path,
                                          boolean register, boolean copy,
                                          boolean tracked, boolean loadAll) {

        ++numEmbeddedFonts;
        /*
         * Fonts that aren't platform installed include those in the
         * application jar, WOFF fonts that are downloaded, and fonts
         * created via Font.loadFont. If copy==true, we can infer its
         * one of these, but its also possible for a font to be file-system
         * installed as part of the application but not known to the
         * platform. In this case copy==false, but we still need to flag
         * to the system its not a platform font so that other pipelines
         * know to reference the file directly.
         */
        PrismFontFile[] frArr = createFontResources(name, path, register,
                                                    true, copy, tracked,
                                                    loadAll);
        if (frArr == null || frArr.length == 0) {
            return null; // yes, this means the caller needs to handle null.
        }

        /* Before we return or register, make sure names are present
         * check whether any of the fonts duplicate an OS font.
         */

        if (embeddedFonts == null) {
            embeddedFonts = new HashMap<>();
        }

        boolean registerEmbedded = true;
        for (int i=0; i fontToFileMap,
                    HashMap fontToFamilyNameMap,
                    HashMap> familyToFontListMap) {

        System.err.println(message);
        for (String keyName : fontToFileMap.keySet()) {
            System.err.println("font="+keyName+" file="+
                               fontToFileMap.get(keyName));
        }
        for (String keyName : fontToFamilyNameMap.keySet()) {
            System.err.println("font="+keyName+" family="+
                               fontToFamilyNameMap.get(keyName));
        }
        for (String keyName : familyToFontListMap.keySet()) {
            System.err.println("family="+keyName+ " fonts="+
                               familyToFontListMap.get(keyName));
        }
    }

    private synchronized HashMap getFullNameToFileMap() {
        if (fontToFileMap == null) {

            HashMap tmpFontToFileMap = new HashMap<>(100);
            fontToFamilyNameMap = new HashMap<>(100);
            familyToFontListMap = new HashMap<>(50);
            fileToFontMap = new HashMap<>(100);

            if (isWindows) {
                getPlatformFontDirs();
                populateFontFileNameMap(tmpFontToFileMap,
                                        fontToFamilyNameMap,
                                        familyToFontListMap,
                                        Locale.ENGLISH);

                if (debugFonts) {
                    System.err.println("Windows Locale ID=" + getSystemLCID());
                    logFontInfo(" *** WINDOWS FONTS BEFORE RESOLVING",
                                tmpFontToFileMap,
                                fontToFamilyNameMap,
                                familyToFontListMap);
                }

                resolveWindowsFonts(tmpFontToFileMap,
                                    fontToFamilyNameMap,
                                    familyToFontListMap);

                if (debugFonts) {
                    logFontInfo(" *** WINDOWS FONTS AFTER RESOLVING",
                                tmpFontToFileMap,
                                fontToFamilyNameMap,
                                familyToFontListMap);
                }

            } else if (isMacOSX || isIOS) {
                MacFontFinder.populateFontFileNameMap(tmpFontToFileMap,
                                                      fontToFamilyNameMap,
                                                      familyToFontListMap,
                                                      Locale.ENGLISH);

                if (debugFonts) {
                    logFontInfo(" *** MACOS LOCATED FONTS:",
                                tmpFontToFileMap,
                                fontToFamilyNameMap,
                                familyToFontListMap);
                }
            } else if (isLinux) {
                FontConfigManager.populateMaps(tmpFontToFileMap,
                                               fontToFamilyNameMap,
                                               familyToFontListMap,
                                               Locale.getDefault());
                if (debugFonts) {
                    logFontInfo(" *** FONTCONFIG LOCATED FONTS:",
                                tmpFontToFileMap,
                                fontToFamilyNameMap,
                                familyToFontListMap);
                }
            } else if (isAndroid) {
               AndroidFontFinder.populateFontFileNameMap(tmpFontToFileMap,
                        fontToFamilyNameMap,
                        familyToFontListMap,
                        Locale.ENGLISH);
           } else { /* unrecognised OS */
                fontToFileMap = tmpFontToFileMap;
                return fontToFileMap;
            }

            /* Reverse map from file to font. file name is base name
             * not a full path.
             */
            for (String font : tmpFontToFileMap.keySet()) {
                String file = tmpFontToFileMap.get(font);
                fileToFontMap.put(file.toLowerCase(), font);
            }

            fontToFileMap = tmpFontToFileMap;
            if (isAndroid) {
                populateFontFileNameMapGeneric(
                       AndroidFontFinder.getSystemFontsDir());
            }
            populateFontFileNameMapGeneric(jreFontDir);

//             for (String keyName : fontToFileMap.keySet()) {
//               System.out.println("font="+keyName+" file="+ fontToFileMap.get(keyName));
//             }

//             for (String keyName : familyToFontListMap.keySet()) {
//               System.out.println("family="+keyName);
//             }
        }
        return fontToFileMap;
    }

    @Override
    @SuppressWarnings("removal")
    public final boolean hasPermission() {
        try {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                sm.checkPermission(LOAD_FONT_PERMISSION);
            }
            return true;
        } catch (SecurityException ex) {
            return false;
        }
    }

    private static class TTFilter implements FilenameFilter {
        @Override
        public boolean accept(File dir,String name) {
            /* all conveniently have the same suffix length */
            int offset = name.length()-4;
            if (offset <= 0) { /* must be at least A.ttf */
                return false;
            } else {
                return(name.startsWith(".ttf", offset) ||
                       name.startsWith(".TTF", offset) ||
                       name.startsWith(".ttc", offset) ||
                       name.startsWith(".TTC", offset) ||
                       name.startsWith(".otf", offset) ||
                       name.startsWith(".OTF", offset));
            }
        }

        private TTFilter() {
        }

        static TTFilter ttFilter;
        static TTFilter getInstance() {
            if (ttFilter == null) {
                ttFilter = new TTFilter();
            }
            return ttFilter;
        }
    }

    void addToMaps(PrismFontFile fr) {

        if (fr == null) {
            return;
        }

        String fullName = fr.getFullName();
        String familyName = fr.getFamilyName();

        if (fullName == null || familyName == null) {
            return;
        }

        String lcFullName = fullName.toLowerCase();
        String lcFamilyName = familyName.toLowerCase();

        fontToFileMap.put(lcFullName, fr.getFileName());
        fontToFamilyNameMap.put(lcFullName, familyName);
        ArrayList familyList = familyToFontListMap.get(lcFamilyName);
        if (familyList == null) {
            familyList = new ArrayList<>();
            familyToFontListMap.put(lcFamilyName, familyList);
        }
        familyList.add(fullName);
    }

    void populateFontFileNameMapGeneric(String fontDir) {
        final File dir = new File(fontDir);
        String[] files = null;
        try {
            @SuppressWarnings("removal")
            String[] tmp = AccessController.doPrivileged(
                    (PrivilegedExceptionAction) () -> dir.list(TTFilter.getInstance())
            );
            files = tmp;
        } catch (Exception e) {
        }

        if (files == null) {
            return;
        }

        for (int i=0;i