8067377: My hobby: caning, then then canning, the the can-can
Summary: Fix ALL the stutters!
Reviewed-by: rriggs, mchung, lancea
/*
* Copyright (c) 2008, 2014, 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 sun.font;
import java.awt.Font;
import java.awt.FontFormatException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.plaf.FontUIResource;
import sun.awt.AppContext;
import sun.awt.FontConfiguration;
import sun.awt.SunToolkit;
import sun.awt.util.ThreadGroupUtils;
import sun.java2d.FontSupport;
import sun.util.logging.PlatformLogger;
/**
* The base implementation of the {@link FontManager} interface. It implements
* the platform independent, shared parts of OpenJDK's FontManager
* implementations. The platform specific parts are declared as abstract
* methods that have to be implemented by specific implementations.
*/
public abstract class SunFontManager implements FontSupport, FontManagerForSGE {
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 static class T1Filter implements FilenameFilter {
public boolean accept(File dir,String name) {
if (noType1Font) {
return false;
}
/* all conveniently have the same suffix length */
int offset = name.length()-4;
if (offset <= 0) { /* must be at least A.pfa */
return false;
} else {
return(name.startsWith(".pfa", offset) ||
name.startsWith(".pfb", offset) ||
name.startsWith(".PFA", offset) ||
name.startsWith(".PFB", offset));
}
}
}
private static class TTorT1Filter 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 or A.pfa */
return false;
} else {
boolean isTT =
name.startsWith(".ttf", offset) ||
name.startsWith(".TTF", offset) ||
name.startsWith(".ttc", offset) ||
name.startsWith(".TTC", offset) ||
name.startsWith(".otf", offset) ||
name.startsWith(".OTF", offset);
if (isTT) {
return true;
} else if (noType1Font) {
return false;
} else {
return(name.startsWith(".pfa", offset) ||
name.startsWith(".pfb", offset) ||
name.startsWith(".PFA", offset) ||
name.startsWith(".PFB", offset));
}
}
}
}
public static final int FONTFORMAT_NONE = -1;
public static final int FONTFORMAT_TRUETYPE = 0;
public static final int FONTFORMAT_TYPE1 = 1;
public static final int FONTFORMAT_T2K = 2;
public static final int FONTFORMAT_TTC = 3;
public static final int FONTFORMAT_COMPOSITE = 4;
public static final int FONTFORMAT_NATIVE = 5;
/* Pool of 20 font file channels chosen because some UTF-8 locale
* composite fonts can use up to 16 platform fonts (including the
* Lucida fall back). This should prevent channel thrashing when
* dealing with one of these fonts.
* The pool array stores the fonts, rather than directly referencing
* the channels, as the font needs to do the open/close work.
*/
// MACOSX begin -- need to access these in subclass
protected static final int CHANNELPOOLSIZE = 20;
protected FileFont fontFileCache[] = new FileFont[CHANNELPOOLSIZE];
// MACOSX end
private int lastPoolIndex = 0;
/* Need to implement a simple linked list scheme for fast
* traversal and lookup.
* Also want to "fast path" dialog so there's minimal overhead.
*/
/* There are at exactly 20 composite fonts: 5 faces (but some are not
* usually different), in 4 styles. The array may be auto-expanded
* later if more are needed, eg for user-defined composites or locale
* variants.
*/
private int maxCompFont = 0;
private CompositeFont [] compFonts = new CompositeFont[20];
private ConcurrentHashMap<String, CompositeFont>
compositeFonts = new ConcurrentHashMap<String, CompositeFont>();
private ConcurrentHashMap<String, PhysicalFont>
physicalFonts = new ConcurrentHashMap<String, PhysicalFont>();
private ConcurrentHashMap<String, PhysicalFont>
registeredFonts = new ConcurrentHashMap<String, PhysicalFont>();
/* given a full name find the Font. Remind: there's duplication
* here in that this contains the content of compositeFonts +
* physicalFonts.
*/
// MACOSX begin -- need to access this in subclass
protected ConcurrentHashMap<String, Font2D>
fullNameToFont = new ConcurrentHashMap<String, Font2D>();
// MACOSX end
/* TrueType fonts have localised names. Support searching all
* of these before giving up on a name.
*/
private HashMap<String, TrueTypeFont> localeFullNamesToFont;
private PhysicalFont defaultPhysicalFont;
static boolean longAddresses;
private boolean loaded1dot0Fonts = false;
boolean loadedAllFonts = false;
boolean loadedAllFontFiles = false;
HashMap<String,String> jreFontMap;
HashSet<String> jreLucidaFontFiles;
String[] jreOtherFontFiles;
boolean noOtherJREFontFiles = false; // initial assumption.
public static final String lucidaFontName = "Lucida Sans Regular";
public static String jreLibDirName;
public static String jreFontDirName;
private static HashSet<String> missingFontFiles = null;
private String defaultFontName;
private String defaultFontFileName;
protected HashSet<String> registeredFontFiles = new HashSet<>();
private ArrayList<String> badFonts;
/* fontPath is the location of all fonts on the system, excluding the
* JRE's own font directory but including any path specified using the
* sun.java2d.fontpath property. Together with that property, it is
* initialised by the getPlatformFontPath() method
* This call must be followed by a call to registerFontDirs(fontPath)
* once any extra debugging path has been appended.
*/
protected String fontPath;
private FontConfiguration fontConfig;
/* discoveredAllFonts is set to true when all fonts on the font path are
* discovered. This usually also implies opening, validating and
* registering, but an implementation may be optimized to avold this.
* So see also "loadedAllFontFiles"
*/
private boolean discoveredAllFonts = false;
/* No need to keep consing up new instances - reuse a singleton.
* The trade-off is that these objects don't get GC'd.
*/
private static final FilenameFilter ttFilter = new TTFilter();
private static final FilenameFilter t1Filter = new T1Filter();
private Font[] allFonts;
private String[] allFamilies; // cache for default locale only
private Locale lastDefaultLocale;
public static boolean noType1Font;
/* Used to indicate required return type from toArray(..); */
private static String[] STR_ARRAY = new String[0];
/**
* Deprecated, unsupported hack - actually invokes a bug!
* Left in for a customer, don't remove.
*/
private boolean usePlatformFontMetrics = false;
/**
* Returns the global SunFontManager instance. This is similar to
* {@link FontManagerFactory#getInstance()} but it returns a
* SunFontManager instance instead. This is only used in internal classes
* where we can safely assume that a SunFontManager is to be used.
*
* @return the global SunFontManager instance
*/
public static SunFontManager getInstance() {
FontManager fm = FontManagerFactory.getInstance();
return (SunFontManager) fm;
}
public FilenameFilter getTrueTypeFilter() {
return ttFilter;
}
public FilenameFilter getType1Filter() {
return t1Filter;
}
@Override
public boolean usingPerAppContextComposites() {
return _usingPerAppContextComposites;
}
private void initJREFontMap() {
/* Key is familyname+style value as an int.
* Value is filename containing the font.
* If no mapping exists, it means there is no font file for the style
* If the mapping exists but the file doesn't exist in the deferred
* list then it means its not installed.
* This looks like a lot of code and strings but if it saves even
* a single file being opened at JRE start-up there's a big payoff.
* Lucida Sans is probably the only important case as the others
* are rarely used. Consider removing the other mappings if there's
* no evidence they are useful in practice.
*/
jreFontMap = new HashMap<String,String>();
jreLucidaFontFiles = new HashSet<String>();
if (isOpenJDK()) {
return;
}
/* Lucida Sans Family */
jreFontMap.put("lucida sans0", "LucidaSansRegular.ttf");
jreFontMap.put("lucida sans1", "LucidaSansDemiBold.ttf");
/* Lucida Sans full names (map Bold and DemiBold to same file) */
jreFontMap.put("lucida sans regular0", "LucidaSansRegular.ttf");
jreFontMap.put("lucida sans regular1", "LucidaSansDemiBold.ttf");
jreFontMap.put("lucida sans bold1", "LucidaSansDemiBold.ttf");
jreFontMap.put("lucida sans demibold1", "LucidaSansDemiBold.ttf");
/* Lucida Sans Typewriter Family */
jreFontMap.put("lucida sans typewriter0",
"LucidaTypewriterRegular.ttf");
jreFontMap.put("lucida sans typewriter1", "LucidaTypewriterBold.ttf");
/* Typewriter full names (map Bold and DemiBold to same file) */
jreFontMap.put("lucida sans typewriter regular0",
"LucidaTypewriter.ttf");
jreFontMap.put("lucida sans typewriter regular1",
"LucidaTypewriterBold.ttf");
jreFontMap.put("lucida sans typewriter bold1",
"LucidaTypewriterBold.ttf");
jreFontMap.put("lucida sans typewriter demibold1",
"LucidaTypewriterBold.ttf");
/* Lucida Bright Family */
jreFontMap.put("lucida bright0", "LucidaBrightRegular.ttf");
jreFontMap.put("lucida bright1", "LucidaBrightDemiBold.ttf");
jreFontMap.put("lucida bright2", "LucidaBrightItalic.ttf");
jreFontMap.put("lucida bright3", "LucidaBrightDemiItalic.ttf");
/* Lucida Bright full names (map Bold and DemiBold to same file) */
jreFontMap.put("lucida bright regular0", "LucidaBrightRegular.ttf");
jreFontMap.put("lucida bright regular1", "LucidaBrightDemiBold.ttf");
jreFontMap.put("lucida bright regular2", "LucidaBrightItalic.ttf");
jreFontMap.put("lucida bright regular3", "LucidaBrightDemiItalic.ttf");
jreFontMap.put("lucida bright bold1", "LucidaBrightDemiBold.ttf");
jreFontMap.put("lucida bright bold3", "LucidaBrightDemiItalic.ttf");
jreFontMap.put("lucida bright demibold1", "LucidaBrightDemiBold.ttf");
jreFontMap.put("lucida bright demibold3","LucidaBrightDemiItalic.ttf");
jreFontMap.put("lucida bright italic2", "LucidaBrightItalic.ttf");
jreFontMap.put("lucida bright italic3", "LucidaBrightDemiItalic.ttf");
jreFontMap.put("lucida bright bold italic3",
"LucidaBrightDemiItalic.ttf");
jreFontMap.put("lucida bright demibold italic3",
"LucidaBrightDemiItalic.ttf");
for (String ffile : jreFontMap.values()) {
jreLucidaFontFiles.add(ffile);
}
}
static {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
FontManagerNativeLibrary.load();
// JNI throws an exception if a class/method/field is not found,
// so there's no need to do anything explicit here.
initIDs();
switch (StrikeCache.nativeAddressSize) {
case 8: longAddresses = true; break;
case 4: longAddresses = false; break;
default: throw new RuntimeException("Unexpected address size");
}
noType1Font =
"true".equals(System.getProperty("sun.java2d.noType1Font"));
jreLibDirName =
System.getProperty("java.home","") + File.separator + "lib";
jreFontDirName = jreLibDirName + File.separator + "fonts";
File lucidaFile =
new File(jreFontDirName + File.separator + FontUtilities.LUCIDA_FILE_NAME);
return null;
}
});
}
public TrueTypeFont getEUDCFont() {
// Overridden in Windows.
return null;
}
/* Initialise ptrs used by JNI methods */
private static native void initIDs();
@SuppressWarnings("unchecked")
protected SunFontManager() {
initJREFontMap();
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
File badFontFile =
new File(jreFontDirName + File.separator +
"badfonts.txt");
if (badFontFile.exists()) {
FileInputStream fis = null;
try {
badFonts = new ArrayList<>();
fis = new FileInputStream(badFontFile);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
while (true) {
String name = br.readLine();
if (name == null) {
break;
} else {
if (FontUtilities.debugFonts()) {
FontUtilities.getLogger().warning("read bad font: " +
name);
}
badFonts.add(name);
}
}
} catch (IOException e) {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ioe) {
}
}
}
/* Here we get the fonts in jre/lib/fonts and register
* them so they are always available and preferred over
* other fonts. This needs to be registered before the
* composite fonts as otherwise some native font that
* corresponds may be found as we don't have a way to
* handle two fonts of the same name, so the JRE one
* must be the first one registered. Pass "true" to
* registerFonts method as on-screen these JRE fonts
* always go through the T2K rasteriser.
*/
if (FontUtilities.isLinux) {
/* Linux font configuration uses these fonts */
registerFontDir(jreFontDirName);
}
registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK,
true, false);
/* Create the font configuration and get any font path
* that might be specified.
*/
fontConfig = createFontConfiguration();
if (isOpenJDK()) {
String[] fontInfo = getDefaultPlatformFont();
defaultFontName = fontInfo[0];
defaultFontFileName = fontInfo[1];
}
String extraFontPath = fontConfig.getExtraFontPath();
/* In prior releases the debugging font path replaced
* all normally located font directories except for the
* JRE fonts dir. This directory is still always located
* and placed at the head of the path but as an
* augmentation to the previous behaviour the
* changes below allow you to additionally append to
* the font path by starting with append: or prepend by
* starting with a prepend: sign. Eg: to append
* -Dsun.java2d.fontpath=append:/usr/local/myfonts
* and to prepend
* -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp
*
* If there is an appendedfontpath it in the font
* configuration it is used instead of searching the
* system for dirs.
* The behaviour of append and prepend is then similar
* to the normal case. ie it goes after what
* you prepend and * before what you append. If the
* sun.java2d.fontpath property is used, but it
* neither the append or prepend syntaxes is used then
* as except for the JRE dir the path is replaced and it
* is up to you to make sure that all the right
* directories are located. This is platform and
* locale-specific so its almost impossible to get
* right, so it should be used with caution.
*/
boolean prependToPath = false;
boolean appendToPath = false;
String dbgFontPath =
System.getProperty("sun.java2d.fontpath");
if (dbgFontPath != null) {
if (dbgFontPath.startsWith("prepend:")) {
prependToPath = true;
dbgFontPath =
dbgFontPath.substring("prepend:".length());
} else if (dbgFontPath.startsWith("append:")) {
appendToPath = true;
dbgFontPath =
dbgFontPath.substring("append:".length());
}
}
if (FontUtilities.debugFonts()) {
PlatformLogger logger = FontUtilities.getLogger();
logger.info("JRE font directory: " + jreFontDirName);
logger.info("Extra font path: " + extraFontPath);
logger.info("Debug font path: " + dbgFontPath);
}
if (dbgFontPath != null) {
/* In debugging mode we register all the paths
* Caution: this is a very expensive call on Solaris:-
*/
fontPath = getPlatformFontPath(noType1Font);
if (extraFontPath != null) {
fontPath =
extraFontPath + File.pathSeparator + fontPath;
}
if (appendToPath) {
fontPath =
fontPath + File.pathSeparator + dbgFontPath;
} else if (prependToPath) {
fontPath =
dbgFontPath + File.pathSeparator + fontPath;
} else {
fontPath = dbgFontPath;
}
registerFontDirs(fontPath);
} else if (extraFontPath != null) {
/* If the font configuration contains an
* "appendedfontpath" entry, it is interpreted as a
* set of locations that should always be registered.
* It may be additional to locations normally found
* for that place, or it may be locations that need
* to have all their paths registered to locate all
* the needed platform names.
* This is typically when the same .TTF file is
* referenced from multiple font.dir files and all
* of these must be read to find all the native
* (XLFD) names for the font, so that X11 font APIs
* can be used for as many code points as possible.
*/
registerFontDirs(extraFontPath);
}
/* On Solaris, we need to register the Japanese TrueType
* directory so that we can find the corresponding
* bitmap fonts. This could be done by listing the
* directory in the font configuration file, but we
* don't want to confuse users with this quirk. There
* are no bitmap fonts for other writing systems that
* correspond to TrueType fonts and have matching XLFDs.
* We need to register the bitmap fonts only in
* environments where they're on the X font path, i.e.,
* in the Japanese locale. Note that if the X Toolkit
* is in use the font path isn't set up by JDK, but
* users of a JA locale should have it
* set up already by their login environment.
*/
if (FontUtilities.isSolaris && Locale.JAPAN.equals(Locale.getDefault())) {
registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT");
}
initCompositeFonts(fontConfig, null);
return null;
}
});
boolean platformFont = AccessController.doPrivileged(
new PrivilegedAction<Boolean>() {
public Boolean run() {
String prop =
System.getProperty("java2d.font.usePlatformFont");
String env = System.getenv("JAVA2D_USEPLATFORMFONT");
return "true".equals(prop) || env != null;
}
});
if (platformFont) {
usePlatformFontMetrics = true;
System.out.println("Enabling platform font metrics for win32. This is an unsupported option.");
System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases.");
System.out.println("It is appropriate only for use by applications which do not use any Java 2");
System.out.println("functionality. This property will be removed in a later release.");
}
}
/**
* This method is provided for internal and exclusive use by Swing.
*
* @param font representing a physical font.
* @return true if the underlying font is a TrueType or OpenType font
* that claims to support the Microsoft Windows encoding corresponding to
* the default file.encoding property of this JRE instance.
* This narrow value is useful for Swing to decide if the font is useful
* for the Windows Look and Feel, or, if a composite font should be
* used instead.
* The information used to make the decision is obtained from
* the ulCodePageRange fields in the font.
* A caller can use isLogicalFont(Font) in this class before calling
* this method and would not need to call this method if that
* returns true.
*/
// static boolean fontSupportsDefaultEncoding(Font font) {
// String encoding =
// (String) java.security.AccessController.doPrivileged(
// new sun.security.action.GetPropertyAction("file.encoding"));
// if (encoding == null || font == null) {
// return false;
// }
// encoding = encoding.toLowerCase(Locale.ENGLISH);
// return FontManager.fontSupportsEncoding(font, encoding);
// }
public Font2DHandle getNewComposite(String family, int style,
Font2DHandle handle) {
if (!(handle.font2D instanceof CompositeFont)) {
return handle;
}
CompositeFont oldComp = (CompositeFont)handle.font2D;
PhysicalFont oldFont = oldComp.getSlotFont(0);
if (family == null) {
family = oldFont.getFamilyName(null);
}
if (style == -1) {
style = oldComp.getStyle();
}
Font2D newFont = findFont2D(family, style, NO_FALLBACK);
if (!(newFont instanceof PhysicalFont)) {
newFont = oldFont;
}
PhysicalFont physicalFont = (PhysicalFont)newFont;
CompositeFont dialog2D =
(CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
if (dialog2D == null) { /* shouldn't happen */
return handle;
}
CompositeFont compFont = new CompositeFont(physicalFont, dialog2D);
Font2DHandle newHandle = new Font2DHandle(compFont);
return newHandle;
}
protected void registerCompositeFont(String compositeName,
String[] componentFileNames,
String[] componentNames,
int numMetricsSlots,
int[] exclusionRanges,
int[] exclusionMaxIndex,
boolean defer) {
CompositeFont cf = new CompositeFont(compositeName,
componentFileNames,
componentNames,
numMetricsSlots,
exclusionRanges,
exclusionMaxIndex, defer, this);
addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK);
synchronized (compFonts) {
compFonts[maxCompFont++] = cf;
}
}
/* This variant is used only when the application specifies
* a variant of composite fonts which prefers locale specific or
* proportional fonts.
*/
protected static void registerCompositeFont(String compositeName,
String[] componentFileNames,
String[] componentNames,
int numMetricsSlots,
int[] exclusionRanges,
int[] exclusionMaxIndex,
boolean defer,
ConcurrentHashMap<String, Font2D>
altNameCache) {
CompositeFont cf = new CompositeFont(compositeName,
componentFileNames,
componentNames,
numMetricsSlots,
exclusionRanges,
exclusionMaxIndex, defer,
SunFontManager.getInstance());
/* if the cache has an existing composite for this case, make
* its handle point to this new font.
* This ensures that when the altNameCache that is passed in
* is the global mapNameCache - ie we are running as an application -
* that any statically created java.awt.Font instances which already
* have a Font2D instance will have that re-directed to the new Font
* on subsequent uses. This is particularly important for "the"
* default font instance, or similar cases where a UI toolkit (eg
* Swing) has cached a java.awt.Font. Note that if Swing is using
* a custom composite APIs which update the standard composites have
* no effect - this is typically the case only when using the Windows
* L&F where these APIs would conflict with that L&F anyway.
*/
Font2D oldFont =altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH));
if (oldFont instanceof CompositeFont) {
oldFont.handle.font2D = cf;
}
altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf);
}
private void addCompositeToFontList(CompositeFont f, int rank) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().info("Add to Family "+ f.familyName +
", Font " + f.fullName + " rank="+rank);
}
f.setRank(rank);
compositeFonts.put(f.fullName, f);
fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f);
FontFamily family = FontFamily.getFamily(f.familyName);
if (family == null) {
family = new FontFamily(f.familyName, true, rank);
}
family.setFont(f, f.style);
}
/*
* Systems may have fonts with the same name.
* We want to register only one of such fonts (at least until
* such time as there might be APIs which can accommodate > 1).
* Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts,
* 4) Type1 fonts, 5) native fonts.
*
* If the new font has the same name as the old font, the higher
* ranked font gets added, replacing the lower ranked one.
* If the fonts are of equal rank, then make a special case of
* font configuration rank fonts, which are on closer inspection,
* OT/TT fonts such that the larger font is registered. This is
* a heuristic since a font may be "larger" in the sense of more
* code points, or be a larger "file" because it has more bitmaps.
* So it is possible that using filesize may lead to less glyphs, and
* using glyphs may lead to lower quality display. Probably number
* of glyphs is the ideal, but filesize is information we already
* have and is good enough for the known cases.
* Also don't want to register fonts that match JRE font families
* but are coming from a source other than the JRE.
* This will ensure that we will algorithmically style the JRE
* plain font and get the same set of glyphs for all styles.
*
* Note that this method returns a value
* if it returns the same object as its argument that means this
* font was newly registered.
* If it returns a different object it means this font already exists,
* and you should use that one.
* If it returns null means this font was not registered and none
* in that name is registered. The caller must find a substitute
*/
// MACOSX begin -- need to access this in subclass
protected PhysicalFont addToFontList(PhysicalFont f, int rank) {
// MACOSX end
String fontName = f.fullName;
String familyName = f.familyName;
if (fontName == null || "".equals(fontName)) {
return null;
}
if (compositeFonts.containsKey(fontName)) {
/* Don't register any font that has the same name as a composite */
return null;
}
f.setRank(rank);
if (!physicalFonts.containsKey(fontName)) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().info("Add to Family "+familyName +
", Font " + fontName + " rank="+rank);
}
physicalFonts.put(fontName, f);
FontFamily family = FontFamily.getFamily(familyName);
if (family == null) {
family = new FontFamily(familyName, false, rank);
family.setFont(f, f.style);
} else {
family.setFont(f, f.style);
}
fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f);
return f;
} else {
PhysicalFont newFont = f;
PhysicalFont oldFont = physicalFonts.get(fontName);
if (oldFont == null) {
return null;
}
/* If the new font is of an equal or higher rank, it is a
* candidate to replace the current one, subject to further tests.
*/
if (oldFont.getRank() >= rank) {
/* All fonts initialise their mapper when first
* used. If the mapper is non-null then this font
* has been accessed at least once. In that case
* do not replace it. This may be overly stringent,
* but its probably better not to replace a font that
* someone is already using without a compelling reason.
* Additionally the primary case where it is known
* this behaviour is important is in certain composite
* fonts, and since all the components of a given
* composite are usually initialised together this
* is unlikely. For this to be a problem, there would
* have to be a case where two different composites used
* different versions of the same-named font, and they
* were initialised and used at separate times.
* In that case we continue on and allow the new font to
* be installed, but replaceFont will continue to allow
* the original font to be used in Composite fonts.
*/
if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) {
return oldFont;
}
/* Normally we require a higher rank to replace a font,
* but as a special case, if the two fonts are the same rank,
* and are instances of TrueTypeFont we want the
* more complete (larger) one.
*/
if (oldFont.getRank() == rank) {
if (oldFont instanceof TrueTypeFont &&
newFont instanceof TrueTypeFont) {
TrueTypeFont oldTTFont = (TrueTypeFont)oldFont;
TrueTypeFont newTTFont = (TrueTypeFont)newFont;
if (oldTTFont.fileSize >= newTTFont.fileSize) {
return oldFont;
}
} else {
return oldFont;
}
}
/* Don't replace ever JRE fonts.
* This test is in case a font configuration references
* a Lucida font, which has been mapped to a Lucida
* from the host O/S. The assumption here is that any
* such font configuration file is probably incorrect, or
* the host O/S version is for the use of AWT.
* In other words if we reach here, there's a possible
* problem with our choice of font configuration fonts.
*/
if (oldFont.platName.startsWith(jreFontDirName)) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.warning("Unexpected attempt to replace a JRE " +
" font " + fontName + " from " +
oldFont.platName +
" with " + newFont.platName);
}
return oldFont;
}
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Replace in Family " + familyName +
",Font " + fontName + " new rank="+rank +
" from " + oldFont.platName +
" with " + newFont.platName);
}
replaceFont(oldFont, newFont);
physicalFonts.put(fontName, newFont);
fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH),
newFont);
FontFamily family = FontFamily.getFamily(familyName);
if (family == null) {
family = new FontFamily(familyName, false, rank);
family.setFont(newFont, newFont.style);
} else {
family.setFont(newFont, newFont.style);
}
return newFont;
} else {
return oldFont;
}
}
}
public Font2D[] getRegisteredFonts() {
PhysicalFont[] physFonts = getPhysicalFonts();
int mcf = maxCompFont; /* for MT-safety */
Font2D[] regFonts = new Font2D[physFonts.length+mcf];
System.arraycopy(compFonts, 0, regFonts, 0, mcf);
System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length);
return regFonts;
}
protected PhysicalFont[] getPhysicalFonts() {
return physicalFonts.values().toArray(new PhysicalFont[0]);
}
/* The class FontRegistrationInfo is used when a client says not
* to register a font immediately. This mechanism is used to defer
* initialisation of all the components of composite fonts at JRE
* start-up. The CompositeFont class is "aware" of this and when it
* is first used it asks for the registration of its components.
* Also in the event that any physical font is requested the
* deferred fonts are initialised before triggering a search of the
* system.
* Two maps are used. One to track the deferred fonts. The
* other to track the fonts that have been initialised through this
* mechanism.
*/
private static final class FontRegistrationInfo {
String fontFilePath;
String[] nativeNames;
int fontFormat;
boolean javaRasterizer;
int fontRank;
FontRegistrationInfo(String fontPath, String[] names, int format,
boolean useJavaRasterizer, int rank) {
this.fontFilePath = fontPath;
this.nativeNames = names;
this.fontFormat = format;
this.javaRasterizer = useJavaRasterizer;
this.fontRank = rank;
}
}
private final ConcurrentHashMap<String, FontRegistrationInfo>
deferredFontFiles =
new ConcurrentHashMap<String, FontRegistrationInfo>();
private final ConcurrentHashMap<String, Font2DHandle>
initialisedFonts = new ConcurrentHashMap<String, Font2DHandle>();
/* Remind: possibly enhance initialiseDeferredFonts() to be
* optionally given a name and a style and it could stop when it
* finds that font - but this would be a problem if two of the
* fonts reference the same font face name (cf the Solaris
* euro fonts).
*/
protected synchronized void initialiseDeferredFonts() {
for (String fileName : deferredFontFiles.keySet()) {
initialiseDeferredFont(fileName);
}
}
protected synchronized void registerDeferredJREFonts(String jreDir) {
for (FontRegistrationInfo info : deferredFontFiles.values()) {
if (info.fontFilePath != null &&
info.fontFilePath.startsWith(jreDir)) {
initialiseDeferredFont(info.fontFilePath);
}
}
}
public boolean isDeferredFont(String fileName) {
return deferredFontFiles.containsKey(fileName);
}
/* We keep a map of the files which contain the Lucida fonts so we
* don't need to search for them.
* But since we know what fonts these files contain, we can also avoid
* opening them to look for a font name we don't recognise - see
* findDeferredFont().
* For typical cases where the font isn't a JRE one the overhead is
* this method call, HashMap.get() and null reference test, then
* a boolean test of noOtherJREFontFiles.
*/
public
/*private*/ PhysicalFont findJREDeferredFont(String name, int style) {
PhysicalFont physicalFont;
String nameAndStyle = name.toLowerCase(Locale.ENGLISH) + style;
String fileName = jreFontMap.get(nameAndStyle);
if (fileName != null) {
fileName = jreFontDirName + File.separator + fileName;
if (deferredFontFiles.get(fileName) != null) {
physicalFont = initialiseDeferredFont(fileName);
if (physicalFont != null &&
(physicalFont.getFontName(null).equalsIgnoreCase(name) ||
physicalFont.getFamilyName(null).equalsIgnoreCase(name))
&& physicalFont.style == style) {
return physicalFont;
}
}
}
/* Iterate over the deferred font files looking for any in the
* jre directory that we didn't recognise, open each of these.
* In almost all installations this will quickly fall through
* because only the Lucidas will be present and jreOtherFontFiles
* will be empty.
* noOtherJREFontFiles is used so we can skip this block as soon
* as its determined that its not needed - almost always after the
* very first time through.
*/
if (noOtherJREFontFiles) {
return null;
}
synchronized (jreLucidaFontFiles) {
if (jreOtherFontFiles == null) {
HashSet<String> otherFontFiles = new HashSet<String>();
for (String deferredFile : deferredFontFiles.keySet()) {
File file = new File(deferredFile);
String dir = file.getParent();
String fname = file.getName();
/* skip names which aren't absolute, aren't in the JRE
* directory, or are known Lucida fonts.
*/
if (dir == null ||
!dir.equals(jreFontDirName) ||
jreLucidaFontFiles.contains(fname)) {
continue;
}
otherFontFiles.add(deferredFile);
}
jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY);
if (jreOtherFontFiles.length == 0) {
noOtherJREFontFiles = true;
}
}
for (int i=0; i<jreOtherFontFiles.length;i++) {
fileName = jreOtherFontFiles[i];
if (fileName == null) {
continue;
}
jreOtherFontFiles[i] = null;
physicalFont = initialiseDeferredFont(fileName);
if (physicalFont != null &&
(physicalFont.getFontName(null).equalsIgnoreCase(name) ||
physicalFont.getFamilyName(null).equalsIgnoreCase(name))
&& physicalFont.style == style) {
return physicalFont;
}
}
}
return null;
}
/* This skips JRE installed fonts. */
private PhysicalFont findOtherDeferredFont(String name, int style) {
for (String fileName : deferredFontFiles.keySet()) {
File file = new File(fileName);
String dir = file.getParent();
String fname = file.getName();
if (dir != null &&
dir.equals(jreFontDirName) &&
jreLucidaFontFiles.contains(fname)) {
continue;
}
PhysicalFont physicalFont = initialiseDeferredFont(fileName);
if (physicalFont != null &&
(physicalFont.getFontName(null).equalsIgnoreCase(name) ||
physicalFont.getFamilyName(null).equalsIgnoreCase(name)) &&
physicalFont.style == style) {
return physicalFont;
}
}
return null;
}
private PhysicalFont findDeferredFont(String name, int style) {
PhysicalFont physicalFont = findJREDeferredFont(name, style);
if (physicalFont != null) {
return physicalFont;
} else {
return findOtherDeferredFont(name, style);
}
}
public void registerDeferredFont(String fileNameKey,
String fullPathName,
String[] nativeNames,
int fontFormat,
boolean useJavaRasterizer,
int fontRank) {
FontRegistrationInfo regInfo =
new FontRegistrationInfo(fullPathName, nativeNames, fontFormat,
useJavaRasterizer, fontRank);
deferredFontFiles.put(fileNameKey, regInfo);
}
public synchronized
PhysicalFont initialiseDeferredFont(String fileNameKey) {
if (fileNameKey == null) {
return null;
}
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Opening deferred font file " + fileNameKey);
}
PhysicalFont physicalFont;
FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey);
if (regInfo != null) {
deferredFontFiles.remove(fileNameKey);
physicalFont = registerFontFile(regInfo.fontFilePath,
regInfo.nativeNames,
regInfo.fontFormat,
regInfo.javaRasterizer,
regInfo.fontRank);
if (physicalFont != null) {
/* Store the handle, so that if a font is bad, we
* retrieve the substituted font.
*/
initialisedFonts.put(fileNameKey, physicalFont.handle);
} else {
initialisedFonts.put(fileNameKey,
getDefaultPhysicalFont().handle);
}
} else {
Font2DHandle handle = initialisedFonts.get(fileNameKey);
if (handle == null) {
/* Probably shouldn't happen, but just in case */
physicalFont = getDefaultPhysicalFont();
} else {
physicalFont = (PhysicalFont)(handle.font2D);
}
}
return physicalFont;
}
public boolean isRegisteredFontFile(String name) {
return registeredFonts.containsKey(name);
}
public PhysicalFont getRegisteredFontFile(String name) {
return registeredFonts.get(name);
}
/* Note that the return value from this method is not always
* derived from this file, and may be null. See addToFontList for
* some explanation of this.
*/
public PhysicalFont registerFontFile(String fileName,
String[] nativeNames,
int fontFormat,
boolean useJavaRasterizer,
int fontRank) {
PhysicalFont regFont = registeredFonts.get(fileName);
if (regFont != null) {
return regFont;
}
PhysicalFont physicalFont = null;
try {
String name;
switch (fontFormat) {
case FONTFORMAT_TRUETYPE:
int fn = 0;
TrueTypeFont ttf;
do {
ttf = new TrueTypeFont(fileName, nativeNames, fn++,
useJavaRasterizer);
PhysicalFont pf = addToFontList(ttf, fontRank);
if (physicalFont == null) {
physicalFont = pf;
}
}
while (fn < ttf.getFontCount());
break;
case FONTFORMAT_TYPE1:
Type1Font t1f = new Type1Font(fileName, nativeNames);
physicalFont = addToFontList(t1f, fontRank);
break;
case FONTFORMAT_NATIVE:
NativeFont nf = new NativeFont(fileName, false);
physicalFont = addToFontList(nf, fontRank);
break;
default:
}
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Registered file " + fileName + " as font " +
physicalFont + " rank=" + fontRank);
}
} catch (FontFormatException ffe) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().warning("Unusable font: " +
fileName + " " + ffe.toString());
}
}
if (physicalFont != null &&
fontFormat != FONTFORMAT_NATIVE) {
registeredFonts.put(fileName, physicalFont);
}
return physicalFont;
}
public void registerFonts(String[] fileNames,
String[][] nativeNames,
int fontCount,
int fontFormat,
boolean useJavaRasterizer,
int fontRank, boolean defer) {
for (int i=0; i < fontCount; i++) {
if (defer) {
registerDeferredFont(fileNames[i],fileNames[i], nativeNames[i],
fontFormat, useJavaRasterizer, fontRank);
} else {
registerFontFile(fileNames[i], nativeNames[i],
fontFormat, useJavaRasterizer, fontRank);
}
}
}
/*
* This is the Physical font used when some other font on the system
* can't be located. There has to be at least one font or the font
* system is not useful and the graphics environment cannot sustain
* the Java platform.
*/
public PhysicalFont getDefaultPhysicalFont() {
if (defaultPhysicalFont == null) {
/* findFont2D will load all fonts before giving up the search.
* If the JRE Lucida isn't found (eg because the JRE fonts
* directory is missing), it could find another version of Lucida
* from the host system. This is OK because at that point we are
* trying to gracefully handle/recover from a system
* misconfiguration and this is probably a reasonable substitution.
*/
defaultPhysicalFont = (PhysicalFont)
findFont2D("Lucida Sans Regular", Font.PLAIN, NO_FALLBACK);
if (defaultPhysicalFont == null) {
defaultPhysicalFont = (PhysicalFont)
findFont2D("Arial", Font.PLAIN, NO_FALLBACK);
}
if (defaultPhysicalFont == null) {
/* Because of the findFont2D call above, if we reach here, we
* know all fonts have already been loaded, just accept any
* match at this point. If this fails we are in real trouble
* and I don't know how to recover from there being absolutely
* no fonts anywhere on the system.
*/
Iterator<PhysicalFont> i = physicalFonts.values().iterator();
if (i.hasNext()) {
defaultPhysicalFont = i.next();
} else {
throw new Error("Probable fatal error:No fonts found.");
}
}
}
return defaultPhysicalFont;
}
public Font2D getDefaultLogicalFont(int style) {
return findFont2D("dialog", style, NO_FALLBACK);
}
/*
* return String representation of style prepended with "."
* This is useful for performance to avoid unnecessary string operations.
*/
private static String dotStyleStr(int num) {
switch(num){
case Font.BOLD:
return ".bold";
case Font.ITALIC:
return ".italic";
case Font.ITALIC | Font.BOLD:
return ".bolditalic";
default:
return ".plain";
}
}
/* This is implemented only on windows and is called from code that
* executes only on windows. This isn't pretty but its not a precedent
* in this file. This very probably should be cleaned up at some point.
*/
protected void
populateFontFileNameMap(HashMap<String,String> fontToFileMap,
HashMap<String,String> fontToFamilyNameMap,
HashMap<String,ArrayList<String>>
familyToFontListMap,
Locale locale) {
}
/* 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 HashMap<String,String> fontToFileMap = 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;
/* The directories which contain platform fonts */
private String[] pathDirs = null;
private boolean haveCheckedUnreferencedFontFiles;
private String[] getFontFilesFromPath(boolean noType1) {
final FilenameFilter filter;
if (noType1) {
filter = ttFilter;
} else {
filter = new TTorT1Filter();
}
return (String[])AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
if (pathDirs.length == 1) {
File dir = new File(pathDirs[0]);
String[] files = dir.list(filter);
if (files == null) {
return new String[0];
}
for (int f=0; f<files.length; f++) {
files[f] = files[f].toLowerCase();
}
return files;
} else {
ArrayList<String> fileList = new ArrayList<String>();
for (int i = 0; i< pathDirs.length; i++) {
File dir = new File(pathDirs[i]);
String[] files = dir.list(filter);
if (files == null) {
continue;
}
for (int f=0; f<files.length ; f++) {
fileList.add(files[f].toLowerCase());
}
}
return fileList.toArray(STR_ARRAY);
}
}
});
}
/* 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() {
ArrayList<String> unmappedFontNames = null;
for (String font : fontToFamilyNameMap.keySet()) {
String file = fontToFileMap.get(font);
if (file == null) {
if (font.indexOf(" ") > 0) {
String newName = font.replaceFirst(" ", " ");
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>();
/* Every font key in fontToFileMap ought to correspond to a
* font key in fontToFamilyNameMap. Entries that don't seem
* to correspond are likely fonts that were named differently
* by GDI than in the registry. One known cause of this is when
* Windows has had its regional settings changed so that from
* GDI we get a localised (eg Chinese or Japanese) name for the
* font, but the registry retains the English version of the name
* that corresponded to the "install" locale for windows.
* Since we are in this code block because there are unmapped
* font names, we can look to find unused font->file mappings
* and then open the files to read the names. We don't generally
* want to open font files, as its a performance hit, but this
* occurs only for a small number of fonts on specific system
* configs - ie is believed that a "true" Japanese windows would
* have JA names in the registry too.
* Clone fontToFileMap and remove from the clone all keys which
* match a fontToFamilyNameMap key. What remains maps to the
* files we want to open to find the fonts GDI returned.
* A font in such a file is added to the fontToFileMap after
* checking its one of the unmappedFontNames we are looking for.
* The original name that didn't map is removed from fontToFileMap
* so essentially this "fixes up" fontToFileMap to use the same
* name as GDI.
* Also note that typically the fonts for which this occurs in
* CJK locales are TTC fonts and not all fonts in a TTC may have
* localised names. Eg MSGOTHIC.TTC contains 3 fonts and one of
* them "MS UI Gothic" has no JA name whereas the other two do.
* So not every font in these files is unmapped or new.
*/
@SuppressWarnings("unchecked")
HashMap<String,String> ffmapCopy =
(HashMap<String,String>)(fontToFileMap.clone());
for (String key : fontToFamilyNameMap.keySet()) {
ffmapCopy.remove(key);
}
for (String key : ffmapCopy.keySet()) {
unmappedFontFiles.add(ffmapCopy.get(key));
fontToFileMap.remove(key);
}
resolveFontFiles(unmappedFontFiles, unmappedFontNames);
/* If there are still unmapped font names, this means there's
* something that wasn't in the registry. We need to get all
* the font files directly and look at the ones that weren't
* found in the registry.
*/
if (unmappedFontNames.size() > 0) {
/* getFontFilesFromPath() returns all lower case names.
* To compare we also need lower case
* versions of the names from the registry.
*/
ArrayList<String> registryFiles = new ArrayList<String>();
for (String regFile : fontToFileMap.values()) {
registryFiles.add(regFile.toLowerCase());
}
/* We don't look for Type1 files here as windows will
* not enumerate these, so aren't useful in reconciling
* GDI's unmapped files. We do find these later when
* we enumerate all fonts.
*/
for (String pathFile : getFontFilesFromPath(true)) {
if (!registryFiles.contains(pathFile)) {
unmappedFontFiles.add(pathFile);
}
}
resolveFontFiles(unmappedFontFiles, unmappedFontNames);
}
/* 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);
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("No file for font:" + name);
}
}
}
}
}
/**
* In some cases windows may have fonts in the fonts folder that
* don't show up in the registry or in the GDI calls to enumerate fonts.
* The only way to find these is to list the directory. We invoke this
* only in getAllFonts/Families, so most searches for a specific
* font that is satisfied by the GDI/registry calls don't take the
* additional hit of listing the directory. This hit is small enough
* that its not significant in these 'enumerate all the fonts' cases.
* The basic approach is to cross-reference the files windows found
* with the ones in the directory listing approach, and for each
* in the latter list that is missing from the former list, register it.
*/
private synchronized void checkForUnreferencedFontFiles() {
if (haveCheckedUnreferencedFontFiles) {
return;
}
haveCheckedUnreferencedFontFiles = true;
if (!FontUtilities.isWindows) {
return;
}
/* getFontFilesFromPath() returns all lower case names.
* To compare we also need lower case
* versions of the names from the registry.
*/
ArrayList<String> registryFiles = new ArrayList<String>();
for (String regFile : fontToFileMap.values()) {
registryFiles.add(regFile.toLowerCase());
}
/* To avoid any issues with concurrent modification, create
* copies of the existing maps, add the new fonts into these
* and then replace the references to the old ones with the
* new maps. ConcurrentHashmap is another option but its a lot
* more changes and with this exception, these maps are intended
* to be static.
*/
HashMap<String,String> fontToFileMap2 = null;
HashMap<String,String> fontToFamilyNameMap2 = null;
HashMap<String,ArrayList<String>> familyToFontListMap2 = null;;
for (String pathFile : getFontFilesFromPath(false)) {
if (!registryFiles.contains(pathFile)) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Found non-registry file : " + pathFile);
}
PhysicalFont f = registerFontFile(getPathName(pathFile));
if (f == null) {
continue;
}
if (fontToFileMap2 == null) {
fontToFileMap2 = new HashMap<String,String>(fontToFileMap);
fontToFamilyNameMap2 =
new HashMap<String,String>(fontToFamilyNameMap);
familyToFontListMap2 = new
HashMap<String,ArrayList<String>>(familyToFontListMap);
}
String fontName = f.getFontName(null);
String family = f.getFamilyName(null);
String familyLC = family.toLowerCase();
fontToFamilyNameMap2.put(fontName, family);
fontToFileMap2.put(fontName, pathFile);
ArrayList<String> fonts = familyToFontListMap2.get(familyLC);
if (fonts == null) {
fonts = new ArrayList<String>();
} else {
fonts = new ArrayList<String>(fonts);
}
fonts.add(fontName);
familyToFontListMap2.put(familyLC, fonts);
}
}
if (fontToFileMap2 != null) {
fontToFileMap = fontToFileMap2;
familyToFontListMap = familyToFontListMap2;
fontToFamilyNameMap = fontToFamilyNameMap2;
}
}
private void resolveFontFiles(HashSet<String> unmappedFiles,
ArrayList<String> unmappedFonts) {
Locale l = SunToolkit.getStartupLocale();
for (String file : unmappedFiles) {
try {
int fn = 0;
TrueTypeFont ttf;
String fullPath = getPathName(file);
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Trying to resolve file " + fullPath);
}
do {
ttf = new TrueTypeFont(fullPath, null, fn++, false);
// prefer the font's locale name.
String fontName = ttf.getFontName(l).toLowerCase();
if (unmappedFonts.contains(fontName)) {
fontToFileMap.put(fontName, file);
unmappedFonts.remove(fontName);
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Resolved absent registry entry for " +
fontName + " located in " + fullPath);
}
}
}
while (fn < ttf.getFontCount());
} catch (Exception e) {
}
}
}
/* Hardwire the English names and expected file names of fonts
* commonly used at start up. Avoiding until later even the small
* cost of calling platform APIs to locate these can help.
* The code that registers these fonts needs to "bail" if any
* of the files do not exist, so it will verify the existence of
* all non-null file names first.
* They are added in to a map with nominally the first
* word in the name of the family as the key. In all the cases
* we are using the family name is a single word, and as is
* more or less required the family name is the initial sequence
* in a full name. So lookup first finds the matching description,
* then registers the whole family, returning the right font.
*/
public static class FamilyDescription {
public String familyName;
public String plainFullName;
public String boldFullName;
public String italicFullName;
public String boldItalicFullName;
public String plainFileName;
public String boldFileName;
public String italicFileName;
public String boldItalicFileName;
}
static HashMap<String, FamilyDescription> platformFontMap;
/**
* default implementation does nothing.
*/
public HashMap<String, FamilyDescription> populateHardcodedFileNameMap() {
return new HashMap<String, FamilyDescription>(0);
}
Font2D findFontFromPlatformMap(String lcName, int style) {
if (platformFontMap == null) {
platformFontMap = populateHardcodedFileNameMap();
}
if (platformFontMap == null || platformFontMap.size() == 0) {
return null;
}
int spaceIndex = lcName.indexOf(' ');
String firstWord = lcName;
if (spaceIndex > 0) {
firstWord = lcName.substring(0, spaceIndex);
}
FamilyDescription fd = platformFontMap.get(firstWord);
if (fd == null) {
return null;
}
/* Once we've established that its at least the first word,
* we need to dig deeper to make sure its a match for either
* a full name, or the family name, to make sure its not
* a request for some other font that just happens to start
* with the same first word.
*/
int styleIndex = -1;
if (lcName.equalsIgnoreCase(fd.plainFullName)) {
styleIndex = 0;
} else if (lcName.equalsIgnoreCase(fd.boldFullName)) {
styleIndex = 1;
} else if (lcName.equalsIgnoreCase(fd.italicFullName)) {
styleIndex = 2;
} else if (lcName.equalsIgnoreCase(fd.boldItalicFullName)) {
styleIndex = 3;
}
if (styleIndex == -1 && !lcName.equalsIgnoreCase(fd.familyName)) {
return null;
}
String plainFile = null, boldFile = null,
italicFile = null, boldItalicFile = null;
boolean failure = false;
/* In a terminal server config, its possible that getPathName()
* will return null, if the file doesn't exist, hence the null
* checks on return. But in the normal client config we need to
* follow this up with a check to see if all the files really
* exist for the non-null paths.
*/
getPlatformFontDirs(noType1Font);
if (fd.plainFileName != null) {
plainFile = getPathName(fd.plainFileName);
if (plainFile == null) {
failure = true;
}
}
if (fd.boldFileName != null) {
boldFile = getPathName(fd.boldFileName);
if (boldFile == null) {
failure = true;
}
}
if (fd.italicFileName != null) {
italicFile = getPathName(fd.italicFileName);
if (italicFile == null) {
failure = true;
}
}
if (fd.boldItalicFileName != null) {
boldItalicFile = getPathName(fd.boldItalicFileName);
if (boldItalicFile == null) {
failure = true;
}
}
if (failure) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().
info("Hardcoded file missing looking for " + lcName);
}
platformFontMap.remove(firstWord);
return null;
}
/* Some of these may be null,as not all styles have to exist */
final String[] files = {
plainFile, boldFile, italicFile, boldItalicFile } ;
failure = java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Boolean>() {
public Boolean run() {
for (int i=0; i<files.length; i++) {
if (files[i] == null) {
continue;
}
File f = new File(files[i]);
if (!f.exists()) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
});
if (failure) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().
info("Hardcoded file missing looking for " + lcName);
}
platformFontMap.remove(firstWord);
return null;
}
/* If we reach here we know that we have all the files we
* expect, so all should be fine so long as the contents
* are what we'd expect. Now on to registering the fonts.
* Currently this code only looks for TrueType fonts, so format
* and rank can be specified without looking at the filename.
*/
Font2D font = null;
for (int f=0;f<files.length;f++) {
if (files[f] == null) {
continue;
}
PhysicalFont pf =
registerFontFile(files[f], null,
FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK);
if (f == styleIndex) {
font = pf;
}
}
/* Two general cases need a bit more work here.
* 1) If font is null, then it was perhaps a request for a
* non-existent font, such as "Tahoma Italic", or a family name -
* where family and full name of the plain font differ.
* Fall back to finding the closest one in the family.
* This could still fail if a client specified "Segoe" instead of
* "Segoe UI".
* 2) The request is of the form "MyFont Bold", style=Font.ITALIC,
* and so we want to see if there's a Bold Italic font, or
* "MyFamily", style=Font.BOLD, and we may have matched the plain,
* but now need to revise that to the BOLD font.
*/
FontFamily fontFamily = FontFamily.getFamily(fd.familyName);
if (fontFamily != null) {
if (font == null) {
font = fontFamily.getFont(style);
if (font == null) {
font = fontFamily.getClosestStyle(style);
}
} else if (style > 0 && style != font.style) {
style |= font.style;
font = fontFamily.getFont(style);
if (font == null) {
font = fontFamily.getClosestStyle(style);
}
}
}
return font;
}
private synchronized HashMap<String,String> getFullNameToFileMap() {
if (fontToFileMap == null) {
pathDirs = getPlatformFontDirs(noType1Font);
fontToFileMap = new HashMap<String,String>(100);
fontToFamilyNameMap = new HashMap<String,String>(100);
familyToFontListMap = new HashMap<String,ArrayList<String>>(50);
populateFontFileNameMap(fontToFileMap,
fontToFamilyNameMap,
familyToFontListMap,
Locale.ENGLISH);
if (FontUtilities.isWindows) {
resolveWindowsFonts();
}
if (FontUtilities.isLogging()) {
logPlatformFontInfo();
}
}
return fontToFileMap;
}
private void logPlatformFontInfo() {
PlatformLogger logger = FontUtilities.getLogger();
for (int i=0; i< pathDirs.length;i++) {
logger.info("fontdir="+pathDirs[i]);
}
for (String keyName : fontToFileMap.keySet()) {
logger.info("font="+keyName+" file="+ fontToFileMap.get(keyName));
}
for (String keyName : fontToFamilyNameMap.keySet()) {
logger.info("font="+keyName+" family="+
fontToFamilyNameMap.get(keyName));
}
for (String keyName : familyToFontListMap.keySet()) {
logger.info("family="+keyName+ " fonts="+
familyToFontListMap.get(keyName));
}
}
/* Note this return list excludes logical fonts and JRE fonts */
protected String[] getFontNamesFromPlatform() {
if (getFullNameToFileMap().size() == 0) {
return null;
}
checkForUnreferencedFontFiles();
/* This odd code with TreeMap is used to preserve a historical
* behaviour wrt the sorting order .. */
ArrayList<String> fontNames = new ArrayList<String>();
for (ArrayList<String> a : familyToFontListMap.values()) {
for (String s : a) {
fontNames.add(s);
}
}
return fontNames.toArray(STR_ARRAY);
}
public boolean gotFontsFromPlatform() {
return getFullNameToFileMap().size() != 0;
}
public String getFileNameForFontName(String fontName) {
String fontNameLC = fontName.toLowerCase(Locale.ENGLISH);
return fontToFileMap.get(fontNameLC);
}
private PhysicalFont registerFontFile(String file) {
if (new File(file).isAbsolute() &&
!registeredFonts.contains(file)) {
int fontFormat = FONTFORMAT_NONE;
int fontRank = Font2D.UNKNOWN_RANK;
if (ttFilter.accept(null, file)) {
fontFormat = FONTFORMAT_TRUETYPE;
fontRank = Font2D.TTF_RANK;
} else if
(t1Filter.accept(null, file)) {
fontFormat = FONTFORMAT_TYPE1;
fontRank = Font2D.TYPE1_RANK;
}
if (fontFormat == FONTFORMAT_NONE) {
return null;
}
return registerFontFile(file, null, fontFormat, false, fontRank);
}
return null;
}
/* Used to register any font files that are found by platform APIs
* that weren't previously found in the standard font locations.
* the isAbsolute() check is needed since that's whats stored in the
* set, and on windows, the fonts in the system font directory that
* are in the fontToFileMap are just basenames. We don't want to try
* to register those again, but we do want to register other registry
* installed fonts.
*/
protected void registerOtherFontFiles(HashSet<String> registeredFontFiles) {
if (getFullNameToFileMap().size() == 0) {
return;
}
for (String file : fontToFileMap.values()) {
registerFontFile(file);
}
}
public boolean
getFamilyNamesFromPlatform(TreeMap<String,String> familyNames,
Locale requestedLocale) {
if (getFullNameToFileMap().size() == 0) {
return false;
}
checkForUnreferencedFontFiles();
for (String name : fontToFamilyNameMap.values()) {
familyNames.put(name.toLowerCase(requestedLocale), name);
}
return true;
}
/* Path may be absolute or a base file name relative to one of
* the platform font directories
*/
private String getPathName(final String s) {
File f = new File(s);
if (f.isAbsolute()) {
return s;
} else if (pathDirs.length==1) {
return pathDirs[0] + File.separator + s;
} else {
String path = java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<String>() {
public String run() {
for (int p=0; p<pathDirs.length; p++) {
File f = new File(pathDirs[p] +File.separator+ s);
if (f.exists()) {
return f.getAbsolutePath();
}
}
return null;
}
});
if (path != null) {
return path;
}
}
return s; // shouldn't happen, but harmless
}
/* lcName is required to be lower case for use as a key.
* lcName may be a full name, or a family name, and style may
* be specified in addition to either of these. So be sure to
* get the right one. Since an app *could* ask for "Foo Regular"
* and later ask for "Foo Italic", if we don't register all the
* styles, then logic in findFont2D may try to style the original
* so we register the entire family if we get a match here.
* This is still a big win because this code is invoked where
* otherwise we would register all fonts.
* It's also useful for the case where "Foo Bold" was specified with
* style Font.ITALIC, as we would want in that case to try to return
* "Foo Bold Italic" if it exists, and it is only by locating "Foo Bold"
* and opening it that we really "know" it's Bold, and can look for
* a font that supports that and the italic style.
* The code in here is not overtly windows-specific but in fact it
* is unlikely to be useful as is on other platforms. It is maintained
* in this shared source file to be close to its sole client and
* because so much of the logic is intertwined with the logic in
* findFont2D.
*/
private Font2D findFontFromPlatform(String lcName, int style) {
if (getFullNameToFileMap().size() == 0) {
return null;
}
ArrayList<String> family = null;
String fontFile = null;
String familyName = fontToFamilyNameMap.get(lcName);
if (familyName != null) {
fontFile = fontToFileMap.get(lcName);
family = familyToFontListMap.get
(familyName.toLowerCase(Locale.ENGLISH));
} else {
family = familyToFontListMap.get(lcName); // is lcName is a family?
if (family != null && family.size() > 0) {
String lcFontName = family.get(0).toLowerCase(Locale.ENGLISH);
if (lcFontName != null) {
familyName = fontToFamilyNameMap.get(lcFontName);
}
}
}
if (family == null || familyName == null) {
return null;
}
String [] fontList = family.toArray(STR_ARRAY);
if (fontList.length == 0) {
return null;
}
/* first check that for every font in this family we can find
* a font file. The specific reason for doing this is that
* in at least one case on Windows a font has the face name "David"
* but the registry entry is "David Regular". That is the "unique"
* name of the font but in other cases the registry contains the
* "full" name. See the specifications of name ids 3 and 4 in the
* TrueType 'name' table.
* In general this could cause a problem that we fail to register
* if we all members of a family that we may end up mapping to
* the wrong font member: eg return Bold when Plain is needed.
*/
for (int f=0;f<fontList.length;f++) {
String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
String fileName = fontToFileMap.get(fontNameLC);
if (fileName == null) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Platform lookup : No file for font " +
fontList[f] + " in family " +familyName);
}
return null;
}
}
/* Currently this code only looks for TrueType fonts, so format
* and rank can be specified without looking at the filename.
*/
PhysicalFont physicalFont = null;
if (fontFile != null) {
physicalFont = registerFontFile(getPathName(fontFile), null,
FONTFORMAT_TRUETYPE, false,
Font2D.TTF_RANK);
}
/* Register all fonts in this family. */
for (int f=0;f<fontList.length;f++) {
String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
String fileName = fontToFileMap.get(fontNameLC);
if (fontFile != null && fontFile.equals(fileName)) {
continue;
}
/* Currently this code only looks for TrueType fonts, so format
* and rank can be specified without looking at the filename.
*/
registerFontFile(getPathName(fileName), null,
FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK);
}
Font2D font = null;
FontFamily fontFamily = FontFamily.getFamily(familyName);
/* Handle case where request "MyFont Bold", style=Font.ITALIC */
if (physicalFont != null) {
style |= physicalFont.style;
}
if (fontFamily != null) {
font = fontFamily.getFont(style);
if (font == null) {
font = fontFamily.getClosestStyle(style);
}
}
return font;
}
private ConcurrentHashMap<String, Font2D> fontNameCache =
new ConcurrentHashMap<String, Font2D>();
/*
* The client supplies a name and a style.
* The name could be a family name, or a full name.
* A font may exist with the specified style, or it may
* exist only in some other style. For non-native fonts the scaler
* may be able to emulate the required style.
*/
public Font2D findFont2D(String name, int style, int fallback) {
String lowerCaseName = name.toLowerCase(Locale.ENGLISH);
String mapName = lowerCaseName + dotStyleStr(style);
Font2D font;
/* If preferLocaleFonts() or preferProportionalFonts() has been
* called we may be using an alternate set of composite fonts in this
* app context. The presence of a pre-built name map indicates whether
* this is so, and gives access to the alternate composite for the
* name.
*/
if (_usingPerAppContextComposites) {
@SuppressWarnings("unchecked")
ConcurrentHashMap<String, Font2D> altNameCache =
(ConcurrentHashMap<String, Font2D>)
AppContext.getAppContext().get(CompositeFont.class);
if (altNameCache != null) {
font = altNameCache.get(mapName);
} else {
font = null;
}
} else {
font = fontNameCache.get(mapName);
}
if (font != null) {
return font;
}
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().info("Search for font: " + name);
}
// The check below is just so that the bitmap fonts being set by
// AWT and Swing thru the desktop properties do not trigger the
// the load fonts case. The two bitmap fonts are now mapped to
// appropriate equivalents for serif and sansserif.
// Note that the cost of this comparison is only for the first
// call until the map is filled.
if (FontUtilities.isWindows) {
if (lowerCaseName.equals("ms sans serif")) {
name = "sansserif";
} else if (lowerCaseName.equals("ms serif")) {
name = "serif";
}
}
/* This isn't intended to support a client passing in the
* string default, but if a client passes in null for the name
* the java.awt.Font class internally substitutes this name.
* So we need to recognise it here to prevent a loadFonts
* on the unrecognised name. The only potential problem with
* this is it would hide any real font called "default"!
* But that seems like a potential problem we can ignore for now.
*/
if (lowerCaseName.equals("default")) {
name = "dialog";
}
/* First see if its a family name. */
FontFamily family = FontFamily.getFamily(name);
if (family != null) {
font = family.getFontWithExactStyleMatch(style);
if (font == null) {
font = findDeferredFont(name, style);
}
if (font == null) {
font = family.getFont(style);
}
if (font == null) {
font = family.getClosestStyle(style);
}
if (font != null) {
fontNameCache.put(mapName, font);
return font;
}
}
/* If it wasn't a family name, it should be a full name of
* either a composite, or a physical font
*/
font = fullNameToFont.get(lowerCaseName);
if (font != null) {
/* Check that the requested style matches the matched font's style.
* But also match style automatically if the requested style is
* "plain". This because the existing behaviour is that the fonts
* listed via getAllFonts etc always list their style as PLAIN.
* This does lead to non-commutative behaviours where you might
* start with "Lucida Sans Regular" and ask for a BOLD version
* and get "Lucida Sans DemiBold" but if you ask for the PLAIN
* style of "Lucida Sans DemiBold" you get "Lucida Sans DemiBold".
* This consistent however with what happens if you have a bold
* version of a font and no plain version exists - alg. styling
* doesn't "unbolden" the font.
*/
if (font.style == style || style == Font.PLAIN) {
fontNameCache.put(mapName, font);
return font;
} else {
/* If it was a full name like "Lucida Sans Regular", but
* the style requested is "bold", then we want to see if
* there's the appropriate match against another font in
* that family before trying to load all fonts, or applying a
* algorithmic styling
*/
family = FontFamily.getFamily(font.getFamilyName(null));
if (family != null) {
Font2D familyFont = family.getFont(style|font.style);
/* We exactly matched the requested style, use it! */
if (familyFont != null) {
fontNameCache.put(mapName, familyFont);
return familyFont;
} else {
/* This next call is designed to support the case
* where bold italic is requested, and if we must
* style, then base it on either bold or italic -
* not on plain!
*/
familyFont = family.getClosestStyle(style|font.style);
if (familyFont != null) {
/* The next check is perhaps one
* that shouldn't be done. ie if we get this
* far we have probably as close a match as we
* are going to get. We could load all fonts to
* see if somehow some parts of the family are
* loaded but not all of it.
*/
if (familyFont.canDoStyle(style|font.style)) {
fontNameCache.put(mapName, familyFont);
return familyFont;
}
}
}
}
}
}
if (FontUtilities.isWindows) {
font = findFontFromPlatformMap(lowerCaseName, style);
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("findFontFromPlatformMap returned " + font);
}
if (font != null) {
fontNameCache.put(mapName, font);
return font;
}
/* Don't want Windows to return a Lucida Sans font from
* C:\Windows\Fonts
*/
if (deferredFontFiles.size() > 0) {
font = findJREDeferredFont(lowerCaseName, style);
if (font != null) {
fontNameCache.put(mapName, font);
return font;
}
}
font = findFontFromPlatform(lowerCaseName, style);
if (font != null) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Found font via platform API for request:\"" +
name + "\":, style="+style+
" found font: " + font);
}
fontNameCache.put(mapName, font);
return font;
}
}
/* If reach here and no match has been located, then if there are
* uninitialised deferred fonts, load as many of those as needed
* to find the deferred font. If none is found through that
* search continue on.
* There is possibly a minor issue when more than one
* deferred font implements the same font face. Since deferred
* fonts are only those in font configuration files, this is a
* controlled situation, the known case being Solaris euro_fonts
* versions of Arial, Times New Roman, Courier New. However
* the larger font will transparently replace the smaller one
* - see addToFontList() - when it is needed by the composite font.
*/
if (deferredFontFiles.size() > 0) {
font = findDeferredFont(name, style);
if (font != null) {
fontNameCache.put(mapName, font);
return font;
}
}
/* Some apps use deprecated 1.0 names such as helvetica and courier. On
* Solaris these are Type1 fonts in /usr/openwin/lib/X11/fonts/Type1.
* If running on Solaris will register all the fonts in this
* directory.
* May as well register the whole directory without actually testing
* the font name is one of the deprecated names as the next step would
* load all fonts which are in this directory anyway.
* In the event that this lookup is successful it potentially "hides"
* TrueType versions of such fonts that are elsewhere but since they
* do not exist on Solaris this is not a problem.
* Set a flag to indicate we've done this registration to avoid
* repetition and more seriously, to avoid recursion.
*/
if (FontUtilities.isSolaris &&!loaded1dot0Fonts) {
/* "timesroman" is a special case since that's not the
* name of any known font on Solaris or elsewhere.
*/
if (lowerCaseName.equals("timesroman")) {
font = findFont2D("serif", style, fallback);
fontNameCache.put(mapName, font);
}
register1dot0Fonts();
loaded1dot0Fonts = true;
Font2D ff = findFont2D(name, style, fallback);
return ff;
}
/* We check for application registered fonts before
* explicitly loading all fonts as if necessary the registration
* code will have done so anyway. And we don't want to needlessly
* load the actual files for all fonts.
* Just as for installed fonts we check for family before fullname.
* We do not add these fonts to fontNameCache for the
* app context case which eliminates the overhead of a per context
* cache for these.
*/
if (fontsAreRegistered || fontsAreRegisteredPerAppContext) {
Hashtable<String, FontFamily> familyTable = null;
Hashtable<String, Font2D> nameTable;
if (fontsAreRegistered) {
familyTable = createdByFamilyName;
nameTable = createdByFullName;
} else {
AppContext appContext = AppContext.getAppContext();
@SuppressWarnings("unchecked")
Hashtable<String,FontFamily> tmp1 =
(Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
familyTable = tmp1;
@SuppressWarnings("unchecked")
Hashtable<String, Font2D> tmp2 =
(Hashtable<String,Font2D>)appContext.get(regFullNameKey);
nameTable = tmp2;
}
family = familyTable.get(lowerCaseName);
if (family != null) {
font = family.getFontWithExactStyleMatch(style);
if (font == null) {
font = family.getFont(style);
}
if (font == null) {
font = family.getClosestStyle(style);
}
if (font != null) {
if (fontsAreRegistered) {
fontNameCache.put(mapName, font);
}
return font;
}
}
font = nameTable.get(lowerCaseName);
if (font != null) {
if (fontsAreRegistered) {
fontNameCache.put(mapName, font);
}
return font;
}
}
/* If reach here and no match has been located, then if all fonts
* are not yet loaded, do so, and then recurse.
*/
if (!loadedAllFonts) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Load fonts looking for:" + name);
}
loadFonts();
loadedAllFonts = true;
return findFont2D(name, style, fallback);
}
if (!loadedAllFontFiles) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Load font files looking for:" + name);
}
loadFontFiles();
loadedAllFontFiles = true;
return findFont2D(name, style, fallback);
}
/* The primary name is the locale default - ie not US/English but
* whatever is the default in this locale. This is the way it always
* has been but may be surprising to some developers if "Arial Regular"
* were hard-coded in their app and yet "Arial Regular" was not the
* default name. Fortunately for them, as a consequence of the JDK
* supporting returning names and family names for arbitrary locales,
* we also need to support searching all localised names for a match.
* But because this case of the name used to reference a font is not
* the same as the default for this locale is rare, it makes sense to
* search a much shorter list of default locale names and only go to
* a longer list of names in the event that no match was found.
* So add here code which searches localised names too.
* As in 1.4.x this happens only after loading all fonts, which
* is probably the right order.
*/
if ((font = findFont2DAllLocales(name, style)) != null) {
fontNameCache.put(mapName, font);
return font;
}
/* Perhaps its a "compatibility" name - timesroman, helvetica,
* or courier, which 1.0 apps used for logical fonts.
* We look for these "late" after a loadFonts as we must not
* hide real fonts of these names.
* Map these appropriately:
* On windows this means according to the rules specified by the
* FontConfiguration : do it only for encoding==Cp1252
*
* REMIND: this is something we plan to remove.
*/
if (FontUtilities.isWindows) {
String compatName =
getFontConfiguration().getFallbackFamilyName(name, null);
if (compatName != null) {
font = findFont2D(compatName, style, fallback);
fontNameCache.put(mapName, font);
return font;
}
} else if (lowerCaseName.equals("timesroman")) {
font = findFont2D("serif", style, fallback);
fontNameCache.put(mapName, font);
return font;
} else if (lowerCaseName.equals("helvetica")) {
font = findFont2D("sansserif", style, fallback);
fontNameCache.put(mapName, font);
return font;
} else if (lowerCaseName.equals("courier")) {
font = findFont2D("monospaced", style, fallback);
fontNameCache.put(mapName, font);
return font;
}
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().info("No font found for:" + name);
}
switch (fallback) {
case PHYSICAL_FALLBACK: return getDefaultPhysicalFont();
case LOGICAL_FALLBACK: return getDefaultLogicalFont(style);
default: return null;
}
}
/*
* Workaround for apps which are dependent on a font metrics bug
* in JDK 1.1. This is an unsupported win32 private setting.
* Left in for a customer - do not remove.
*/
public boolean usePlatformFontMetrics() {
return usePlatformFontMetrics;
}
public int getNumFonts() {
return physicalFonts.size()+maxCompFont;
}
private static boolean fontSupportsEncoding(Font font, String encoding) {
return FontUtilities.getFont2D(font).supportsEncoding(encoding);
}
protected abstract String getFontPath(boolean noType1Fonts);
// MACOSX begin -- need to access this in subclass
protected Thread fileCloser = null;
// MACOSX end
Vector<File> tmpFontFiles = null;
public Font2D createFont2D(File fontFile, int fontFormat,
boolean isCopy, CreatedFontTracker tracker)
throws FontFormatException {
String fontFilePath = fontFile.getPath();
FileFont font2D = null;
final File fFile = fontFile;
final CreatedFontTracker _tracker = tracker;
try {
switch (fontFormat) {
case Font.TRUETYPE_FONT:
font2D = new TrueTypeFont(fontFilePath, null, 0, true);
break;
case Font.TYPE1_FONT:
font2D = new Type1Font(fontFilePath, null, isCopy);
break;
default:
throw new FontFormatException("Unrecognised Font Format");
}
} catch (FontFormatException e) {
if (isCopy) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
if (_tracker != null) {
_tracker.subBytes((int)fFile.length());
}
fFile.delete();
return null;
}
});
}
throw(e);
}
if (isCopy) {
font2D.setFileToRemove(fontFile, tracker);
synchronized (FontManager.class) {
if (tmpFontFiles == null) {
tmpFontFiles = new Vector<File>();
}
tmpFontFiles.add(fontFile);
if (fileCloser == null) {
final Runnable fileCloserRunnable = new Runnable() {
public void run() {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
for (int i=0;i<CHANNELPOOLSIZE;i++) {
if (fontFileCache[i] != null) {
try {
fontFileCache[i].close();
} catch (Exception e) {
}
}
}
if (tmpFontFiles != null) {
File[] files = new File[tmpFontFiles.size()];
files = tmpFontFiles.toArray(files);
for (int f=0; f<files.length;f++) {
try {
files[f].delete();
} catch (Exception e) {
}
}
}
return null;
}
});
}
};
AccessController.doPrivileged(
(PrivilegedAction<Void>) () -> {
/* 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 rootTG = ThreadGroupUtils.getRootThreadGroup();
fileCloser = new Thread(rootTG, fileCloserRunnable);
fileCloser.setContextClassLoader(null);
Runtime.getRuntime().addShutdownHook(fileCloser);
return null;
});
}
}
}
return font2D;
}
/* remind: used in X11GraphicsEnvironment and called often enough
* that we ought to obsolete this code
*/
public synchronized String getFullNameByFileName(String fileName) {
PhysicalFont[] physFonts = getPhysicalFonts();
for (int i=0;i<physFonts.length;i++) {
if (physFonts[i].platName.equals(fileName)) {
return (physFonts[i].getFontName(null));
}
}
return null;
}
/*
* This is called when font is determined to be invalid/bad.
* It designed to be called (for example) by the font scaler
* when in processing a font file it is discovered to be incorrect.
* This is different than the case where fonts are discovered to
* be incorrect during initial verification, as such fonts are
* never registered.
* Handles to this font held are re-directed to a default font.
* This default may not be an ideal substitute buts it better than
* crashing This code assumes a PhysicalFont parameter as it doesn't
* make sense for a Composite to be "bad".
*/
public synchronized void deRegisterBadFont(Font2D font2D) {
if (!(font2D instanceof PhysicalFont)) {
/* We should never reach here, but just in case */
return;
} else {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.severe("Deregister bad font: " + font2D);
}
replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont());
}
}
/*
* This encapsulates all the work that needs to be done when a
* Font2D is replaced by a different Font2D.
*/
public synchronized void replaceFont(PhysicalFont oldFont,
PhysicalFont newFont) {
if (oldFont.handle.font2D != oldFont) {
/* already done */
return;
}
/* If we try to replace the font with itself, that won't work,
* so pick any alternative physical font
*/
if (oldFont == newFont) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.severe("Can't replace bad font with itself " + oldFont);
}
PhysicalFont[] physFonts = getPhysicalFonts();
for (int i=0; i<physFonts.length;i++) {
if (physFonts[i] != newFont) {
newFont = physFonts[i];
break;
}
}
if (oldFont == newFont) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.severe("This is bad. No good physicalFonts found.");
}
return;
}
}
/* eliminate references to this font, so it won't be located
* by future callers, and will be eligible for GC when all
* references are removed
*/
oldFont.handle.font2D = newFont;
physicalFonts.remove(oldFont.fullName);
fullNameToFont.remove(oldFont.fullName.toLowerCase(Locale.ENGLISH));
FontFamily.remove(oldFont);
if (localeFullNamesToFont != null) {
Map.Entry<?, ?>[] mapEntries = localeFullNamesToFont.entrySet().
toArray(new Map.Entry<?, ?>[0]);
/* Should I be replacing these, or just I just remove
* the names from the map?
*/
for (int i=0; i<mapEntries.length;i++) {
if (mapEntries[i].getValue() == oldFont) {
try {
@SuppressWarnings("unchecked")
Map.Entry<String, PhysicalFont> tmp = (Map.Entry<String, PhysicalFont>)mapEntries[i];
tmp.setValue(newFont);
} catch (Exception e) {
/* some maps don't support this operation.
* In this case just give up and remove the entry.
*/
localeFullNamesToFont.remove(mapEntries[i].getKey());
}
}
}
}
for (int i=0; i<maxCompFont; i++) {
/* Deferred initialization of composites shouldn't be
* a problem for this case, since a font must have been
* initialised to be discovered to be bad.
* Some JRE composites on Solaris use two versions of the same
* font. The replaced font isn't bad, just "smaller" so there's
* no need to make the slot point to the new font.
* Since composites have a direct reference to the Font2D (not
* via a handle) making this substitution is not safe and could
* cause an additional problem and so this substitution is
* warranted only when a font is truly "bad" and could cause
* a crash. So we now replace it only if its being substituted
* with some font other than a fontconfig rank font
* Since in practice a substitution will have the same rank
* this may never happen, but the code is safer even if its
* also now a no-op.
* The only obvious "glitch" from this stems from the current
* implementation that when asked for the number of glyphs in a
* composite it lies and returns the number in slot 0 because
* composite glyphs aren't contiguous. Since we live with that
* we can live with the glitch that depending on how it was
* initialised a composite may return different values for this.
* Fixing the issues with composite glyph ids is tricky as
* there are exclusion ranges and unlike other fonts even the
* true "numGlyphs" isn't a contiguous range. Likely the only
* solution is an API that returns an array of glyph ranges
* which takes precedence over the existing API. That might
* also need to address excluding ranges which represent a
* code point supported by an earlier component.
*/
if (newFont.getRank() > Font2D.FONT_CONFIG_RANK) {
compFonts[i].replaceComponentFont(oldFont, newFont);
}
}
}
private synchronized void loadLocaleNames() {
if (localeFullNamesToFont != null) {
return;
}
localeFullNamesToFont = new HashMap<String, TrueTypeFont>();
Font2D[] fonts = getRegisteredFonts();
for (int i=0; i<fonts.length; i++) {
if (fonts[i] instanceof TrueTypeFont) {
TrueTypeFont ttf = (TrueTypeFont)fonts[i];
String[] fullNames = ttf.getAllFullNames();
for (int n=0; n<fullNames.length; n++) {
localeFullNamesToFont.put(fullNames[n], ttf);
}
FontFamily family = FontFamily.getFamily(ttf.familyName);
if (family != null) {
FontFamily.addLocaleNames(family, ttf.getAllFamilyNames());
}
}
}
}
/* This replicate the core logic of findFont2D but operates on
* all the locale names. This hasn't been merged into findFont2D to
* keep the logic simpler and reduce overhead, since this case is
* almost never used. The main case in which it is called is when
* a bogus font name is used and we need to check all possible names
* before returning the default case.
*/
private Font2D findFont2DAllLocales(String name, int style) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Searching localised font names for:" + name);
}
/* If reach here and no match has been located, then if we have
* not yet built the map of localeFullNamesToFont for TT fonts, do so
* now. This method must be called after all fonts have been loaded.
*/
if (localeFullNamesToFont == null) {
loadLocaleNames();
}
String lowerCaseName = name.toLowerCase();
Font2D font = null;
/* First see if its a family name. */
FontFamily family = FontFamily.getLocaleFamily(lowerCaseName);
if (family != null) {
font = family.getFont(style);
if (font == null) {
font = family.getClosestStyle(style);
}
if (font != null) {
return font;
}
}
/* If it wasn't a family name, it should be a full name. */
synchronized (this) {
font = localeFullNamesToFont.get(name);
}
if (font != null) {
if (font.style == style || style == Font.PLAIN) {
return font;
} else {
family = FontFamily.getFamily(font.getFamilyName(null));
if (family != null) {
Font2D familyFont = family.getFont(style);
/* We exactly matched the requested style, use it! */
if (familyFont != null) {
return familyFont;
} else {
familyFont = family.getClosestStyle(style);
if (familyFont != null) {
/* The next check is perhaps one
* that shouldn't be done. ie if we get this
* far we have probably as close a match as we
* are going to get. We could load all fonts to
* see if somehow some parts of the family are
* loaded but not all of it.
* This check is commented out for now.
*/
if (!familyFont.canDoStyle(style)) {
familyFont = null;
}
return familyFont;
}
}
}
}
}
return font;
}
/* Supporting "alternate" composite fonts on 2D graphics objects
* is accessed by the application by calling methods on the local
* GraphicsEnvironment. The overall implementation is described
* in one place, here, since otherwise the implementation is spread
* around it may be difficult to track.
* The methods below call into SunGraphicsEnvironment which creates a
* new FontConfiguration instance. The FontConfiguration class,
* and its platform sub-classes are updated to take parameters requesting
* these behaviours. This is then used to create new composite font
* instances. Since this calls the initCompositeFont method in
* SunGraphicsEnvironment it performs the same initialization as is
* performed normally. There may be some duplication of effort, but
* that code is already written to be able to perform properly if called
* to duplicate work. The main difference is that if we detect we are
* running in an applet/browser/Java plugin environment these new fonts
* are not placed in the "default" maps but into an AppContext instance.
* The font lookup mechanism in java.awt.Font.getFont2D() is also updated
* so that look-up for composite fonts will in that case always
* do a lookup rather than returning a cached result.
* This is inefficient but necessary else singleton java.awt.Font
* instances would not retrieve the correct Font2D for the appcontext.
* sun.font.FontManager.findFont2D is also updated to that it uses
* a name map cache specific to that appcontext.
*
* Getting an AppContext is expensive, so there is a global variable
* that records whether these methods have ever been called and can
* avoid the expense for almost all applications. Once the correct
* CompositeFont is associated with the Font, everything should work
* through existing mechanisms.
* A special case is that GraphicsEnvironment.getAllFonts() must
* return an AppContext specific list.
*
* Calling the methods below is "heavyweight" but it is expected that
* these methods will be called very rarely.
*
* If _usingPerAppContextComposites is true, we are in "applet"
* (eg browser) environment and at least one context has selected
* an alternate composite font behaviour.
* If _usingAlternateComposites is true, we are not in an "applet"
* environment and the (single) application has selected
* an alternate composite font behaviour.
*
* - Printing: The implementation delegates logical fonts to an AWT
* mechanism which cannot use these alternate configurations.
* We can detect that alternate fonts are in use and back-off to 2D, but
* that uses outlines. Much of this can be fixed with additional work
* but that may have to wait. The results should be correct, just not
* optimal.
*/
private static final Object altJAFontKey = new Object();
private static final Object localeFontKey = new Object();
private static final Object proportionalFontKey = new Object();
private boolean _usingPerAppContextComposites = false;
private boolean _usingAlternateComposites = false;
/* These values are used only if we are running as a standalone
* application, as determined by maybeMultiAppContext();
*/
private static boolean gAltJAFont = false;
private boolean gLocalePref = false;
private boolean gPropPref = false;
/* This method doesn't check if alternates are selected in this app
* context. Its used by the FontMetrics caching code which in such
* a case cannot retrieve a cached metrics solely on the basis of
* the Font.equals() method since it needs to also check if the Font2D
* is the same.
* We also use non-standard composites for Swing native L&F fonts on
* Windows. In that case the policy is that the metrics reported are
* based solely on the physical font in the first slot which is the
* visible java.awt.Font. So in that case the metrics cache which tests
* the Font does what we want. In the near future when we expand the GTK
* logical font definitions we may need to revisit this if GTK reports
* combined metrics instead. For now though this test can be simple.
*/
public boolean maybeUsingAlternateCompositeFonts() {
return _usingAlternateComposites || _usingPerAppContextComposites;
}
public boolean usingAlternateCompositeFonts() {
return (_usingAlternateComposites ||
(_usingPerAppContextComposites &&
AppContext.getAppContext().get(CompositeFont.class) != null));
}
private static boolean maybeMultiAppContext() {
Boolean appletSM = (Boolean)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
SecurityManager sm = System.getSecurityManager();
return sm instanceof sun.applet.AppletSecurity;
}
});
return appletSM.booleanValue();
}
/* Modifies the behaviour of a subsequent call to preferLocaleFonts()
* to use Mincho instead of Gothic for dialoginput in JA locales
* on windows. Not needed on other platforms.
*/
public synchronized void useAlternateFontforJALocales() {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Entered useAlternateFontforJALocales().");
}
if (!FontUtilities.isWindows) {
return;
}
if (!maybeMultiAppContext()) {
gAltJAFont = true;
} else {
AppContext appContext = AppContext.getAppContext();
appContext.put(altJAFontKey, altJAFontKey);
}
}
public boolean usingAlternateFontforJALocales() {
if (!maybeMultiAppContext()) {
return gAltJAFont;
} else {
AppContext appContext = AppContext.getAppContext();
return appContext.get(altJAFontKey) == altJAFontKey;
}
}
public synchronized void preferLocaleFonts() {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().info("Entered preferLocaleFonts().");
}
/* Test if re-ordering will have any effect */
if (!FontConfiguration.willReorderForStartupLocale()) {
return;
}
if (!maybeMultiAppContext()) {
if (gLocalePref == true) {
return;
}
gLocalePref = true;
createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
_usingAlternateComposites = true;
} else {
AppContext appContext = AppContext.getAppContext();
if (appContext.get(localeFontKey) == localeFontKey) {
return;
}
appContext.put(localeFontKey, localeFontKey);
boolean acPropPref =
appContext.get(proportionalFontKey) == proportionalFontKey;
ConcurrentHashMap<String, Font2D>
altNameCache = new ConcurrentHashMap<String, Font2D> ();
/* If there is an existing hashtable, we can drop it. */
appContext.put(CompositeFont.class, altNameCache);
_usingPerAppContextComposites = true;
createCompositeFonts(altNameCache, true, acPropPref);
}
}
public synchronized void preferProportionalFonts() {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Entered preferProportionalFonts().");
}
/* If no proportional fonts are configured, there's no need
* to take any action.
*/
if (!FontConfiguration.hasMonoToPropMap()) {
return;
}
if (!maybeMultiAppContext()) {
if (gPropPref == true) {
return;
}
gPropPref = true;
createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
_usingAlternateComposites = true;
} else {
AppContext appContext = AppContext.getAppContext();
if (appContext.get(proportionalFontKey) == proportionalFontKey) {
return;
}
appContext.put(proportionalFontKey, proportionalFontKey);
boolean acLocalePref =
appContext.get(localeFontKey) == localeFontKey;
ConcurrentHashMap<String, Font2D>
altNameCache = new ConcurrentHashMap<String, Font2D> ();
/* If there is an existing hashtable, we can drop it. */
appContext.put(CompositeFont.class, altNameCache);
_usingPerAppContextComposites = true;
createCompositeFonts(altNameCache, acLocalePref, true);
}
}
private static HashSet<String> installedNames = null;
private static HashSet<String> getInstalledNames() {
if (installedNames == null) {
Locale l = getSystemStartupLocale();
SunFontManager fontManager = SunFontManager.getInstance();
String[] installedFamilies =
fontManager.getInstalledFontFamilyNames(l);
Font[] installedFonts = fontManager.getAllInstalledFonts();
HashSet<String> names = new HashSet<String>();
for (int i=0; i<installedFamilies.length; i++) {
names.add(installedFamilies[i].toLowerCase(l));
}
for (int i=0; i<installedFonts.length; i++) {
names.add(installedFonts[i].getFontName(l).toLowerCase(l));
}
installedNames = names;
}
return installedNames;
}
/* Keys are used to lookup per-AppContext Hashtables */
private static final Object regFamilyKey = new Object();
private static final Object regFullNameKey = new Object();
private Hashtable<String,FontFamily> createdByFamilyName;
private Hashtable<String,Font2D> createdByFullName;
private boolean fontsAreRegistered = false;
private boolean fontsAreRegisteredPerAppContext = false;
public boolean registerFont(Font font) {
/* This method should not be called with "null".
* It is the caller's responsibility to ensure that.
*/
if (font == null) {
return false;
}
/* Initialise these objects only once we start to use this API */
synchronized (regFamilyKey) {
if (createdByFamilyName == null) {
createdByFamilyName = new Hashtable<String,FontFamily>();
createdByFullName = new Hashtable<String,Font2D>();
}
}
if (! FontAccess.getFontAccess().isCreatedFont(font)) {
return false;
}
/* We want to ensure that this font cannot override existing
* installed fonts. Check these conditions :
* - family name is not that of an installed font
* - full name is not that of an installed font
* - family name is not the same as the full name of an installed font
* - full name is not the same as the family name of an installed font
* The last two of these may initially look odd but the reason is
* that (unfortunately) Font constructors do not distinuguish these.
* An extreme example of such a problem would be a font which has
* family name "Dialog.Plain" and full name of "Dialog".
* The one arguably overly stringent restriction here is that if an
* application wants to supply a new member of an existing family
* It will get rejected. But since the JRE can perform synthetic
* styling in many cases its not necessary.
* We don't apply the same logic to registered fonts. If apps want
* to do this lets assume they have a reason. It won't cause problems
* except for themselves.
*/
HashSet<String> names = getInstalledNames();
Locale l = getSystemStartupLocale();
String familyName = font.getFamily(l).toLowerCase();
String fullName = font.getFontName(l).toLowerCase();
if (names.contains(familyName) || names.contains(fullName)) {
return false;
}
/* Checks passed, now register the font */
Hashtable<String,FontFamily> familyTable;
Hashtable<String,Font2D> fullNameTable;
if (!maybeMultiAppContext()) {
familyTable = createdByFamilyName;
fullNameTable = createdByFullName;
fontsAreRegistered = true;
} else {
AppContext appContext = AppContext.getAppContext();
@SuppressWarnings("unchecked")
Hashtable<String,FontFamily> tmp1 =
(Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
familyTable = tmp1;
@SuppressWarnings("unchecked")
Hashtable<String,Font2D> tmp2 =
(Hashtable<String,Font2D>)appContext.get(regFullNameKey);
fullNameTable = tmp2;
if (familyTable == null) {
familyTable = new Hashtable<String,FontFamily>();
fullNameTable = new Hashtable<String,Font2D>();
appContext.put(regFamilyKey, familyTable);
appContext.put(regFullNameKey, fullNameTable);
}
fontsAreRegisteredPerAppContext = true;
}
/* Create the FontFamily and add font to the tables */
Font2D font2D = FontUtilities.getFont2D(font);
int style = font2D.getStyle();
FontFamily family = familyTable.get(familyName);
if (family == null) {
family = new FontFamily(font.getFamily(l));
familyTable.put(familyName, family);
}
/* Remove name cache entries if not using app contexts.
* To accommodate a case where code may have registered first a plain
* family member and then used it and is now registering a bold family
* member, we need to remove all members of the family, so that the
* new style can get picked up rather than continuing to synthesise.
*/
if (fontsAreRegistered) {
removeFromCache(family.getFont(Font.PLAIN));
removeFromCache(family.getFont(Font.BOLD));
removeFromCache(family.getFont(Font.ITALIC));
removeFromCache(family.getFont(Font.BOLD|Font.ITALIC));
removeFromCache(fullNameTable.get(fullName));
}
family.setFont(font2D, style);
fullNameTable.put(fullName, font2D);
return true;
}
/* Remove from the name cache all references to the Font2D */
private void removeFromCache(Font2D font) {
if (font == null) {
return;
}
String[] keys = fontNameCache.keySet().toArray(STR_ARRAY);
for (int k=0; k<keys.length;k++) {
if (fontNameCache.get(keys[k]) == font) {
fontNameCache.remove(keys[k]);
}
}
}
// It may look odd to use TreeMap but its more convenient to the caller.
public TreeMap<String, String> getCreatedFontFamilyNames() {
Hashtable<String,FontFamily> familyTable;
if (fontsAreRegistered) {
familyTable = createdByFamilyName;
} else if (fontsAreRegisteredPerAppContext) {
AppContext appContext = AppContext.getAppContext();
@SuppressWarnings("unchecked")
Hashtable<String,FontFamily> tmp =
(Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
familyTable = tmp;
} else {
return null;
}
Locale l = getSystemStartupLocale();
synchronized (familyTable) {
TreeMap<String, String> map = new TreeMap<String, String>();
for (FontFamily f : familyTable.values()) {
Font2D font2D = f.getFont(Font.PLAIN);
if (font2D == null) {
font2D = f.getClosestStyle(Font.PLAIN);
}
String name = font2D.getFamilyName(l);
map.put(name.toLowerCase(l), name);
}
return map;
}
}
public Font[] getCreatedFonts() {
Hashtable<String,Font2D> nameTable;
if (fontsAreRegistered) {
nameTable = createdByFullName;
} else if (fontsAreRegisteredPerAppContext) {
AppContext appContext = AppContext.getAppContext();
@SuppressWarnings("unchecked")
Hashtable<String,Font2D> tmp =
(Hashtable<String,Font2D>)appContext.get(regFullNameKey);
nameTable = tmp;
} else {
return null;
}
Locale l = getSystemStartupLocale();
synchronized (nameTable) {
Font[] fonts = new Font[nameTable.size()];
int i=0;
for (Font2D font2D : nameTable.values()) {
fonts[i++] = new Font(font2D.getFontName(l), Font.PLAIN, 1);
}
return fonts;
}
}
protected String[] getPlatformFontDirs(boolean noType1Fonts) {
/* First check if we already initialised path dirs */
if (pathDirs != null) {
return pathDirs;
}
String path = getPlatformFontPath(noType1Fonts);
StringTokenizer parser =
new StringTokenizer(path, File.pathSeparator);
ArrayList<String> pathList = new ArrayList<String>();
try {
while (parser.hasMoreTokens()) {
pathList.add(parser.nextToken());
}
} catch (NoSuchElementException e) {
}
pathDirs = pathList.toArray(new String[0]);
return pathDirs;
}
/**
* Returns an array of two strings. The first element is the
* name of the font. The second element is the file name.
*/
protected abstract String[] getDefaultPlatformFont();
// Begin: Refactored from SunGraphicsEnviroment.
/*
* helper function for registerFonts
*/
private void addDirFonts(String dirName, File dirFile,
FilenameFilter filter,
int fontFormat, boolean useJavaRasterizer,
int fontRank,
boolean defer, boolean resolveSymLinks) {
String[] ls = dirFile.list(filter);
if (ls == null || ls.length == 0) {
return;
}
String[] fontNames = new String[ls.length];
String[][] nativeNames = new String[ls.length][];
int fontCount = 0;
for (int i=0; i < ls.length; i++ ) {
File theFile = new File(dirFile, ls[i]);
String fullName = null;
if (resolveSymLinks) {
try {
fullName = theFile.getCanonicalPath();
} catch (IOException e) {
}
}
if (fullName == null) {
fullName = dirName + File.separator + ls[i];
}
// REMIND: case compare depends on platform
if (registeredFontFiles.contains(fullName)) {
continue;
}
if (badFonts != null && badFonts.contains(fullName)) {
if (FontUtilities.debugFonts()) {
FontUtilities.getLogger()
.warning("skip bad font " + fullName);
}
continue; // skip this font file.
}
registeredFontFiles.add(fullName);
if (FontUtilities.debugFonts()
&& FontUtilities.getLogger().isLoggable(PlatformLogger.Level.INFO)) {
String message = "Registering font " + fullName;
String[] natNames = getNativeNames(fullName, null);
if (natNames == null) {
message += " with no native name";
} else {
message += " with native name(s) " + natNames[0];
for (int nn = 1; nn < natNames.length; nn++) {
message += ", " + natNames[nn];
}
}
FontUtilities.getLogger().info(message);
}
fontNames[fontCount] = fullName;
nativeNames[fontCount++] = getNativeNames(fullName, null);
}
registerFonts(fontNames, nativeNames, fontCount, fontFormat,
useJavaRasterizer, fontRank, defer);
return;
}
protected String[] getNativeNames(String fontFileName,
String platformName) {
return null;
}
/**
* Returns a file name for the physical font represented by this platform
* font name. The default implementation tries to obtain the file name
* from the font configuration.
* Subclasses may override to provide information from other sources.
*/
protected String getFileNameFromPlatformName(String platformFontName) {
return fontConfig.getFileNameFromPlatformName(platformFontName);
}
/**
* Return the default font configuration.
*/
public FontConfiguration getFontConfiguration() {
return fontConfig;
}
/* A call to this method should be followed by a call to
* registerFontDirs(..)
*/
public String getPlatformFontPath(boolean noType1Font) {
if (fontPath == null) {
fontPath = getFontPath(noType1Font);
}
return fontPath;
}
public static boolean isOpenJDK() {
return FontUtilities.isOpenJDK;
}
protected void loadFonts() {
if (discoveredAllFonts) {
return;
}
/* Use lock specific to the font system */
synchronized (this) {
if (FontUtilities.debugFonts()) {
Thread.dumpStack();
FontUtilities.getLogger()
.info("SunGraphicsEnvironment.loadFonts() called");
}
initialiseDeferredFonts();
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
if (fontPath == null) {
fontPath = getPlatformFontPath(noType1Font);
registerFontDirs(fontPath);
}
if (fontPath != null) {
// this will find all fonts including those already
// registered. But we have checks in place to prevent
// double registration.
if (! gotFontsFromPlatform()) {
registerFontsOnPath(fontPath, false,
Font2D.UNKNOWN_RANK,
false, true);
loadedAllFontFiles = true;
}
}
registerOtherFontFiles(registeredFontFiles);
discoveredAllFonts = true;
return null;
}
});
}
}
protected void registerFontDirs(String pathName) {
return;
}
private void registerFontsOnPath(String pathName,
boolean useJavaRasterizer, int fontRank,
boolean defer, boolean resolveSymLinks) {
StringTokenizer parser = new StringTokenizer(pathName,
File.pathSeparator);
try {
while (parser.hasMoreTokens()) {
registerFontsInDir(parser.nextToken(),
useJavaRasterizer, fontRank,
defer, resolveSymLinks);
}
} catch (NoSuchElementException e) {
}
}
/* Called to register fall back fonts */
public void registerFontsInDir(String dirName) {
registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false);
}
// MACOSX begin -- need to access this in subclass
protected void registerFontsInDir(String dirName, boolean useJavaRasterizer,
// MACOSX end
int fontRank,
boolean defer, boolean resolveSymLinks) {
File pathFile = new File(dirName);
addDirFonts(dirName, pathFile, ttFilter,
FONTFORMAT_TRUETYPE, useJavaRasterizer,
fontRank==Font2D.UNKNOWN_RANK ?
Font2D.TTF_RANK : fontRank,
defer, resolveSymLinks);
addDirFonts(dirName, pathFile, t1Filter,
FONTFORMAT_TYPE1, useJavaRasterizer,
fontRank==Font2D.UNKNOWN_RANK ?
Font2D.TYPE1_RANK : fontRank,
defer, resolveSymLinks);
}
protected void registerFontDir(String path) {
}
/**
* Returns file name for default font, either absolute
* or relative as needed by registerFontFile.
*/
public synchronized String getDefaultFontFile() {
if (defaultFontFileName == null) {
initDefaultFonts();
}
return defaultFontFileName;
}
private void initDefaultFonts() {
if (!isOpenJDK()) {
defaultFontName = lucidaFontName;
if (useAbsoluteFontFileNames()) {
defaultFontFileName =
jreFontDirName + File.separator + FontUtilities.LUCIDA_FILE_NAME;
} else {
defaultFontFileName = FontUtilities.LUCIDA_FILE_NAME;
}
}
}
/**
* Whether registerFontFile expects absolute or relative
* font file names.
*/
protected boolean useAbsoluteFontFileNames() {
return true;
}
/**
* Creates this environment's FontConfiguration.
*/
protected abstract FontConfiguration createFontConfiguration();
public abstract FontConfiguration
createFontConfiguration(boolean preferLocaleFonts,
boolean preferPropFonts);
/**
* Returns face name for default font, or null if
* no face names are used for CompositeFontDescriptors
* for this platform.
*/
public synchronized String getDefaultFontFaceName() {
if (defaultFontName == null) {
initDefaultFonts();
}
return defaultFontName;
}
public void loadFontFiles() {
loadFonts();
if (loadedAllFontFiles) {
return;
}
/* Use lock specific to the font system */
synchronized (this) {
if (FontUtilities.debugFonts()) {
Thread.dumpStack();
FontUtilities.getLogger().info("loadAllFontFiles() called");
}
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
if (fontPath == null) {
fontPath = getPlatformFontPath(noType1Font);
}
if (fontPath != null) {
// this will find all fonts including those already
// registered. But we have checks in place to prevent
// double registration.
registerFontsOnPath(fontPath, false,
Font2D.UNKNOWN_RANK,
false, true);
}
loadedAllFontFiles = true;
return null;
}
});
}
}
/*
* This method asks the font configuration API for all platform names
* used as components of composite/logical fonts and iterates over these
* looking up their corresponding file name and registers these fonts.
* It also ensures that the fonts are accessible via platform APIs.
* The composites themselves are then registered.
*/
private void
initCompositeFonts(FontConfiguration fontConfig,
ConcurrentHashMap<String, Font2D> altNameCache) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.info("Initialising composite fonts");
}
int numCoreFonts = fontConfig.getNumberCoreFonts();
String[] fcFonts = fontConfig.getPlatformFontNames();
for (int f=0; f<fcFonts.length; f++) {
String platformFontName = fcFonts[f];
String fontFileName =
getFileNameFromPlatformName(platformFontName);
String[] nativeNames = null;
if (fontFileName == null
|| fontFileName.equals(platformFontName)) {
/* No file located, so register using the platform name,
* i.e. as a native font.
*/
fontFileName = platformFontName;
} else {
if (f < numCoreFonts) {
/* If platform APIs also need to access the font, add it
* to a set to be registered with the platform too.
* This may be used to add the parent directory to the X11
* font path if its not already there. See the docs for the
* subclass implementation.
* This is now mainly for the benefit of X11-based AWT
* But for historical reasons, 2D initialisation code
* makes these calls.
* If the fontconfiguration file is properly set up
* so that all fonts are mapped to files and all their
* appropriate directories are specified, then this
* method will be low cost as it will return after
* a test that finds a null lookup map.
*/
addFontToPlatformFontPath(platformFontName);
}
nativeNames = getNativeNames(fontFileName, platformFontName);
}
/* Uncomment these two lines to "generate" the XLFD->filename
* mappings needed to speed start-up on Solaris.
* Augment this with the appendedpathname and the mappings
* for native (F3) fonts
*/
//String platName = platformFontName.replaceAll(" ", "_");
//System.out.println("filename."+platName+"="+fontFileName);
registerFontFile(fontFileName, nativeNames,
Font2D.FONT_CONFIG_RANK, true);
}
/* This registers accumulated paths from the calls to
* addFontToPlatformFontPath(..) and any specified by
* the font configuration. Rather than registering
* the fonts it puts them in a place and form suitable for
* the Toolkit to pick up and use if a toolkit is initialised,
* and if it uses X11 fonts.
*/
registerPlatformFontsUsedByFontConfiguration();
CompositeFontDescriptor[] compositeFontInfo
= fontConfig.get2DCompositeFontInfo();
for (int i = 0; i < compositeFontInfo.length; i++) {
CompositeFontDescriptor descriptor = compositeFontInfo[i];
String[] componentFileNames = descriptor.getComponentFileNames();
String[] componentFaceNames = descriptor.getComponentFaceNames();
/* It would be better eventually to handle this in the
* FontConfiguration code which should also remove duplicate slots
*/
if (missingFontFiles != null) {
for (int ii=0; ii<componentFileNames.length; ii++) {
if (missingFontFiles.contains(componentFileNames[ii])) {
componentFileNames[ii] = getDefaultFontFile();
componentFaceNames[ii] = getDefaultFontFaceName();
}
}
}
/* FontConfiguration needs to convey how many fonts it has added
* as fallback component fonts which should not affect metrics.
* The core component count will be the number of metrics slots.
* This does not preclude other mechanisms for adding
* fall back component fonts to the composite.
*/
if (altNameCache != null) {
SunFontManager.registerCompositeFont(
descriptor.getFaceName(),
componentFileNames, componentFaceNames,
descriptor.getCoreComponentCount(),
descriptor.getExclusionRanges(),
descriptor.getExclusionRangeLimits(),
true,
altNameCache);
} else {
registerCompositeFont(descriptor.getFaceName(),
componentFileNames, componentFaceNames,
descriptor.getCoreComponentCount(),
descriptor.getExclusionRanges(),
descriptor.getExclusionRangeLimits(),
true);
}
if (FontUtilities.debugFonts()) {
FontUtilities.getLogger()
.info("registered " + descriptor.getFaceName());
}
}
}
/**
* Notifies graphics environment that the logical font configuration
* uses the given platform font name. The graphics environment may
* use this for platform specific initialization.
*/
protected void addFontToPlatformFontPath(String platformFontName) {
}
protected void registerFontFile(String fontFileName, String[] nativeNames,
int fontRank, boolean defer) {
// REMIND: case compare depends on platform
if (registeredFontFiles.contains(fontFileName)) {
return;
}
int fontFormat;
if (ttFilter.accept(null, fontFileName)) {
fontFormat = FONTFORMAT_TRUETYPE;
} else if (t1Filter.accept(null, fontFileName)) {
fontFormat = FONTFORMAT_TYPE1;
} else {
fontFormat = FONTFORMAT_NATIVE;
}
registeredFontFiles.add(fontFileName);
if (defer) {
registerDeferredFont(fontFileName, fontFileName, nativeNames,
fontFormat, false, fontRank);
} else {
registerFontFile(fontFileName, nativeNames, fontFormat, false,
fontRank);
}
}
protected void registerPlatformFontsUsedByFontConfiguration() {
}
/*
* A GE may verify whether a font file used in a fontconfiguration
* exists. If it doesn't then either we may substitute the default
* font, or perhaps elide it altogether from the composite font.
* This makes some sense on windows where the font file is only
* likely to be in one place. But on other OSes, eg Linux, the file
* can move around depending. So there we probably don't want to assume
* its missing and so won't add it to this list.
* If this list - missingFontFiles - is non-null then the composite
* font initialisation logic tests to see if a font file is in that
* set.
* Only one thread should be able to add to this set so we don't
* synchronize.
*/
protected void addToMissingFontFileList(String fileName) {
if (missingFontFiles == null) {
missingFontFiles = new HashSet<String>();
}
missingFontFiles.add(fileName);
}
/*
* This is for use only within getAllFonts().
* Fonts listed in the fontconfig files for windows were all
* on the "deferred" initialisation list. They were registered
* either in the course of the application, or in the call to
* loadFonts() within getAllFonts(). The fontconfig file specifies
* the names of the fonts using the English names. If there's a
* different name in the execution locale, then the platform will
* report that, and we will construct the font with both names, and
* thereby enumerate it twice. This happens for Japanese fonts listed
* in the windows fontconfig, when run in the JA locale. The solution
* is to rely (in this case) on the platform's font->file mapping to
* determine that this name corresponds to a file we already registered.
* This works because
* - we know when we get here all deferred fonts are already initialised
* - when we register a font file, we register all fonts in it.
* - we know the fontconfig fonts are all in the windows registry
*/
private boolean isNameForRegisteredFile(String fontName) {
String fileName = getFileNameForFontName(fontName);
if (fileName == null) {
return false;
}
return registeredFontFiles.contains(fileName);
}
/*
* This invocation is not in a privileged block because
* all privileged operations (reading files and properties)
* was conducted on the creation of the GE
*/
public void
createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache,
boolean preferLocale,
boolean preferProportional) {
FontConfiguration fontConfig =
createFontConfiguration(preferLocale, preferProportional);
initCompositeFonts(fontConfig, altNameCache);
}
/**
* Returns all fonts installed in this environment.
*/
public Font[] getAllInstalledFonts() {
if (allFonts == null) {
loadFonts();
TreeMap<String, Font2D> fontMapNames = new TreeMap<>();
/* warning: the number of composite fonts could change dynamically
* if applications are allowed to create them. "allfonts" could
* then be stale.
*/
Font2D[] allfonts = getRegisteredFonts();
for (int i=0; i < allfonts.length; i++) {
if (!(allfonts[i] instanceof NativeFont)) {
fontMapNames.put(allfonts[i].getFontName(null),
allfonts[i]);
}
}
String[] platformNames = getFontNamesFromPlatform();
if (platformNames != null) {
for (int i=0; i<platformNames.length; i++) {
if (!isNameForRegisteredFile(platformNames[i])) {
fontMapNames.put(platformNames[i], null);
}
}
}
String[] fontNames = null;
if (fontMapNames.size() > 0) {
fontNames = new String[fontMapNames.size()];
Object [] keyNames = fontMapNames.keySet().toArray();
for (int i=0; i < keyNames.length; i++) {
fontNames[i] = (String)keyNames[i];
}
}
Font[] fonts = new Font[fontNames.length];
for (int i=0; i < fontNames.length; i++) {
fonts[i] = new Font(fontNames[i], Font.PLAIN, 1);
Font2D f2d = fontMapNames.get(fontNames[i]);
if (f2d != null) {
FontAccess.getFontAccess().setFont2D(fonts[i], f2d.handle);
}
}
allFonts = fonts;
}
Font []copyFonts = new Font[allFonts.length];
System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length);
return copyFonts;
}
/**
* Get a list of installed fonts in the requested {@link Locale}.
* The list contains the fonts Family Names.
* If Locale is null, the default locale is used.
*
* @param requestedLocale, if null the default locale is used.
* @return list of installed fonts in the system.
*/
public String[] getInstalledFontFamilyNames(Locale requestedLocale) {
if (requestedLocale == null) {
requestedLocale = Locale.getDefault();
}
if (allFamilies != null && lastDefaultLocale != null &&
requestedLocale.equals(lastDefaultLocale)) {
String[] copyFamilies = new String[allFamilies.length];
System.arraycopy(allFamilies, 0, copyFamilies,
0, allFamilies.length);
return copyFamilies;
}
TreeMap<String,String> familyNames = new TreeMap<String,String>();
// these names are always there and aren't localised
String str;
str = Font.SERIF; familyNames.put(str.toLowerCase(), str);
str = Font.SANS_SERIF; familyNames.put(str.toLowerCase(), str);
str = Font.MONOSPACED; familyNames.put(str.toLowerCase(), str);
str = Font.DIALOG; familyNames.put(str.toLowerCase(), str);
str = Font.DIALOG_INPUT; familyNames.put(str.toLowerCase(), str);
/* Platform APIs may be used to get the set of available family
* names for the current default locale so long as it is the same
* as the start-up system locale, rather than loading all fonts.
*/
if (requestedLocale.equals(getSystemStartupLocale()) &&
getFamilyNamesFromPlatform(familyNames, requestedLocale)) {
/* Augment platform names with JRE font family names */
getJREFontFamilyNames(familyNames, requestedLocale);
} else {
loadFontFiles();
Font2D[] physicalfonts = getPhysicalFonts();
for (int i=0; i < physicalfonts.length; i++) {
if (!(physicalfonts[i] instanceof NativeFont)) {
String name =
physicalfonts[i].getFamilyName(requestedLocale);
familyNames.put(name.toLowerCase(requestedLocale), name);
}
}
}
// Add any native font family names here
addNativeFontFamilyNames(familyNames, requestedLocale);
String[] retval = new String[familyNames.size()];
Object [] keyNames = familyNames.keySet().toArray();
for (int i=0; i < keyNames.length; i++) {
retval[i] = familyNames.get(keyNames[i]);
}
if (requestedLocale.equals(Locale.getDefault())) {
lastDefaultLocale = requestedLocale;
allFamilies = new String[retval.length];
System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length);
}
return retval;
}
// Provides an aperture to add native font family names to the map
protected void addNativeFontFamilyNames(TreeMap<String, String> familyNames, Locale requestedLocale) { }
public void register1dot0Fonts() {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
String type1Dir = "/usr/openwin/lib/X11/fonts/Type1";
registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK,
false, false);
return null;
}
});
}
/* Really we need only the JRE fonts family names, but there's little
* overhead in doing this the easy way by adding all the currently
* known fonts.
*/
protected void getJREFontFamilyNames(TreeMap<String,String> familyNames,
Locale requestedLocale) {
registerDeferredJREFonts(jreFontDirName);
Font2D[] physicalfonts = getPhysicalFonts();
for (int i=0; i < physicalfonts.length; i++) {
if (!(physicalfonts[i] instanceof NativeFont)) {
String name =
physicalfonts[i].getFamilyName(requestedLocale);
familyNames.put(name.toLowerCase(requestedLocale), name);
}
}
}
/**
* Default locale can be changed but we need to know the initial locale
* as that is what is used by native code. Changing Java default locale
* doesn't affect that.
* Returns the locale in use when using native code to communicate
* with platform APIs. On windows this is known as the "system" locale,
* and it is usually the same as the platform locale, but not always,
* so this method also checks an implementation property used only
* on windows and uses that if set.
*/
private static Locale systemLocale = null;
private static Locale getSystemStartupLocale() {
if (systemLocale == null) {
systemLocale = (Locale)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
/* On windows the system locale may be different than the
* user locale. This is an unsupported configuration, but
* in that case we want to return a dummy locale that will
* never cause a match in the usage of this API. This is
* important because Windows documents that the family
* names of fonts are enumerated using the language of
* the system locale. BY returning a dummy locale in that
* case we do not use the platform API which would not
* return us the names we want.
*/
String fileEncoding = System.getProperty("file.encoding", "");
String sysEncoding = System.getProperty("sun.jnu.encoding");
if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) {
return Locale.ROOT;
}
String language = System.getProperty("user.language", "en");
String country = System.getProperty("user.country","");
String variant = System.getProperty("user.variant","");
return new Locale(language, country, variant);
}
});
}
return systemLocale;
}
void addToPool(FileFont font) {
FileFont fontFileToClose = null;
int freeSlot = -1;
synchronized (fontFileCache) {
/* Avoid duplicate entries in the pool, and don't close() it,
* since this method is called only from within open().
* Seeing a duplicate is most likely to happen if the thread
* was interrupted during a read, forcing perhaps repeated
* close and open calls and it eventually it ends up pointing
* at the same slot.
*/
for (int i=0;i<CHANNELPOOLSIZE;i++) {
if (fontFileCache[i] == font) {
return;
}
if (fontFileCache[i] == null && freeSlot < 0) {
freeSlot = i;
}
}
if (freeSlot >= 0) {
fontFileCache[freeSlot] = font;
return;
} else {
/* replace with new font. */
fontFileToClose = fontFileCache[lastPoolIndex];
fontFileCache[lastPoolIndex] = font;
/* lastPoolIndex is updated so that the least recently opened
* file will be closed next.
*/
lastPoolIndex = (lastPoolIndex+1) % CHANNELPOOLSIZE;
}
}
/* Need to close the font file outside of the synchronized block,
* since its possible some other thread is in an open() call on
* this font file, and could be holding its lock and the pool lock.
* Releasing the pool lock allows that thread to continue, so it can
* then release the lock on this font, allowing the close() call
* below to proceed.
* Also, calling close() is safe because any other thread using
* the font we are closing() synchronizes all reading, so we
* will not close the file while its in use.
*/
if (fontFileToClose != null) {
fontFileToClose.close();
}
}
protected FontUIResource getFontConfigFUIR(String family, int style,
int size)
{
return new FontUIResource(family, style, size);
}
}