com.sun.javafx.font.PrismFontFactory Maven / Gradle / Ivy
The 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