/*
* This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com>
* Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/)
*/
/*
* @(#)SunGraphicsEnvironment.java 1.109 03/01/23
*
* Copyright 2003 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
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.awt.print.PrinterJob;
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.Hashtable;
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 sun.awt.FontProperties;
import sun.awt.font.NativeFontWrapper;
import sun.awt.image.BufImgSurfaceData;
import sun.awt.AppContext;
/**
* This is an implementation of a GraphicsEnvironment object for the
* default local GraphicsEnvironment.
*
* @see GraphicsDevice
* @see GraphicsConfiguration
* @version 1.109 01/23/03
*/
public abstract class SunGraphicsEnvironment extends GraphicsEnvironment
implements FontSupport {
protected static boolean debugMapping = false;
private static Font defaultFont;
private static String [] logicalFontNames = {
"default",
"serif",
"sansserif",
"monospaced",
"dialog",
"dialoginput",
};
private FontProperties fprops;
private TreeMap terminalNames;
private HashSet physicalNames;
private boolean loadedAllFonts;
protected boolean registeredAllPaths = false;
protected boolean noType1Font = false;
protected String fontPath;
protected TreeMap registeredFonts;
private Hashtable mapFamilyCache;
protected boolean loadNativeFonts = false;
private ArrayList badFonts;
public SunGraphicsEnvironment() {
loadedAllFonts = false;
registeredFonts = new TreeMap();
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
if (System.getProperty("sun.java2d.debugfonts") != null) {
debugMapping = true;
}
fontPath = System.getProperty("sun.java2d.fontpath", "");
if (fontPath != null && !fontPath.equals(""))
registeredAllPaths = true;
String defaultPathName =
System.getProperty("java.home","") + File.separator +
"lib" + File.separator + "fonts";
File badFontFile =
new File(defaultPathName + 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 (debugMapping) {
System.out.println("read bad font "+ name);
}
badFonts.add(name);
}
}
} catch (IOException e) {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ioe) {
}
}
}
/* Install the JRE fonts so that the native platform
* can access them.
*/
registerFontsWithPlatform(defaultPathName);
fprops = createFontProperties();
/* use "appendedfontpath" in font.properties file to add
additional fontpath(s) */
String appendedPathName = null;
if (fprops != null){
appendedPathName = fprops.getProperty("appendedfontpath");
}
if (fontPath.length() == 0) {
String prop = System.getProperty("sun.java2d.noType1Font");
if (prop == null) {
noType1Font = NativeFontWrapper.getType1FontVar();
}
if ("true".equals(prop)) {
noType1Font = true;
}
loadNativeFonts = true;
fontPath = getBasePlatformFontPath(noType1Font);
fontPath = defaultPathName + File.pathSeparator + fontPath;
if (appendedPathName != null){
fontPath = fontPath + File.pathSeparator + appendedPathName;
} else if (!registeredAllPaths) {
fontPath = fontPath + File.pathSeparator +
getPlatformFontPath(noType1Font);
}
if (debugMapping) {
System.out.println("Platform Font Path=" + fontPath);
}
}
/* On windows the registerFontPaths method does nothing.
* Solaris specific notes (nowhere better to document) :-
* register just the paths, (it doesn't register the fonts).
* This call was being made already when the
* composite font building code needed to find a file
* for a composite font entry. Its just been moved up
* front to make things more obvious.
* We register all the paths on Solaris, because
* the fontPath we have here is the complete one from
* parsing /var/sadm/install/contents, not just
* what's on the X font path (may be this should be
* changed).
* But for now what it means is that if we didn't do
* this then if the font weren't listed anywhere on the
* less complete font path we'd trigger loadFonts which
* actually registers the fonts. This may actually be
* the right thing tho' since that would also set up
* the X font path without which we wouldn't be able to
* display some "native" fonts.
* So something to revisit is that probably fontPath
* here ought to be only the X font path + jre font dir.
* loadFonts should have a separate native call to
* get the rest of the platform font path.
*/
registerFontPaths(fontPath);
/*
* Here we get the fonts from the library 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
* instead for Lucida Sans which we now (since 1.4 beta 2)
* implicitly add as the last component of all composite fonts.
* Note that the jre fonts dir is now pre-pended to the font
* path too. Pass "true" to registerFonts method as these
* JRE fonts always go through the T2K rasteriser.
*/
registerFonts(defaultPathName, true);
initCompositeFonts(fprops);
/* Add the fonts already registered : typically the JRE fonts
* and the platform fonts which comprise the composite fonts,
* into the terminal names map. Failing to do so triggers
* loadfonts() when they are referenced. Note that platform
* "native" fonts which we may not choose to expose may be
* added but this is harmless since the map is used only to
* decide whether to call loadfonts(). It is independent of
* those listed by getAllFonts().
* This has no effect if the font name isn't a full
* path name - so it works on solaris but not on windows.
* So on windows "physicalNames" serves the same purpose
* This needs rewriting to have just one way of doing it.
*/
Object [] regFonts = registeredFonts.keySet().toArray();
for (int i=0; i<regFonts.length; i++) {
String key = (String)regFonts[i];
String name = NativeFontWrapper.getFullNameByFileName(key);
if (name != null) {
name = name.toLowerCase();
}
if (name != null && !terminalNames.containsKey(name)) {
terminalNames.put(name, new Integer(0));
}
}
return null;
}
});
}
protected String getBasePlatformFontPath(boolean noType1Font) {
return getPlatformFontPath(noType1Font);
}
protected String getPlatformFontPath(boolean noType1Font) {
registeredAllPaths = true;
return NativeFontWrapper.getFontPath(noType1Font);
}
protected GraphicsDevice[] screens;
protected synchronized void loadFonts() {
if (loadedAllFonts) {
return;
}
if (debugMapping) {
System.out.println("loadfonts called");
}
final String newPath;
if (!registeredAllPaths) {
newPath = getPlatformFontPath(noType1Font);
registerFontPaths(newPath);
/* Results of next line will not be used by runtime code, but
* may be clearer when debugging to know the full path */
fontPath = fontPath + File.separator + newPath;
} else {
newPath = fontPath;
}
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
// this will find all fonts including those already
// registered. But we have checks in place to prevent
// double registration.
boolean foundFonts = registerFonts(newPath, false);
boolean foundNativeFonts = false;
if (loadNativeFonts) {
foundNativeFonts = registerNativeFonts ();
}
// Needed to add the test for existing # of registered
// fonts being zero, else this would have caused a JRE
// exits if no NEW fonts were registered.
if ((!foundFonts) && (!foundNativeFonts) &&
registeredFonts.size() == 0) {
//- REMIND adg: need to throw an exception
System.out.println("\nNo fonts were found in '"
+ newPath + "'.\n");
System.exit(2);
}
loadedAllFonts = true;
return null;
}
});
}
/**
* 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 = BufImgSurfaceData.createData(img);
if (defaultFont == null) {
defaultFont = new Font("Dialog", Font.PLAIN, 12);
}
return new SunGraphics2D(sd, Color.white, Color.black, defaultFont);
}
private Font[] allFonts;
/**
* Returns all fonts available in this environment.
*/
public Font[] getAllFonts() {
if (allFonts != null) {
return allFonts;
}
loadFonts();
Font [] fonts = null;
String [] fontNames = null;
int count = NativeFontWrapper.getNumFonts();
if (count > 0) {
TreeMap fontMapNames = new TreeMap();
for (int i=0; i < count; i++) {
String name = NativeFontWrapper.getFullNameByIndex(i);
if (! name.startsWith(PLSF_PREFIX)) {
fontMapNames.put(name, 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];
}
}
}
if (fontNames != null) {
fonts = new Font[fontNames.length];
for (int i=0; i < fontNames.length; i++) {
fonts[i] = new Font(fontNames[i], Font.PLAIN, 1);
}
}
allFonts = fonts;
return allFonts;
}
public String[] getAvailableFontFamilyNames(Locale theLocale) {
// Need to allow for a null locale, as specified in doc
if (theLocale == null) {
return getAvailableFontFamilyNames();
}
loadFonts();
String [] retval = null;
int count = NativeFontWrapper.getNumFonts();
int localeID = NativeFontWrapper.getLCIDFromLocale(theLocale);
if (count > 0) {
TreeMap familyNames = new TreeMap();
for (int i=0; i < count; i++) {
String name = NativeFontWrapper.getFamilyNameByIndex(i, localeID);
String cmpName = name.toLowerCase();
if ( cmpName.endsWith( ".bold" ) ||
cmpName.endsWith( ".bolditalic" ) ||
cmpName.endsWith ( ".italic" ) ||
cmpName.startsWith ( PLSF_PREFIX )) {
}
else {
familyNames.put(cmpName, name);
}
}
String str;
// compatibility
str = "Serif"; familyNames.put(str.toLowerCase(), str);
str = "SansSerif"; familyNames.put(str.toLowerCase(), str);
str = "Monospaced"; familyNames.put(str.toLowerCase(), str);
str = "Dialog"; familyNames.put(str.toLowerCase(), str);
str = "DialogInput"; familyNames.put(str.toLowerCase(), str);
str = "Default"; familyNames.put(str.toLowerCase(), str);
if (familyNames.size() > 0) {
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]);
}
}
}
return retval;
}
public String[] getAvailableFontFamilyNames() {
return getAvailableFontFamilyNames(Locale.getDefault());
}
// implements FontSupport.mapFontName
public String mapFontName(String fontName, int style) {
// try the cache first
String mappedName = (String) mapFontCache.get(fontName + "." + styleStr(style));
if (mappedName != null) {
if (fprops.supportPLSF() || fallbackFont != null) {
return getInternalFontName(mappedName);
}
return mappedName;
}
String lowerCaseName = fontName.toLowerCase(Locale.ENGLISH);
// 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.
// Also check for a few common misspellings of sansserif.
if (lowerCaseName.equals("sanserif") || lowerCaseName.equals("san serif") ||
lowerCaseName.equals("sans serif") || lowerCaseName.equals("ms sans serif")) {
lowerCaseName = "sansserif";
} else if (lowerCaseName.equals("ms serif" )) {
lowerCaseName = "serif";
}
// Check whether we have a logical font family name
if (FontProperties.isLogicalFontFamilyName(lowerCaseName)) {
mappedName = getLogicalFontFaceName(lowerCaseName, style);
}
// Check whether an alias is defined for the name
if (mappedName == null) {
String aliasName = fprops.getAliasedFamilyName(lowerCaseName);
if (aliasName != null) {
mappedName = getLogicalFontFaceName(aliasName, style);
}
}
// Look for a physical font name, and use fallback font if no physical font exists
if (mappedName == null) {
// If the font name is one of the JDK 1.0 logical font names, we may want to
// use special fallback mappings if no physical font exists. Therefore,
// we need to be precise in looking for physical fonts, i.e., go to the native
// level which has more complete information about physical fonts.
// REMIND: remove this compatibility workaround from the next feature release.
if (fprops.getFallbackFamilyName(lowerCaseName, null) != null) {
// Check whether a font is registered to support this font/style
// combination. At this point, this would normally be a physical font.
// We may do this check twice, once without, once with loadFonts, since
// loadFonts can be quite expensive, and the font may already have
// been registered as the component of a logical font.
// Also, we don't want to synchronize this entire section, so
// we need to have the check on loadedAllFonts first.
if (loadedAllFonts) {
if (NativeFontWrapper.isFontRegistered(fontName, style)) {
mappedName = fontName;
}
} else {
if (NativeFontWrapper.isFontRegistered(fontName, style)) {
mappedName = fontName;
} else {
if (debugMapping) {
System.out.println("calling loadFonts to find font " + fontName);
}
loadFonts();
if (NativeFontWrapper.isFontRegistered(fontName, style)) {
mappedName = fontName;
}
}
}
// Apply a fallback mapping if there is no physical font.
if (mappedName == null) {
String fallbackName = fprops.getFallbackFamilyName(lowerCaseName, "dialog");
mappedName = getLogicalFontFaceName(fallbackName, style);
}
} else {
// For all other font names, we don't need to do a precise lookup here.
// We're checking whether the font name has already been registered as
// as the component font of a logical font in order to avoid unnecessary
// calls to loadFonts. But if we don't find the font here, we just use
// the given font name, make sure all fonts have been loaded, and let
// initializeFont (called from the Font constructor) deal with a precise
// lookup and, if necessary, the fallback to Dialog.
if (!terminalNames.containsKey(lowerCaseName)
&& (physicalNames == null || (!physicalNames.contains(lowerCaseName)))) {
if (debugMapping) {
System.out.println("calling loadFonts to find font " + fontName);
}
loadFonts();
}
mappedName = fontName;
}
}
//assertion: mappedName != null;
// cache and return the result
if (debugMapping) {
System.out.println("mapped font " + fontName
+ " (" + styleStr(style) + ") " + " to " + mappedName);
}
mapFontCache.put(fontName + "." + styleStr(style), mappedName);
if (fprops.supportPLSF() || fallbackFont != null) {
if (debugMapping) {
String newName = getInternalFontName(mappedName);
System.out.println("==============================================================");
System.out.println("originalName =" + mappedName + ", localeName=" + newName);
System.out.println("currentThread=" + Thread.currentThread());
return newName;
}
else {
return getInternalFontName(mappedName);
}
}
return mappedName;
}
private String getLogicalFontFaceName(String familyName, int style) {
String fullName = familyName.toLowerCase(Locale.ENGLISH) + "." + styleStr(style);
if (terminalNames.containsKey(fullName)) {
return fullName;
}
return familyName;
}
private static Hashtable mapFontCache = new Hashtable(5, (float) 0.9);
// can have platform-specific overrides - see X11GraphicsEnvironment
protected String parseFamilyNameProperty(String name) {
int separator = name.indexOf(",");
if (separator == -1) {
separator = name.length();
}
return name.substring(0, separator);
}
// can have platform-specific overrides - see X11GraphicsEnvironment
protected String getFontPropertyFD(String name) {
return parseFamilyNameProperty(name);
}
// can have platform-specific overrides - see X11GraphicsEnvironment
protected String getFileNameFromPlatformName(String platName) {
if (fprops == null) {
return null;
}
platName = platName.replace(' ','_');
return fprops.getProperty("filename" + "." + platName);
}
/**
* Gets a <code>PrintJob2D</code> object suitable for the
* the current platform.
* @return a <code>PrintJob2D</code> object.
* @see java.awt.PrintJob2D
* @since JDK1.2
*/
public PrinterJob getPrinterJob() {
new Exception().printStackTrace();
return null;
}
/* MACPORTING NOTE.needs to do file type on the Macintosh */
// adg: ttc files are now handled by the ttf code
public class TTFilter implements FilenameFilter{
public boolean accept(File dir,String name) {
return(name.endsWith(".ttf") ||
name.endsWith(".TTF") ||
name.endsWith(".ttc") ||
name.endsWith(".TTC"));
}
}
public class T2KFilter implements FilenameFilter{
public boolean accept(File dir,String name) {
return(name.endsWith(".t2k") ||
name.endsWith(".T2K"));
}
}
public class T1Filter implements FilenameFilter{
public boolean accept(File dir,String name) {
return(name.endsWith(".ps") ||
name.endsWith(".PS") ||
name.endsWith(".pfb") ||
name.endsWith(".PFB") ||
name.endsWith(".pfa") ||
name.endsWith(".PFA"));
}
}
/* 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 registerFontsWithPlatform(String pathName) {
return;
}
protected void registerFontPaths(String pathName) {
return;
}
private boolean registerFonts(String pathName, boolean useJavaRasterizer) {
boolean retval = false;
StringTokenizer parser = new StringTokenizer(pathName,
File.pathSeparator);
try {
while (parser.hasMoreTokens()) {
String newPath = parser.nextToken();
// paths now registered in constructor.
// registerFontPath(newPath);
retval |= addPathFonts(newPath, new TTFilter(),
NativeFontWrapper.FONTFORMAT_TRUETYPE,
useJavaRasterizer);
retval |= addPathFonts(newPath, new T1Filter(),
NativeFontWrapper.FONTFORMAT_TYPE1,
useJavaRasterizer);
retval |= addPathFonts(newPath, new T2KFilter(),
NativeFontWrapper.FONTFORMAT_T2K,
useJavaRasterizer);
}
} catch (NoSuchElementException e) {
System.err.println(e);
}
return retval;
}
// ** REMIND : VERIFY WHAT THIS DOES ON WINDOWS
// It appears this method is used only on windows
// On solaris it it were called it would be passed a full path name
// which is clearly not what its expecting.
// If it gets just a file name on windows that would "work" but be
// really bad code. If it gets a full path then I don't see how it
// could possibly work.
// can have platform specific override
protected void registerFontFile(String fontFileName, Vector nativeNames) {
// REMIND: case compare depends on platform
if (registeredFonts.containsKey(fontFileName)) {
return;
}
int fontFormat;
if (new TTFilter().accept(null, fontFileName)) {
fontFormat = NativeFontWrapper.FONTFORMAT_TRUETYPE;
} else if (new T1Filter().accept(null, fontFileName)) {
fontFormat = NativeFontWrapper.FONTFORMAT_TYPE1;
} else if (new T2KFilter().accept(null, fontFileName)) {
fontFormat = NativeFontWrapper.FONTFORMAT_T2K;
} else {
registerNative (fontFileName);
return;
}
StringTokenizer parser = new StringTokenizer(fontPath,
File.pathSeparator);
try {
while (parser.hasMoreTokens()) {
String newPath = parser.nextToken();
File theFile = new File(newPath, fontFileName);
String path = null;
try {
path = theFile.getCanonicalPath();
} catch (IOException e) {
path = theFile.getAbsolutePath();
}
if (theFile.canRead()) {
Vector fontNames = new Vector(1, 1);
Vector platNames = new Vector(1, 1);
platNames.addElement(nativeNames);
fontNames.addElement(path);
registeredFonts.put(fontFileName, path);
NativeFontWrapper.registerFonts(fontNames,
fontNames.size(),
platNames,
fontFormat, false);
/* We note the physical fonts registered for font
* properties so that we can add these to the
* set searched before calling loadfonts()
*/
if (physicalNames == null) {
physicalNames = new HashSet();
}
String name =
NativeFontWrapper.getFullNameByFileName(path);
if (name != null) {
name = name.toLowerCase();
}
if (!physicalNames.contains(name)) {
physicalNames.add(name);
}
break;
}
}
} catch (NoSuchElementException e) {
System.err.println(e);
}
}
// can have platform specific override
protected void registerFontPath(String path) {
}
protected void registerNative (String fontFileName) {
}
protected Vector getNativeNames (String fontFileName) {
Vector v = new Vector();
//v.add(fontFileName);
return v;
}
protected boolean registerNativeFonts () {
return false;
}
/*
* helper function for registerFonts
*/
private boolean addPathFonts(String path, FilenameFilter filter,
int fontFormat, boolean useJavaRasterizer) {
boolean retval = false;
Vector fontNames = new Vector(20, 10);
Vector nativeNames = new Vector(20,10);
File f1 = new File(path);
String[] ls = f1.list(filter);
if (ls == null) {
return retval;
}
for (int i=0; i < ls.length; i++ ) {
File theFile = new File(f1, ls[i]);
String fullName = null;
try {
fullName = theFile.getCanonicalPath();
} catch (IOException e) {
fullName = theFile.getAbsolutePath();
}
// REMIND: case compare depends on platform
if (registeredFonts.containsKey(fullName)) {
continue;
}
if (badFonts != null && badFonts.contains(fullName)) {
if (debugMapping) {
System.out.println("skip bad font " + fullName);
}
continue; // skip this font file.
}
registeredFonts.put(fullName, fullName);
if (debugMapping) {
System.out.println("Registering font " + fullName);
Vector v = getNativeNames(fullName);
if (v.size() == 0) {
System.out.println("No native name");
} else {
for (int nn=0; nn< v.size(); nn++) {
System.out.println("native name : " +
(String)v.elementAt(nn));
}
}
}
fontNames.addElement(fullName);
nativeNames.addElement (getNativeNames(fullName));
retval = true;
}
// REMIND - native code might not register everything which we
// pass into it.
NativeFontWrapper.registerFonts(fontNames, fontNames.size(),
nativeNames,
fontFormat, useJavaRasterizer );
return retval; // REMIND: get status of registration from native
}
/**
* Resolve styles on the character at start into an instance of Font
* that can best render the text between start and limit.
* REMIND jk. Move it to graphics environment.
*/
public static Font getBestFontFor(AttributedCharacterIterator text,
int start, int limit) {
/*
* choose the first font that can display the first character
* first iterate through the styles in the range of text we were
* passed. If none of them work, iterate through font families
* using the attributes on the first character. If this also
* fails, use the first font.
*/
char c = text.setIndex(start);
Map ff = text.getAttributes();
Font font = Font.getFont(ff);
while (!font.canDisplay(c) && (text.getRunLimit() < limit)) {
text.setIndex(text.getRunLimit());
font = Font.getFont(text.getAttributes());
}
if (!font.canDisplay(c)) {
text.setIndex(start);
String[] families =
GraphicsEnvironment.getLocalGraphicsEnvironment(
).getAvailableFontFamilyNames();
for (int i = 0; i < families.length; ++i) {
Hashtable ht = new Hashtable();
ht.putAll(ff);
ht.put(TextAttribute.FAMILY, families[i]);
font = Font.getFont((Map)ht);
if (font.canDisplay(c)) {
break;
}
}
if (!font.canDisplay(c)) {
font = Font.getFont(ff);
}
}
return font;
}
/**
* Creates this environment's FontProperties.
*/
protected abstract FontProperties createFontProperties();
private void initCompositeFonts(FontProperties fprops) {
TreeMap terminalNames = initTerminalNames(fprops);
Object [] terminalKeys = terminalNames.keySet().toArray();
for (int i=0; i < terminalKeys.length; i++) {
String compositeFontName = (String)terminalKeys[i];
Integer maxEntryInt = (Integer)terminalNames.get(terminalKeys[i]);
int numEntries = maxEntryInt.intValue();
// Check to see if the Lucida Sans Regular is already in the
// font.properties as an entry. If it is do not add it to the
// list at the bottom as it would never be used.
boolean containsLucida = false;
for (int entries=0; entries < numEntries; entries++) {
String entryName = parseFamilyNameProperty(
fprops.getProperty(
compositeFontName + "." + entries));
if (entryName.compareToIgnoreCase("Lucida Sans Regular")==0) {
containsLucida = true;
break;
}
}
// Add an entry for Lucida Sans Regular
if ( containsLucida == false ) {
numEntries++; // one for the Lucida fallback
}
if (fallbackFont != null &&
this.terminalNames.containsKey(fallbackFont.toLowerCase(Locale.ENGLISH))) {
numEntries++; //for the font specified by setFallbackFont();
}
String names[] = new String[numEntries];
int exclusionMaxIndex[] = new int[numEntries];
int exclusionRanges[] = new int[0];
int totalEntries = numEntries;
if ( containsLucida == false ) {
// Add the Lucida Sans Regular font here for richer glyph
// availablity in the logical fonts. This enables Dingbats
// and Symbols glyphs.
names[numEntries - 1] = "Lucida Sans Regular";
totalEntries--;
}
if (fallbackFont != null &&
this.terminalNames.containsKey(fallbackFont.toLowerCase(Locale.ENGLISH))) {
if ( containsLucida == false ) {
names[numEntries - 2] = "Lucida Sans Regular";
}
names[numEntries - 1] = fallbackFont;
totalEntries--;
}
for (int j=0; j < totalEntries; j++) {
names[j] = parseFamilyNameProperty(
fprops.getProperty(
compositeFontName + "." + j));
if (debugMapping) {
System.out.println ( "The composite name = " + names[j] );
}
exclusionRanges =
appendExclusions(fprops, compositeFontName, j, exclusionRanges);
exclusionMaxIndex[j] = exclusionRanges.length;
}
if (debugMapping) {
System.out.println("initCompositeFonts compositeFontName="+
compositeFontName);
}
if (initPLSFFallback){
compositeFontName = prefixPLSF + compositeFontName;
}
if (debugMapping) {
System.out.println("registerCompositeFont:" + compositeFontName);
for (int j=0; j < numEntries; j++) {
System.out.println(" slot=" + names[j]);
}
}
NativeFontWrapper.registerCompositeFont(
compositeFontName, names,
exclusionRanges, exclusionMaxIndex);
}
terminalNames.put("default",new Integer(0));
if (!initPLSFFallback) {
//don't update the table if its not the first time
this.terminalNames = terminalNames;
}
}
private int[] appendExclusions(FontProperties fprops, String name, int slot, int [] ranges) {
// We check for exclusions first with family name and style, then
// family name only.
String familyName;
String styleName;
int period = name.indexOf('.');
if (period > 0) {
familyName = name.substring(0, period);
styleName = name.substring(period + 1);
} else {
familyName = name;
styleName = "plain";
}
String exclusions = fprops.getProperty(
"exclusion." + familyName + "." + styleName + "." + slot);
if (exclusions == null) {
exclusions = fprops.getProperty(
"exclusion." + familyName + "." + slot);
}
// REMIND: invent exclusion ranges for dingbats and symbols
// since no properties files specify them
// (or fix all properties files --- better)
if (exclusions != null) {
/*
* range format is xxxx-XXXX,yyyy-YYYY,.....
*/
int numExclusions = (exclusions.length() + 1) / 10;
if (numExclusions > 0) {
int newRanges[] = new int[numExclusions * 2];
for (int i = 0; i < numExclusions; i++) {
String lower = exclusions.substring(i*10 , i*10 + 4);
String upper = exclusions.substring(i*10 + 5, i*10 + 9);
newRanges[i*2 ] = Integer.parseInt(lower, 16);
newRanges[i*2+1] = Integer.parseInt(upper, 16);
}
int totalRanges = ranges.length + newRanges.length;
int tempRanges[] = new int[totalRanges];
System.arraycopy( ranges, 0,
tempRanges, 0,
ranges.length);
System.arraycopy( newRanges, 0,
tempRanges, ranges.length,
newRanges.length);
ranges = tempRanges;
}
}
return ranges;
}
private TreeMap initTerminalNames(FontProperties fprops) {
TreeMap predefinedNames = new TreeMap();
TreeMap registeredFileNames = new TreeMap();
TreeMap terminalNames = new TreeMap();
addPlatformCompatibilityFileNames(registeredFileNames);
String str;
// compatibility
str = "Serif";
predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str);
str = "SansSerif";
predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str);
str = "Monospaced";
predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str);
str = "Dialog";
predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str);
str = "DialogInput";
predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str);
if (fprops == null)
throw new Error("no font properties file found.");
Object [] propKeys = fprops.keySet().toArray();
for (int i=0; i < propKeys.length; i++) {
// discard keys which aren't predefined font family names
String property = (String)propKeys[i];
int separator = property.indexOf(".");
if (separator == -1) {
separator = property.length();
}
String propFamily = property.substring(0, separator);
if (!predefinedNames.containsKey(propFamily)) {
continue; // discard, not predefined
}
// find out how many entries for this key
separator = property.lastIndexOf(".");
if (separator == -1) {
continue; // discard, invalid format
}
String familyStyle = property.substring(0, separator);
if (terminalNames.containsKey(familyStyle)) {
continue; // discard, already analyzed
}
if (!fprops.containsKey(familyStyle + ".0")) {
continue; // discard, invalid file format
}
int maxEntry = 0;
while (fprops.containsKey(familyStyle + "." + maxEntry)) {
maxEntry++;
}
if (maxEntry == 0) {
continue; // discard, want direct mapping
}
terminalNames.put(familyStyle, new Integer(maxEntry));
if (debugMapping) {
System.out.println("FamilyStyle: " + familyStyle);
System.out.println("NumSlots: " + maxEntry);
System.out.println("Key: " + (String)propKeys[i]);
}
for (int j=0; j < maxEntry; j++) {
String platName = getFontPropertyFD(
fprops.getProperty(
familyStyle + "." + j));
if (!initPLSFFallback) {
//needed only the first time? ? ?
addPlatformNameForFontProperties(platName);
}
String fontFileName = getFileNameFromPlatformName(platName);
if (debugMapping) {
System.out.println("FS: [" + familyStyle + "." + j
+ "] PN: [" + platName + "] FN: ["
+ fontFileName + "]");
}
if (fontFileName == null) {
// invalid configuration file(s), but only warn if
// is debugging. Usually saves xterminal users from
// being told they don't have sun dingbats fonts.
if (debugMapping) {
System.err.println(
"Font specified in font.properties not found [" +
platName + "]");
}
// on headless loadfonts was being triggered
// every time because the native only fonts were
// not returning null for fontFileName.
// So now do only if local & headful.
loadFonts();
// was "break" here - why?
} else {
// A font file may occur more than once in font
// properties. It may appear with the same or different
// platform/native names - particularly on X11 where
// each encoding is a different platform name.
// We could just ask for the native names here and
// register those except that the font properties
// files have carefully crafted strings ready for
// a simple sprintf of the required pt size.
// We'd like to register those as our native names
// rather than the ones returned from X
// This is somewhat fiddly as we need to first gather
// all these used in the font properties file and
// associate them all with the same font.
// That needs to be delegated to the platform subclas
// as equating the native names needs to be done there.
HashSet s = (HashSet)registeredFileNames.get(fontFileName);
if (s == null) {
s = new HashSet();
s.add(platName);
registeredFileNames.put(fontFileName, s);
} else {
if (!s.contains(platName)) {
s.add(platName);
}
}
}
}
}
if (!initPLSFFallback) {
//needed only the first time? ? ?
registerFontPropertiesFonts(registeredFileNames);
}
return terminalNames;
}
/**
* Adds entries to registeredFileNames for fonts that should be
* preferred when looking for physical fonts. Each entry has a file name
* as its key and a HashSet with the platform names of fonts in the file
* as its value.
* REMIND: remove this method and references to it from the next feature release.
*/
protected void addPlatformCompatibilityFileNames(Map registeredFileNames) {
}
protected void addPlatformNameForFontProperties(String platName) {
return;
}
// this method accepts a TreeMap where either
// - the keys are font file names which may be or may not be full
// path names, and the value is a possibly empty set of native names
// - or the key and value are native names.
protected void registerFontPropertiesFonts(TreeMap fPropFonts) {
Object [] fonts = fPropFonts.keySet().toArray();
for (int i=0; i<fonts.length; i++) {
String fontFileName = (String)fonts[i];
HashSet s = (HashSet)fPropFonts.get(fontFileName);
String[] platNames = (String[])s.toArray(new String[0]);
Vector nativeNames = getNativeNames(fontFileName);
// merge the platNames & nativeNames.
for (int j=0;j<platNames.length;j++) {
if (!nativeNames.contains(platNames[j])) {
nativeNames.add(platNames[j]);
}
}
registerFontFile(fontFileName, nativeNames);
}
}
/*
* return String representation of style
*/
public static String styleStr(int num){
switch(num){
case Font.BOLD:
return "bold";
case Font.ITALIC:
return "italic";
case Font.ITALIC | Font.BOLD:
return "bolditalic";
default:
return "plain";
}
}
public static boolean isLogicalFont(Font f) {
String name = f.getFamily();
return isLogicalFont(name);
}
public static boolean isLogicalFont(String name) {
name = name.toLowerCase(Locale.ENGLISH);
for (int i=0; i<logicalFontNames.length; i++) {
if (name.equals(logicalFontNames[i])) {
return true;
}
}
return false;
}
public static String createFont(File fontFile) {
return
NativeFontWrapper.createFont(fontFile.getAbsolutePath(),
NativeFontWrapper.FONTFORMAT_TRUETYPE);
}
/**
* Return the current font properties.
*/
public FontProperties getFontProperties() {
if (!FPAName.equals(AppContext.getAppContext().get(FPAKey)) ||
fpropsPLSF == null) {
return fprops;
}
return fpropsPLSF;
}
/**
* 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;
}
/**
* @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 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.
*/
public 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(java.util.Locale.ENGLISH);
return NativeFontWrapper.fontSupportsEncoding(font, encoding);
}
/**
* Method invoked by applet to prefer "LocaleSpecificFonts" logic
* font mapping for the current AppContext.
*/
public static void preferLocaleSpecificFonts(){
hasPLSF = true;
AppContext.getAppContext().put(FPAKey, FPAName);
}
/**
* Method invoked by applet to specify a fallback font for logic
* fonts used with in current AppContext
*/
public static void setFallbackFont(String name) {
fallbackFont = name;
AppContext.getAppContext().put(FPAKey, FPAName);
}
//key/value stored in AppContext
private static final String FPAKey = "FontPropertiesAttr";
private static final String FPAName = "PLSFFallback";
private static final String PLSF_PREFIX = "_plsf_";
private String prefixPLSF = null;
private boolean initPLSFFallback = false;
private FontProperties fpropsPLSF = null;
protected static boolean hasPLSF = false;
protected static String fallbackFont = null;
public String getInternalFontName(String orgName){
if (!hasPLSF && fallbackFont == null) {
return orgName;
}
String fpaValue;
Object fpaName;
//if nothing has been defined in AppContext, return the original name
if ((fpaName = AppContext.getAppContext().get(FPAKey)) == null || ! fpaName.equals(FPAName)) {
return orgName;
}
//Return the original name if it's not a logical font
String name = orgName.toLowerCase();
boolean isLogicalFont = false;
for (int i = 0; i < logicalFontNames.length; i++) {
if (name.startsWith(logicalFontNames[i])){
isLogicalFont = true;
break;
}
}
if (!isLogicalFont) {
return orgName;
}
if (prefixPLSF != null) {
return prefixPLSF + orgName;
}
synchronized (this){
if (prefixPLSF != null) {
return prefixPLSF + orgName;
}
if (hasPLSF) {
fpropsPLSF = fprops.applyPreferLocaleSpecificFonts(fprops);
} else {
fpropsPLSF = fprops;
}
initPLSFFallback = true;
prefixPLSF = PLSF_PREFIX;
initCompositeFonts(fpropsPLSF);
return prefixPLSF + orgName;
}
}
}