/*
* Copyright 1997-2007 Sun Microsystems, Inc. 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package sun.java2d;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.font.TextAttribute;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.InputStreamReader;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import sun.awt.AppContext;
import sun.awt.DisplayChangedListener;
import sun.awt.FontConfiguration;
import sun.awt.SunDisplayChanger;
import sun.font.CompositeFontDescriptor;
import sun.font.Font2D;
import sun.font.FontManager;
import sun.font.NativeFont;
/**
* This is an implementation of a GraphicsEnvironment object for the
* default local GraphicsEnvironment.
*
* @see GraphicsDevice
* @see GraphicsConfiguration
*/
public abstract class SunGraphicsEnvironment extends GraphicsEnvironment
implements FontSupport, DisplayChangedListener {
public static boolean isLinux;
public static boolean isSolaris;
public static boolean isWindows;
public static boolean noType1Font;
private static Font defaultFont;
private static String defaultFontFileName;
private static String defaultFontName;
public static final String lucidaFontName = "Lucida Sans Regular";
public static final String lucidaFileName = "LucidaSansRegular.ttf";
public static boolean debugFonts = false;
protected static Logger logger = null;
private static ArrayList badFonts;
public static String jreLibDirName;
public static String jreFontDirName;
private static HashSet<String> missingFontFiles = null;
private FontConfiguration fontConfig;
/* 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;
/* 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;
/* loadedAllFontFiles is set to true when all fonts on the font path are
* actually opened, validated and registered. This always implies
* discoveredAllFonts is true.
*/
private boolean loadedAllFontFiles = false;
protected HashSet registeredFontFiles = new HashSet();
public static String eudcFontFileName; /* Initialised only on windows */
private static boolean isOpenJDK;
/**
* A few things in Java 2D code are different in OpenJDK,
* so need a way to tell which implementation this is.
* The absence of Lucida Sans Regular is the simplest way for now.
*/
public static boolean isOpenJDK() {
return isOpenJDK;
}
static {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
jreLibDirName = System.getProperty("java.home","") +
File.separator + "lib";
jreFontDirName = jreLibDirName + File.separator + "fonts";
File lucidaFile =
new File(jreFontDirName + File.separator + lucidaFileName);
isOpenJDK = !lucidaFile.exists();
String debugLevel =
System.getProperty("sun.java2d.debugfonts");
if (debugLevel != null && !debugLevel.equals("false")) {
debugFonts = true;
logger = Logger.getLogger("sun.java2d");
if (debugLevel.equals("warning")) {
logger.setLevel(Level.WARNING);
} else if (debugLevel.equals("severe")) {
logger.setLevel(Level.SEVERE);
}
}
return null;
}
});
};
public SunGraphicsEnvironment() {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
String osName = System.getProperty("os.name");
if ("Linux".equals(osName)) {
isLinux = true;
} else if ("SunOS".equals(osName)) {
isSolaris = true;
} else if ("Windows".equals(osName)) {
isWindows = true;
}
noType1Font = "true".
equals(System.getProperty("sun.java2d.noType1Font"));
if (isOpenJDK()) {
String[] fontInfo = FontManager.getDefaultPlatformFont();
defaultFontName = fontInfo[0];
defaultFontFileName = fontInfo[1];
} else {
defaultFontName = lucidaFontName;
if (useAbsoluteFontFileNames()) {
defaultFontFileName =
jreFontDirName + File.separator + lucidaFileName;
} else {
defaultFontFileName = lucidaFileName;
}
}
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 (debugFonts) {
logger.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 (isLinux) {
/* Linux font configuration uses these fonts */
registerFontDir(jreFontDirName);
}
registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK,
true, false);
/* Register the JRE fonts so that the native platform can
* access them. This is used only on Windows so that when
* printing the printer driver can access the fonts.
*/
registerJREFontsWithPlatform(jreFontDirName);
/* Create the font configuration and get any font path
* that might be specified.
*/
fontConfig = createFontConfiguration();
getPlatformFontPathFromFontConfig();
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 (debugFonts) {
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 (isSolaris && Locale.JAPAN.equals(Locale.getDefault())) {
registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT");
}
initCompositeFonts(fontConfig, null);
/* Establish the default font to be used by SG2D etc */
defaultFont = new Font(Font.DIALOG, Font.PLAIN, 12);
return null;
}
});
}
protected GraphicsDevice[] screens;
/**
* Returns an array of all of the screen devices.
*/
public synchronized GraphicsDevice[] getScreenDevices() {
GraphicsDevice[] ret = screens;
if (ret == null) {
int num = getNumScreens();
ret = new GraphicsDevice[num];
for (int i = 0; i < num; i++) {
ret[i] = makeScreenDevice(i);
}
screens = ret;
}
return ret;
}
protected abstract int getNumScreens();
protected abstract GraphicsDevice makeScreenDevice(int screennum);
/**
* Returns the default screen graphics device.
*/
public GraphicsDevice getDefaultScreenDevice() {
return getScreenDevices()[0];
}
/**
* Returns a Graphics2D object for rendering into the
* given BufferedImage.
* @throws NullPointerException if BufferedImage argument is null
*/
public Graphics2D createGraphics(BufferedImage img) {
if (img == null) {
throw new NullPointerException("BufferedImage cannot be null");
}
SurfaceData sd = SurfaceData.getPrimarySurfaceData(img);
return new SunGraphics2D(sd, Color.white, Color.black, defaultFont);
}
/* A call to this method should be followed by a call to
* registerFontDirs(..)
*/
protected String getPlatformFontPath(boolean noType1Font) {
if (fontPath == null) {
fontPath = FontManager.getFontPath(noType1Font);
}
return fontPath;
}
private String[] platformFontDirs;
/**
* Get all directories which contain installed fonts.
*/
public String[] getPlatformFontDirs() {
if (platformFontDirs == null) {
String path = getPlatformFontPath(noType1Font);
StringTokenizer parser =
new StringTokenizer(path, File.pathSeparator);;
ArrayList<String> pathList = new ArrayList<String>();
try {
while (parser.hasMoreTokens()) {
pathList.add(parser.nextToken());
}
} catch (NoSuchElementException e) {
}
platformFontDirs = pathList.toArray(new String[0]);
}
return platformFontDirs;
}
/**
* Whether registerFontFile expects absolute or relative
* font file names.
*/
protected boolean useAbsoluteFontFileNames() {
return true;
}
/**
* Returns file name for default font, either absolute
* or relative as needed by registerFontFile.
*/
public String getDefaultFontFile() {
return defaultFontFileName;
}
/**
* Returns face name for default font, or null if
* no face names are used for CompositeFontDescriptors
* for this platform.
*/
public String getDefaultFontFaceName() {
return defaultFontName;
}
public void loadFonts() {
if (discoveredAllFonts) {
return;
}
/* Use lock specific to the font system */
synchronized (lucidaFontName) {
if (debugFonts) {
Thread.dumpStack();
logger.info("SunGraphicsEnvironment.loadFonts() called");
}
FontManager.initialiseDeferredFonts();
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
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 (!FontManager.gotFontsFromPlatform()) {
registerFontsOnPath(fontPath, false,
Font2D.UNKNOWN_RANK,
false, true);
loadedAllFontFiles = true;
}
}
FontManager.registerOtherFontFiles(registeredFontFiles);
discoveredAllFonts = true;
return null;
}
});
}
}
public void loadFontFiles() {
loadFonts();
if (loadedAllFontFiles) {
return;
}
/* Use lock specific to the font system */
synchronized (lucidaFontName) {
if (debugFonts) {
Thread.dumpStack();
logger.info("loadAllFontFiles() called");
}
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
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 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 = FontManager.getFileNameForFontName(fontName);
if (fileName == null) {
return false;
}
return registeredFontFiles.contains(fileName);
}
private Font[] allFonts;
/**
* Returns all fonts installed in this environment.
*/
public Font[] getAllInstalledFonts() {
if (allFonts == null) {
loadFonts();
TreeMap 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 = FontManager.getRegisteredFonts();
for (int i=0; i < allfonts.length; i++) {
if (!(allfonts[i] instanceof NativeFont)) {
fontMapNames.put(allfonts[i].getFontName(null),
allfonts[i]);
}
}
String[] platformNames = FontManager.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 = (Font2D)fontMapNames.get(fontNames[i]);
if (f2d != null) {
FontManager.setFont2D(fonts[i], f2d.handle);
}
}
allFonts = fonts;
}
Font []copyFonts = new Font[allFonts.length];
System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length);
return copyFonts;
}
/**
* Returns all fonts available in this environment.
*/
public Font[] getAllFonts() {
Font[] installedFonts = getAllInstalledFonts();
Font[] created = FontManager.getCreatedFonts();
if (created == null || created.length == 0) {
return installedFonts;
} else {
int newlen = installedFonts.length + created.length;
Font [] fonts = java.util.Arrays.copyOf(installedFonts, newlen);
System.arraycopy(created, 0, fonts,
installedFonts.length, created.length);
return fonts;
}
}
/**
* 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;
public static Locale getSystemStartupLocale() {
if (systemLocale == null) {
systemLocale = (Locale)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
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;
}
/* 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) {
FontManager.registerDeferredJREFonts(jreFontDirName);
Font2D[] physicalfonts = FontManager.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);
}
}
}
private String[] allFamilies; // cache for default locale only
private Locale lastDefaultLocale;
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()) &&
FontManager.getFamilyNamesFromPlatform(familyNames,
requestedLocale)) {
/* Augment platform names with JRE font family names */
getJREFontFamilyNames(familyNames, requestedLocale);
} else {
loadFontFiles();
Font2D[] physicalfonts = FontManager.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);
}
}
}
String[] retval = new String[familyNames.size()];
Object [] keyNames = familyNames.keySet().toArray();
for (int i=0; i < keyNames.length; i++) {
retval[i] = (String)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;
}
public String[] getAvailableFontFamilyNames(Locale requestedLocale) {
String[] installed = getInstalledFontFamilyNames(requestedLocale);
/* Use a new TreeMap as used in getInstalledFontFamilyNames
* and insert all the keys in lower case, so that the sort order
* is the same as the installed families. This preserves historical
* behaviour and inserts new families in the right place.
* It would have been marginally more efficient to directly obtain
* the tree map and just insert new entries, but not so much as
* to justify the extra internal interface.
*/
TreeMap<String, String> map = FontManager.getCreatedFontFamilyNames();
if (map == null || map.size() == 0) {
return installed;
} else {
for (int i=0; i<installed.length; i++) {
map.put(installed[i].toLowerCase(requestedLocale),
installed[i]);
}
String[] retval = new String[map.size()];
Object [] keyNames = map.keySet().toArray();
for (int i=0; i < keyNames.length; i++) {
retval[i] = (String)map.get(keyNames[i]);
}
return retval;
}
}
public String[] getAvailableFontFamilyNames() {
return getAvailableFontFamilyNames(Locale.getDefault());
}
/**
* 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);
}
public 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));
}
}
}
public 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));
}
}
}
public 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);
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));
}
}
}
}
/* No need to keep consing up new instances - reuse a singleton.
* The trade-off is that these objects don't get GC'd.
*/
public static final TTFilter ttFilter = new TTFilter();
public static final T1Filter t1Filter = new T1Filter();
/* The majority of the register functions in this class are
* registering platform fonts in the JRE's font maps.
* The next one is opposite in function as it registers the JRE
* fonts as platform fonts. If subsequent to calling this
* your implementation enumerates platform fonts in a way that
* would return these fonts too you may get duplicates.
* This function is primarily used to install the JRE fonts
* so that the native platform can access them.
* It is intended to be overridden by platform subclasses
* Currently minimal use is made of this as generally
* Java 2D doesn't need the platform to be able to
* use its fonts and platforms which already have matching
* fonts registered (possibly even from other different JRE
* versions) generally can't be guaranteed to use the
* one registered by this JRE version in response to
* requests from this JRE.
*/
protected void registerJREFontsWithPlatform(String pathName) {
return;
}
/* Called from FontManager - has Solaris specific implementation */
public void register1dot0Fonts() {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
String type1Dir = "/usr/openwin/lib/X11/fonts/Type1";
registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK,
false, false);
return null;
}
});
}
protected void registerFontDirs(String pathName) {
return;
}
/* Called to register fall back fonts */
public void registerFontsInDir(String dirName) {
registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false);
}
private void registerFontsInDir(String dirName, boolean useJavaRasterizer,
int fontRank,
boolean defer, boolean resolveSymLinks) {
File pathFile = new File(dirName);
addDirFonts(dirName, pathFile, ttFilter,
FontManager.FONTFORMAT_TRUETYPE, useJavaRasterizer,
fontRank==Font2D.UNKNOWN_RANK ?
Font2D.TTF_RANK : fontRank,
defer, resolveSymLinks);
addDirFonts(dirName, pathFile, t1Filter,
FontManager.FONTFORMAT_TYPE1, useJavaRasterizer,
fontRank==Font2D.UNKNOWN_RANK ?
Font2D.TYPE1_RANK : fontRank,
defer, resolveSymLinks);
}
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) {
}
}
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 = FontManager.FONTFORMAT_TRUETYPE;
} else if (t1Filter.accept(null, fontFileName)) {
fontFormat = FontManager.FONTFORMAT_TYPE1;
} else {
fontFormat = FontManager.FONTFORMAT_NATIVE;
}
registeredFontFiles.add(fontFileName);
if (defer) {
FontManager.registerDeferredFont(fontFileName,
fontFileName, nativeNames,
fontFormat, false, fontRank);
} else {
FontManager.registerFontFile(fontFileName, nativeNames,
fontFormat, false, fontRank);
}
}
protected void registerFontDir(String path) {
}
protected String[] getNativeNames(String fontFileName,
String platformName) {
return null;
}
/*
* 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 (debugFonts) {
logger.warning("skip bad font " + fullName);
}
continue; // skip this font file.
}
registeredFontFiles.add(fullName);
if (debugFonts && logger.isLoggable(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];
}
}
logger.info(message);
}
fontNames[fontCount] = fullName;
nativeNames[fontCount++] = getNativeNames(fullName, null);
}
FontManager.registerFonts(fontNames, nativeNames, fontCount,
fontFormat, useJavaRasterizer, fontRank,
defer);
return;
}
/*
* 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);
}
/**
* Creates this environment's FontConfiguration.
*/
protected abstract FontConfiguration createFontConfiguration();
public abstract FontConfiguration
createFontConfiguration(boolean preferLocaleFonts,
boolean preferPropFonts);
/*
* 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) {
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) {
/* 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) {
FontManager.registerCompositeFont(
descriptor.getFaceName(),
componentFileNames, componentFaceNames,
descriptor.getCoreComponentCount(),
descriptor.getExclusionRanges(),
descriptor.getExclusionRangeLimits(),
true,
altNameCache);
} else {
FontManager.registerCompositeFont(
descriptor.getFaceName(),
componentFileNames, componentFaceNames,
descriptor.getCoreComponentCount(),
descriptor.getExclusionRanges(),
descriptor.getExclusionRangeLimits(),
true);
}
if (debugFonts) {
logger.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 registerPlatformFontsUsedByFontConfiguration() {
}
/**
* Determines whether the given font is a logical font.
*/
public static boolean isLogicalFont(Font f) {
return FontConfiguration.isLogicalFontFamilyName(f.getFamily());
}
/**
* Return the default font configuration.
*/
public FontConfiguration getFontConfiguration() {
return fontConfig;
}
/**
* Return the bounds of a GraphicsDevice, less its screen insets.
* See also java.awt.GraphicsEnvironment.getUsableBounds();
*/
public static Rectangle getUsableBounds(GraphicsDevice gd) {
GraphicsConfiguration gc = gd.getDefaultConfiguration();
Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
Rectangle usableBounds = gc.getBounds();
usableBounds.x += insets.left;
usableBounds.y += insets.top;
usableBounds.width -= (insets.left + insets.right);
usableBounds.height -= (insets.top + insets.bottom);
return usableBounds;
}
/**
* This method is provided for internal and exclusive use by Swing.
* This method should no longer be called, instead directly call
* FontManager.fontSupportsDefaultEncoding(Font).
* This method will be removed once Swing is updated to no longer
* call it.
*/
public static boolean fontSupportsDefaultEncoding(Font font) {
return FontManager.fontSupportsDefaultEncoding(font);
}
public static void useAlternateFontforJALocales() {
FontManager.useAlternateFontforJALocales();
}
/*
* 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);
}
/* If (as we do on X11) need to set a platform font path,
* then the needed path may be specified by the platform
* specific FontConfiguration class & data file. Such platforms
* (ie X11) need to override this method to retrieve this information
* into suitable data structures.
*/
protected void getPlatformFontPathFromFontConfig() {
}
/**
* From the DisplayChangedListener interface; called
* when the display mode has been changed.
*/
public void displayChanged() {
// notify screens in device array to do display update stuff
for (GraphicsDevice gd : getScreenDevices()) {
if (gd instanceof DisplayChangedListener) {
((DisplayChangedListener) gd).displayChanged();
}
}
// notify SunDisplayChanger list (e.g. VolatileSurfaceManagers and
// SurfaceDataProxies) about the display change event
displayChanger.notifyListeners();
}
/**
* Part of the DisplayChangedListener interface:
* propagate this event to listeners
*/
public void paletteChanged() {
displayChanger.notifyPaletteChanged();
}
/*
* ----DISPLAY CHANGE SUPPORT----
*/
protected SunDisplayChanger displayChanger = new SunDisplayChanger();
/**
* Add a DisplayChangeListener to be notified when the display settings
* are changed.
*/
public void addDisplayChangedListener(DisplayChangedListener client) {
displayChanger.add(client);
}
/**
* Remove a DisplayChangeListener from Win32GraphicsEnvironment
*/
public void removeDisplayChangedListener(DisplayChangedListener client) {
displayChanger.remove(client);
}
/*
* ----END DISPLAY CHANGE SUPPORT----
*/
}