/*
* Copyright (c) 2011, 2018, 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<String, FontResource> fontResourceMap =
new HashMap<String, FontResource>();
HashMap<String, CompositeFontResource> compResourceMap =
new HashMap<String, CompositeFontResource>();
static {
isWindows = PlatformUtil.isWindows();
isMacOSX = PlatformUtil.isMac();
isLinux = PlatformUtil.isLinux();
isIOS = PlatformUtil.isIOS();
isAndroid = PlatformUtil.isAndroid();
isEmbedded = PlatformUtil.isEmbedded();
int[] tempCacheLayoutSize = {0x10000};
debugFonts = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) () -> {
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 = 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;
}
);
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<String, PrismFontFile>
fileNameToFontResourceMap = new HashMap<String, PrismFontFile>();
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();
PrismFontFile fr = fileNameToFontResourceMap.get(key);
if (fr != null) {
return fr;
}
try {
fr = createFontFile(name, filename, index, register,
embedded, copy, tracked);
if (register) {
storeInMap(fr.getFullName(), fr);
fileNameToFontResourceMap.put(key, fr);
}
return fr;
} catch (Exception e) {
if (PrismFontFactory.debugFonts) {
e.printStackTrace();
}
return null;
}
}
private PrismFontFile createFontResource(String name, String filename) {
PrismFontFile[] pffArr =
createFontResources(name, filename,
true, false, false, false, false);
if (pffArr == null || pffArr.length == 0) {
return null;
} else {
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";
}
}
}
private 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<WeakReference<PrismFontFile>> tmpFonts;
synchronized void addDecodedFont(PrismFontFile fr) {
fr.setIsDecoded(true);
addTmpFont(fr);
}
private synchronized void addTmpFont(PrismFontFile fr) {
if (tmpFonts == null) {
tmpFonts = new ArrayList<WeakReference<PrismFontFile>>();
}
WeakReference<PrismFontFile> 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<PrismFontFile>(fr);
} else {
ref = fr.createFileDisposer(this, fr.getFileRefCounter());
}
tmpFonts.add(ref);
addFileCloserHook();
}
synchronized void removeTmpFont(WeakReference<PrismFontFile> 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<String> 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;
}
public synchronized PGFont createFont(String familyName, boolean bold,
boolean italic, float size) {
FontResource fr = null;
if (familyName != null && !familyName.isEmpty()) {
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);
}
public synchronized PGFont createFont(String name, float size) {
FontResource fr = null;
if (name != null && !name.isEmpty()) {
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;
}
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;
}
boolean isInstalledFont(String fileName) {
// avoid loading the full windows map. Ignore drive letter
// as its common to install on D: too in multi-boot.
String fileKey;
if (isWindows) {
if (fileName.toLowerCase().contains("\\windows\\fonts")) {
return true;
}
File f = new File(fileName);
fileKey = f.getName();
} else {
if (isMacOSX && fileName.toLowerCase().contains("/library/fonts")) {
// Most fonts are installed in either /System/Library/Fonts/
// or /Library/Fonts/
return true;
}
File f = new File(fileName);
// fileToFontMap key is the full path on non-windows
fileKey = f.getPath();
}
getFullNameToFileMap();
return fileToFontMap.get(fileKey.toLowerCase()) != 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<String,String> fontToFileMap = null;
/* TTF/OTF Font File to Font Full Name */
private HashMap<String,String> 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<String,String> 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<String,ArrayList<String>> 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 byte[] getFontPath();
private static native String regReadFontLink(String searchfont);
private static native String getEUDCFontFile();
private static void getPlatformFontDirs() {
if (userFontDir != null || sysFontDir != null) {
return;
}
byte [] pathBytes = getFontPath();
String path = new String(pathBytes);
int scIdx = path.indexOf(';');
if (scIdx < 0) {
sysFontDir = path;
} else {
sysFontDir = path.substring(0, scIdx);
userFontDir = path.substring(scIdx+1, path.length());
}
}
This will return an array of size 2, each element being an array
list of String
. The first (zeroth) array holds file
names, and, the second array holds the corresponding fontnames.
If the file does not have a corresponding font name, its corresponding
name is assigned an empty string "".
As a further complication, Windows 7 frequently lists a font twice,
once with some scaling values, and again without. We don't use this
so find these and exclude duplicates.
/**
* This will return an array of size 2, each element being an array
* list of <code>String</code>. The first (zeroth) array holds file
* names, and, the second array holds the corresponding fontnames.
* If the file does not have a corresponding font name, its corresponding
* name is assigned an empty string "".
* As a further complication, Windows 7 frequently lists a font twice,
* once with some scaling values, and again without. We don't use this
* so find these and exclude duplicates.
*/
static ArrayList<String> [] getLinkedFonts(String searchFont,
boolean addSearchFont) {
ArrayList<String> [] fontRegInfo = new ArrayList[2];
// index 0 = file names, 1 = font name.
// the name is only specified for TTC files.
fontRegInfo[0] = new ArrayList<String>();
fontRegInfo[1] = new ArrayList<String>();
if (isMacOSX) {
// Hotkey implementation of fallback font on Mac
fontRegInfo[0].add("/Library/Fonts/Arial Unicode.ttf");
fontRegInfo[1].add("Arial Unicode MS");
// Add Lucida Sans Regular to Mac OS X fallback list
fontRegInfo[0].add(jreFontDir + jreDefaultFontFile);
fontRegInfo[1].add(jreDefaultFont);
// Add Apple Symbols to Mac OS X fallback list
fontRegInfo[0].add("/System/Library/Fonts/Apple Symbols.ttf");
fontRegInfo[1].add("Apple Symbols");
// Add Apple Emoji Symbols to Mac OS X fallback list
fontRegInfo[0].add("/System/Library/Fonts/Apple Color Emoji.ttc");
fontRegInfo[1].add("Apple Color Emoji");
// Add CJK Ext B supplementary characters.
fontRegInfo[0].add("/System/Library/Fonts/STHeiti Light.ttf");
fontRegInfo[1].add("Heiti SC Light");
return fontRegInfo;
}
if (!isWindows) {
return fontRegInfo;
}
if (addSearchFont) {
fontRegInfo[0].add(null);
fontRegInfo[1].add(searchFont);
}
String fontRegBuf = regReadFontLink(searchFont);
if (fontRegBuf != null && fontRegBuf.length() > 0) {
// split registry data into null terminated strings
String[] fontRegList = fontRegBuf.split("\u0000");
int linkListLen = fontRegList.length;
for (int i=0; i < linkListLen; i++) {
String[] splitFontData = fontRegList[i].split(",");
int len = splitFontData.length;
String file = getPathNameWindows(splitFontData[0]);
String name = (len > 1) ? splitFontData[1] : null;
if (name != null && fontRegInfo[1].contains(name)) {
continue;
} else if (name == null && fontRegInfo[0].contains(file)) {
continue;
}
fontRegInfo[0].add(file);
fontRegInfo[1].add(name);
}
}
String eudcFontFile = getEUDCFontFile();
if (eudcFontFile != null) {
fontRegInfo[0].add(eudcFontFile);
fontRegInfo[1].add(null);
}
// Add Lucida Sans Regular to Windows fallback list
fontRegInfo[0].add(jreFontDir + jreDefaultFontFile);
fontRegInfo[1].add(jreDefaultFont);
if (PlatformUtil.isWinVistaOrLater()) {
// CJK Ext B Supplementary character fallbacks.
fontRegInfo[0].add(getPathNameWindows("mingliub.ttc"));
fontRegInfo[1].add("MingLiU-ExtB");
if (PlatformUtil.isWin7OrLater()) {
// Add Segoe UI Symbol to Windows 7 or later fallback list
fontRegInfo[0].add(getPathNameWindows("seguisym.ttf"));
fontRegInfo[1].add("Segoe UI Symbol");
} else {
// Add Cambria Math to Windows Vista fallback list
fontRegInfo[0].add(getPathNameWindows("cambria.ttc"));
fontRegInfo[1].add("Cambria Math");
}
}
return fontRegInfo;
}
/* 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<String,String> fontToFileMap,
HashMap<String,String> fontToFamilyNameMap,
HashMap<String,ArrayList<String>> familyToFontListMap) {
ArrayList<String> 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<String>();
}
unmappedFontNames.add(font);
}
}
}
if (unmappedFontNames != null) {
HashSet<String> unmappedFontFiles = new HashSet<String>();
// Used HashMap.clone() on SE but TV didn't support it.
HashMap<String,String> ffmapCopy = new HashMap<String,String>();
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<sz; i++) {
String name = unmappedFontNames.get(i);
String familyName = fontToFamilyNameMap.get(name);
if (familyName != null) {
ArrayList<String> family = familyToFontListMap.get(familyName);
if (family != null) {
if (family.size() <= 1) {
familyToFontListMap.remove(familyName);
}
}
}
fontToFamilyNameMap.remove(name);
}
}
}
}
private void resolveFontFiles(HashSet<String> unmappedFiles,
ArrayList<String> unmappedFonts,
HashMap<String,String> fontToFileMap,
HashMap<String,String> fontToFamilyNameMap,
HashMap<String,ArrayList<String>> 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<String> 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<String>();
familyToFontListMap.put(familyLC, familylist);
}
familylist.add(ttf.getFullName());
}
}
}
while (fn < ttf.getFontCount());
} catch (Exception e) {
if (debugFonts) {
e.printStackTrace();
}
}
}
}
static native void
populateFontFileNameMap(HashMap<String,String> fontToFileMap,
HashMap<String,String> fontToFamilyNameMap,
HashMap<String,ArrayList<String>>
familyToFontListMap,
Locale locale);
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;
}
String path = AccessController.doPrivileged(
new PrivilegedAction<String>() {
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.
}
private static ArrayList<String> allFamilyNames;
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<String> familyNames = new ArrayList<String>();
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)) {
familyNames.add(f);
}
}
Collections.sort(familyNames);
allFamilyNames = new ArrayList<String>(familyNames);
}
return allFamilyNames.toArray(STR_ARRAY);
}
private static ArrayList<String> allFontNames;
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<String> fontNames = new ArrayList<String>();
LogicalFont.addFullNames(fontNames);
if (embeddedFonts != null) {
for (PrismFontFile embeddedFont : embeddedFonts.values()) {
if (!fontNames.contains(embeddedFont.getFullName())) {
fontNames.add(embeddedFont.getFullName());
}
}
}
getFullNameToFileMap();
for (ArrayList<String> a : familyToFontListMap.values()) {
for (String s : a) {
fontNames.add(s);
}
}
Collections.sort(fontNames);
allFontNames = fontNames;
}
return allFontNames.toArray(STR_ARRAY);
}
public String[] getFontFullNames(String family) {
// 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<String> embeddedFamily = null;
for (PrismFontFile embeddedFont : embeddedFonts.values()) {
if (embeddedFont.getFamilyName().equalsIgnoreCase(family)) {
if (embeddedFamily == null) {
embeddedFamily = new ArrayList<String>();
}
embeddedFamily.add(embeddedFont.getFullName());
}
}
if (embeddedFamily != null) {
return embeddedFamily.toArray(STR_ARRAY);
}
}
getFullNameToFileMap();
family = family.toLowerCase();
ArrayList<String> 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<PrismFontFile> ref : tmpFonts) {
PrismFontFile font = ref.get();
if (font != null) {
font.disposeOnShutdown();
}
}
}
};
java.security.AccessController.doPrivileged(
(PrivilegedAction<Object>) () -> {
/* 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<String, PrismFontFile> embeddedFonts;
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 in a shared applet-type context, after all references to
* the applet and therefore the font are dropped, the file
* should be removed. This isn't so much an issue so long as
* the VM exists to serve a single FX app, but will be
* important in an app-context model.
* But also fonts that are over-written by new versions
* need to be cleaned up and that applies even in the single
* context.
* 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<num; i++) {
pFonts[i] = new PrismFont(fr[i], fr[i].getFullName(), size);
}
return pFonts;
}
return null;
}
registerEmbeddedFont(String name, String path) is a small subset of
registerEmbeddedFont(String name, InputStream fontStream)
It does not attempt to create a temporary file and has different
parameters.
Params: - name – font name
- path – Path name to system file
- size – font size
- register – whether the font should be registered.
- loadAll – whether to load all fonts if it is a TTC
Returns: font name extracted from font file
/**
* registerEmbeddedFont(String name, String path) is a small subset of
* registerEmbeddedFont(String name, InputStream fontStream)
* It does not attempt to create a temporary file and has different
* parameters.
*
* @param name font name
* @param path Path name to system file
* @param size font size
* @param register whether the font should be registered.
* @param loadAll whether to load all fonts if it is a TTC
* @return font name extracted from font file
*/
public PGFont[] loadEmbeddedFont(String name, String path,
float size,
boolean register,
boolean loadAll) {
if (!hasPermission()) {
return new PGFont[] { createFont(DEFAULT_FULLNAME, size) };
}
addFileCloserHook();
FontResource[] frArr =
loadEmbeddedFont1(name, path, register, false, false, loadAll);
if (frArr != null && frArr.length > 0) {
if (size <= 0) size = getSystemFontSize();
int num = frArr.length;
PGFont[] pgFonts = new PGFont[num];
for (int i=0; i<num; i++) {
pgFonts[i] =
new PrismFont(frArr[i], frArr[i].getFullName(), size);
}
return pgFonts;
}
return null;
}
/* This should make the embedded font eligible for reclaimation
* and subsequently, disposal of native resources, once any existing
* strong refs by the application are released.
*/
private void removeEmbeddedFont(String name) {
PrismFontFile font = embeddedFonts.get(name);
if (font == null) {
return;
}
embeddedFonts.remove(name);
String lcName = name.toLowerCase();
fontResourceMap.remove(lcName);
compResourceMap.remove(lcName);
// The following looks tedious, but if the compMap could have
// the font referenced via some lookup name that applies a style
// or used the family name, we need to find it and remove all
// references to it, so it can be collected.
Iterator<CompositeFontResource> 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<String, PrismFontFile>();
}
boolean registerEmbedded = true;
for (int i=0; i<frArr.length; i++) {
PrismFontFile fr = frArr[i];
String family = fr.getFamilyName();
if (family == null || family.length() == 0) return null;
String fullname = fr.getFullName();
if (fullname == null || fullname.length() == 0) return null;
String psname = fr.getPSName();
if (psname == null || psname.length() == 0) return null;
FontResource resource = embeddedFonts.get(fullname);
if (resource != null && fr.equals(resource)) {
/* Do not register the same font twice in the OS */
registerEmbedded = false;
}
}
if (registerEmbedded) {
/* Use filename from the resource so woff fonts are handled */
if (!registerEmbeddedFont(frArr[0].getFileName())) {
/* This font file can't be used by the underlying rasterizer */
return null;
}
}
/* If a temporary font is a copy but it is not decoded then it
* will not be anywhere the shutdown hook can see.
* That means if the font is keep for the entire life of the VM
* its file will not be deleted.
* The fix is to add this font to the list of temporary fonts.
*/
if (copy && !frArr[0].isDecoded()) {
addTmpFont(frArr[0]);
}
if (!register) {
return frArr;
}
/* If a font name is provided then we will also store that in the
* map as an alias, otherwise should use the only the real name,
* REMIND: its possible that either name may hide some installed
* version of the font, possibly one we haven't seen yet. But
* without loading all the platform fonts (expensive) this is
* difficult to ascertain. A contains() check here is therefore
* probably mostly futile.
*/
if (name != null && !name.isEmpty()) {
embeddedFonts.put(name, frArr[0]);
storeInMap(name, frArr[0]);
}
for (int i=0; i<frArr.length; i++) {
PrismFontFile fr = frArr[i];
String family = fr.getFamilyName();
String fullname = fr.getFullName();
removeEmbeddedFont(fullname);
embeddedFonts.put(fullname, fr);
storeInMap(fullname, fr);
family = family + dotStyleStr(fr.isBold(), fr.isItalic());
storeInMap(family, fr);
/* The remove call is to assist the case where we have
* previously mapped into the composite map a different style
* in this family as a partial match for the application request.
* This can occur when an application requested a bold font before
* it called Font.loadFont to register the bold font. It won't
* fix the cases that already happened, but will fix the future ones.
*/
compResourceMap.remove(family.toLowerCase());
}
return frArr;
}
private void
logFontInfo(String message,
HashMap<String,String> fontToFileMap,
HashMap<String,String> fontToFamilyNameMap,
HashMap<String,ArrayList<String>> 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<String,String> getFullNameToFileMap() {
if (fontToFileMap == null) {
HashMap<String, String>
tmpFontToFileMap = new HashMap<String,String>(100);
fontToFamilyNameMap = new HashMap<String,String>(100);
familyToFontListMap = new HashMap<String,ArrayList<String>>(50);
fileToFontMap = new HashMap<String,String>(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);
} 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;
}
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 {
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<String> familyList = familyToFontListMap.get(lcFamilyName);
if (familyList == null) {
familyList = new ArrayList<String>();
familyToFontListMap.put(lcFamilyName, familyList);
}
familyList.add(fullName);
}
void populateFontFileNameMapGeneric(String fontDir) {
final File dir = new File(fontDir);
String[] files = null;
try {
files = AccessController.doPrivileged(
(PrivilegedExceptionAction<String[]>) () -> dir.list(TTFilter.getInstance())
);
} catch (Exception e) {
}
if (files == null) {
return;
}
for (int i=0;i<files.length;i++) {
try {
String path = fontDir+File.separator+files[i];
/* Use filename from the resource so woff fonts are handled */
if (!registerEmbeddedFont(path)) {
/* This font file can't be used by the underlying rasterizer */
continue;
}
int index = 0;
PrismFontFile fr = createFontResource(path, index++);
if (fr == null) {
continue;
}
addToMaps(fr);
while (index < fr.getFontCount()) {
fr = createFontResource(path, index++);
if (fr == null) {
break;
}
addToMaps(fr);
}
} catch (Exception e) {
/* Keep going if anything bad happens with a font */
}
}
}
static native int getLCDContrastWin32();
private static native float getSystemFontSizeNative();
private static native String getSystemFontNative();
private static float systemFontSize;
private static String systemFontFamily = null;
private static String monospaceFontFamily = null;
public static float getSystemFontSize() {
if (systemFontSize == -1) {
if (isWindows) {
systemFontSize = getSystemFontSizeNative();
} else if (isMacOSX || isIOS) {
systemFontSize = MacFontFinder.getSystemFontSize();
} else if (isAndroid) {
systemFontSize = AndroidFontFinder.getSystemFontSize();
} else if (isEmbedded) {
try {
int screenDPI = Screen.getMainScreen().getResolutionY();
systemFontSize = ((float) screenDPI) / 6f; // 12 points
} catch (NullPointerException npe) {
// if no screen is defined
systemFontSize = 13f; // same as desktop Linux
}
} else {
systemFontSize = 13f; // Gnome uses 13.
}
}
return systemFontSize;
}
/* Applies to Windows and Mac. Not used on Linux */
public static String getSystemFont(String name) {
if (name.equals(LogicalFont.SYSTEM)) {
if (systemFontFamily == null) {
if (isWindows) {
systemFontFamily = getSystemFontNative();
if (systemFontFamily == null) {
systemFontFamily = "Arial"; // play it safe.
}
} else if (isMacOSX || isIOS) {
systemFontFamily = MacFontFinder.getSystemFont();
if (systemFontFamily == null) {
systemFontFamily = "Lucida Grande";
}
} else if (isAndroid) {
systemFontFamily = AndroidFontFinder.getSystemFont();
} else {
systemFontFamily = "Lucida Sans"; // for now.
}
}
return systemFontFamily;
} else if (name.equals(LogicalFont.SANS_SERIF)) {
return "Arial";
} else if (name.equals(LogicalFont.SERIF)) {
return "Times New Roman";
} else /* if (name.equals(LogicalFont.MONOSPACED)) */ {
if (monospaceFontFamily == null) {
if (isMacOSX) {
/* This code is intentionally commented:
* On the OS X the preferred monospaced font is Monaco,
* although this can be a good choice for most Mac application
* it is not suitable for JavaFX because Monaco does not
* have neither bold nor italic.
*/
// monospaceFontFamily = MacFontFinder.getMonospacedFont();
}
}
if (monospaceFontFamily == null) {
monospaceFontFamily = "Courier New";
}
return monospaceFontFamily;
}
}
/* Called from PrismFontFile which caches the return value */
static native short getSystemLCID();
}