Mercurial > hg > release > icedtea8-forest-3.0 > jdk
changeset 1756:92607d652d7a
6869705: Missing files of CR6795908, FontManager refactoring
Reviewed-by: prr, igor
author | rkennke |
---|---|
date | Fri, 07 Aug 2009 19:36:28 +0200 |
parents | 64b0c953635d |
children | 11b38980893c |
files | src/share/classes/sun/font/FontAccess.java src/share/classes/sun/font/FontManagerFactory.java src/share/classes/sun/font/FontManagerForSGE.java src/share/classes/sun/font/FontUtilities.java src/share/classes/sun/font/SunFontManager.java src/solaris/classes/sun/awt/X11FontManager.java src/solaris/classes/sun/font/FontConfigManager.java src/windows/classes/sun/awt/Win32FontManager.java |
diffstat | 8 files changed, 5956 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/font/FontAccess.java Fri Aug 07 19:36:28 2009 +0200 @@ -0,0 +1,48 @@ +/* + * Copyright 2008 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.font; + +import java.awt.Font; + +public abstract class FontAccess { + + private static FontAccess access; + public static synchronized void setFontAccess(FontAccess acc) { + if (access != null) { + throw new InternalError("Attempt to set FontAccessor twice"); + } + access = acc; + } + + public static synchronized FontAccess getFontAccess() { + return access; + } + + public abstract Font2D getFont2D(Font f); + public abstract void setFont2D(Font f, Font2DHandle h); + public abstract void setCreatedFont(Font f); + public abstract boolean isCreatedFont(Font f); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/font/FontManagerFactory.java Fri Aug 07 19:36:28 2009 +0200 @@ -0,0 +1,106 @@ +/* + * Copyright 2008 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.font; + +import java.awt.AWTError; +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.awt.Toolkit; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import sun.security.action.GetPropertyAction; + + +/** + * Factory class used to retrieve a valid FontManager instance for the current + * platform. + * + * A default implementation is given for Linux, Solaris and Windows. + * You can alter the behaviour of the {@link #getInstance()} method by setting + * the {@code sun.font.fontmanager} property. For example: + * {@code sun.font.fontmanager=sun.awt.X11FontManager} + */ +public final class FontManagerFactory { + + /** Our singleton instance. */ + private static FontManager instance = null; + + private static final String DEFAULT_CLASS; + static { + if (FontUtilities.isWindows) + DEFAULT_CLASS = "sun.awt.Win32FontManager"; + else + DEFAULT_CLASS = "sun.awt.X11FontManager"; + } + + /** + * Get a valid FontManager implementation for the current platform. + * + * @return a valid FontManager instance for the current platform + */ + public static synchronized FontManager getInstance() { + + if (instance != null) { + return instance; + } + + String fmClassName = AccessController.doPrivileged( + new GetPropertyAction("sun.font.fontmanager", + DEFAULT_CLASS)); + + try { + @SuppressWarnings("unchecked") + ClassLoader cl = (ClassLoader) + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return ClassLoader.getSystemClassLoader(); + } + }); + + @SuppressWarnings("unchecked") + Class fmClass = Class.forName(fmClassName, true, cl); + instance = (FontManager) fmClass.newInstance(); + + } catch (ClassNotFoundException ex) { + InternalError err = new InternalError(); + err.initCause(ex); + throw err; + + } catch (InstantiationException ex) { + InternalError err = new InternalError(); + err.initCause(ex); + throw err; + + } catch (IllegalAccessException ex) { + InternalError err = new InternalError(); + err.initCause(ex); + throw err; + } + + return instance; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/font/FontManagerForSGE.java Fri Aug 07 19:36:28 2009 +0200 @@ -0,0 +1,57 @@ +/* + * Copyright 2008 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.font; + +import java.awt.Font; +import java.util.Locale; +import java.util.TreeMap; + +/** + * This is an extension of the {@link FontManager} interface which has to + * be implemented on systems that want to use SunGraphicsEnvironment. It + * adds a couple of methods that are only required by SGE. Graphics + * implementations that use their own GraphicsEnvironment are not required + * to implement this and can use plain FontManager instead. + */ +public interface FontManagerForSGE extends FontManager { + + /** + * Return an array of created Fonts, or null, if no fonts were created yet. + */ + public Font[] getCreatedFonts(); + + /** + * Similar to getCreatedFonts, but returns a TreeMap of fonts by family name. + */ + public TreeMap<String, String> getCreatedFontFamilyNames(); + + /** + * Returns all fonts installed in this environment. + */ + public Font[] getAllInstalledFonts(); + + public String[] getInstalledFontFamilyNames(Locale requestedLocale); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/font/FontUtilities.java Fri Aug 07 19:36:28 2009 +0200 @@ -0,0 +1,486 @@ +/* + * Copyright 2008 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.font; + +import java.awt.Font; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.security.AccessController; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.plaf.FontUIResource; + +import sun.security.action.GetPropertyAction; + +/** + * A collection of utility methods. + */ +public final class FontUtilities { + + public static final boolean isSolaris; + + public static final boolean isLinux; + + public static final boolean isSolaris8; + + public static final boolean isSolaris9; + + public static final boolean isOpenSolaris; + + public static final boolean useT2K; + + public static final boolean isWindows; + + public static final boolean isOpenJDK; + + static final String LUCIDA_FILE_NAME = "LucidaSansRegular.ttf"; + + // This static initializer block figures out the OS constants. + static { + + String osName = AccessController.doPrivileged( + new GetPropertyAction("os.name", "unknownOS")); + isSolaris = osName.startsWith("SunOS"); + + isLinux = osName.startsWith("Linux"); + + String t2kStr = AccessController.doPrivileged( + new GetPropertyAction("sun.java2d.font.scaler")); + if (t2kStr != null) { + useT2K = "t2k".equals(t2kStr); + } else { + useT2K = false; + } + if (isSolaris) { + String version = AccessController.doPrivileged( + new GetPropertyAction("os.version", "0.0")); + isSolaris8 = version.startsWith("5.8"); + isSolaris9 = version.startsWith("5.9"); + float ver = Float.parseFloat(version); + if (ver > 5.10f) { + File f = new File("/etc/release"); + String line = null; + try { + FileInputStream fis = new FileInputStream(f); + InputStreamReader isr = new InputStreamReader( + fis, "ISO-8859-1"); + BufferedReader br = new BufferedReader(isr); + line = br.readLine(); + fis.close(); + } catch (Exception ex) { + // Nothing to do here. + } + if (line != null && line.indexOf("OpenSolaris") >= 0) { + isOpenSolaris = true; + } else { + isOpenSolaris = false; + } + } else { + isOpenSolaris= false; + } + } else { + isSolaris8 = false; + isSolaris9 = false; + isOpenSolaris = false; + } + isWindows = osName.startsWith("Windows"); + String jreLibDirName = AccessController.doPrivileged( + new GetPropertyAction("java.home","")) + File.separator + "lib"; + String jreFontDirName = jreLibDirName + File.separator + "fonts"; + File lucidaFile = + new File(jreFontDirName + File.separator + LUCIDA_FILE_NAME); + isOpenJDK = !lucidaFile.exists(); + } + + /** + * Referenced by code in the JDK which wants to test for the + * minimum char code for which layout may be required. + * Note that even basic latin text can benefit from ligatures, + * eg "ffi" but we presently apply those only if explicitly + * requested with TextAttribute.LIGATURES_ON. + * The value here indicates the lowest char code for which failing + * to invoke layout would prevent acceptable rendering. + */ + public static final int MIN_LAYOUT_CHARCODE = 0x0300; + + /** + * Referenced by code in the JDK which wants to test for the + * maximum char code for which layout may be required. + * Note this does not account for supplementary characters + * where the caller interprets 'layout' to mean any case where + * one 'char' (ie the java type char) does not map to one glyph + */ + public static final int MAX_LAYOUT_CHARCODE = 0x206F; + + private static boolean debugFonts = false; + private static Logger logger = null; + private static boolean logging; + + static { + + 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); + } + } + + if (debugFonts) { + logger = Logger.getLogger("sun.java2d", null); + logging = logger.getLevel() != Level.OFF; + } + + } + + /** + * Calls the private getFont2D() method in java.awt.Font objects. + * + * @param font the font object to call + * + * @return the Font2D object returned by Font.getFont2D() + */ + public static Font2D getFont2D(Font font) { + return FontAccess.getFontAccess().getFont2D(font); + } + + /** + * If there is anything in the text which triggers a case + * where char->glyph does not map 1:1 in straightforward + * left->right ordering, then this method returns true. + * Scripts which might require it but are not treated as such + * due to JDK implementations will not return true. + * ie a 'true' return is an indication of the treatment by + * the implementation. + * Whether supplementary characters should be considered is dependent + * on the needs of the caller. Since this method accepts the 'char' type + * then such chars are always represented by a pair. From a rendering + * perspective these will all (in the cases I know of) still be one + * unicode character -> one glyph. But if a caller is using this to + * discover any case where it cannot make naive assumptions about + * the number of chars, and how to index through them, then it may + * need the option to have a 'true' return in such a case. + */ + public static boolean isComplexText(char [] chs, int start, int limit) { + + for (int i = start; i < limit; i++) { + if (chs[i] < MIN_LAYOUT_CHARCODE) { + continue; + } + else if (isNonSimpleChar(chs[i])) { + return true; + } + } + return false; + } + + /* This is almost the same as the method above, except it takes a + * char which means it may include undecoded surrogate pairs. + * The distinction is made so that code which needs to identify all + * cases in which we do not have a simple mapping from + * char->unicode character->glyph can be be identified. + * For example measurement cannot simply sum advances of 'chars', + * the caret in editable text cannot advance one 'char' at a time, etc. + * These callers really are asking for more than whether 'layout' + * needs to be run, they need to know if they can assume 1->1 + * char->glyph mapping. + */ + public static boolean isNonSimpleChar(char ch) { + return + isComplexCharCode(ch) || + (ch >= CharToGlyphMapper.HI_SURROGATE_START && + ch <= CharToGlyphMapper.LO_SURROGATE_END); + } + + /* If the character code falls into any of a number of unicode ranges + * where we know that simple left->right layout mapping chars to glyphs + * 1:1 and accumulating advances is going to produce incorrect results, + * we want to know this so the caller can use a more intelligent layout + * approach. A caller who cares about optimum performance may want to + * check the first case and skip the method call if its in that range. + * Although there's a lot of tests in here, knowing you can skip + * CTL saves a great deal more. The rest of the checks are ordered + * so that rather than checking explicitly if (>= start & <= end) + * which would mean all ranges would need to be checked so be sure + * CTL is not needed, the method returns as soon as it recognises + * the code point is outside of a CTL ranges. + * NOTE: Since this method accepts an 'int' it is asssumed to properly + * represent a CHARACTER. ie it assumes the caller has already + * converted surrogate pairs into supplementary characters, and so + * can handle this case and doesn't need to be told such a case is + * 'complex'. + */ + public static boolean isComplexCharCode(int code) { + + if (code < MIN_LAYOUT_CHARCODE || code > MAX_LAYOUT_CHARCODE) { + return false; + } + else if (code <= 0x036f) { + // Trigger layout for combining diacriticals 0x0300->0x036f + return true; + } + else if (code < 0x0590) { + // No automatic layout for Greek, Cyrillic, Armenian. + return false; + } + else if (code <= 0x06ff) { + // Hebrew 0590 - 05ff + // Arabic 0600 - 06ff + return true; + } + else if (code < 0x0900) { + return false; // Syriac and Thaana + } + else if (code <= 0x0e7f) { + // if Indic, assume shaping for conjuncts, reordering: + // 0900 - 097F Devanagari + // 0980 - 09FF Bengali + // 0A00 - 0A7F Gurmukhi + // 0A80 - 0AFF Gujarati + // 0B00 - 0B7F Oriya + // 0B80 - 0BFF Tamil + // 0C00 - 0C7F Telugu + // 0C80 - 0CFF Kannada + // 0D00 - 0D7F Malayalam + // 0D80 - 0DFF Sinhala + // 0E00 - 0E7F if Thai, assume shaping for vowel, tone marks + return true; + } + else if (code < 0x1780) { + return false; + } + else if (code <= 0x17ff) { // 1780 - 17FF Khmer + return true; + } + else if (code < 0x200c) { + return false; + } + else if (code <= 0x200d) { // zwj or zwnj + return true; + } + else if (code >= 0x202a && code <= 0x202e) { // directional control + return true; + } + else if (code >= 0x206a && code <= 0x206f) { // directional control + return true; + } + return false; + } + + public static Logger getLogger() { + return logger; + } + + public static boolean isLogging() { + return logging; + } + + public static boolean debugFonts() { + return debugFonts; + } + + + // The following methods are used by Swing. + + /* Revise the implementation to in fact mean "font is a composite font. + * This ensures that Swing components will always benefit from the + * fall back fonts + */ + public static boolean fontSupportsDefaultEncoding(Font font) { + return getFont2D(font) instanceof CompositeFont; + } + + /** + * This method is provided for internal and exclusive use by Swing. + * + * It may be used in conjunction with fontSupportsDefaultEncoding(Font) + * In the event that a desktop properties font doesn't directly + * support the default encoding, (ie because the host OS supports + * adding support for the current locale automatically for native apps), + * then Swing calls this method to get a font which uses the specified + * font for the code points it covers, but also supports this locale + * just as the standard composite fonts do. + * Note: this will over-ride any setting where an application + * specifies it prefers locale specific composite fonts. + * The logic for this, is that this method is used only where the user or + * application has specified that the native L&F be used, and that + * we should honour that request to use the same font as native apps use. + * + * The behaviour of this method is to construct a new composite + * Font object that uses the specified physical font as its first + * component, and adds all the components of "dialog" as fall back + * components. + * The method currently assumes that only the size and style attributes + * are set on the specified font. It doesn't copy the font transform or + * other attributes because they aren't set on a font created from + * the desktop. This will need to be fixed if use is broadened. + * + * Operations such as Font.deriveFont will work properly on the + * font returned by this method for deriving a different point size. + * Additionally it tries to support a different style by calling + * getNewComposite() below. That also supports replacing slot zero + * with a different physical font but that is expected to be "rare". + * Deriving with a different style is needed because its been shown + * that some applications try to do this for Swing FontUIResources. + * Also operations such as new Font(font.getFontName(..), Font.PLAIN, 14); + * will NOT yield the same result, as the new underlying CompositeFont + * cannot be "looked up" in the font registry. + * This returns a FontUIResource as that is the Font sub-class needed + * by Swing. + * Suggested usage is something like : + * FontUIResource fuir; + * Font desktopFont = getDesktopFont(..); + * // NOTE even if fontSupportsDefaultEncoding returns true because + * // you get Tahoma and are running in an English locale, you may + * // still want to just call getCompositeFontUIResource() anyway + * // as only then will you get fallback fonts - eg for CJK. + * if (FontManager.fontSupportsDefaultEncoding(desktopFont)) { + * fuir = new FontUIResource(..); + * } else { + * fuir = FontManager.getCompositeFontUIResource(desktopFont); + * } + * return fuir; + */ + public static FontUIResource getCompositeFontUIResource(Font font) { + + FontUIResource fuir = + new FontUIResource(font.getName(),font.getStyle(),font.getSize()); + Font2D font2D = FontUtilities.getFont2D(font); + + if (!(font2D instanceof PhysicalFont)) { + /* Swing should only be calling this when a font is obtained + * from desktop properties, so should generally be a physical font, + * an exception might be for names like "MS Serif" which are + * automatically mapped to "Serif", so there's no need to do + * anything special in that case. But note that suggested usage + * is first to call fontSupportsDefaultEncoding(Font) and this + * method should not be called if that were to return true. + */ + return fuir; + } + + FontManager fm = FontManagerFactory.getInstance(); + CompositeFont dialog2D = + (CompositeFont) fm.findFont2D("dialog", font.getStyle(), FontManager.NO_FALLBACK); + if (dialog2D == null) { /* shouldn't happen */ + return fuir; + } + PhysicalFont physicalFont = (PhysicalFont)font2D; + CompositeFont compFont = new CompositeFont(physicalFont, dialog2D); + FontAccess.getFontAccess().setFont2D(fuir, compFont.handle); + /* marking this as a created font is needed as only created fonts + * copy their creator's handles. + */ + FontAccess.getFontAccess().setCreatedFont(fuir); + return fuir; + } + + /* A small "map" from GTK/fontconfig names to the equivalent JDK + * logical font name. + */ + private static final String[][] nameMap = { + {"sans", "sansserif"}, + {"sans-serif", "sansserif"}, + {"serif", "serif"}, + {"monospace", "monospaced"} + }; + + public static String mapFcName(String name) { + for (int i = 0; i < nameMap.length; i++) { + if (name.equals(nameMap[i][0])) { + return nameMap[i][1]; + } + } + return null; + } + + + /* This is called by Swing passing in a fontconfig family name + * such as "sans". In return Swing gets a FontUIResource instance + * that has queried fontconfig to resolve the font(s) used for this. + * Fontconfig will if asked return a list of fonts to give the largest + * possible code point coverage. + * For now we use only the first font returned by fontconfig, and + * back it up with the most closely matching JDK logical font. + * Essentially this means pre-pending what we return now with fontconfig's + * preferred physical font. This could lead to some duplication in cases, + * if we already included that font later. We probably should remove such + * duplicates, but it is not a significant problem. It can be addressed + * later as part of creating a Composite which uses more of the + * same fonts as fontconfig. At that time we also should pay more + * attention to the special rendering instructions fontconfig returns, + * such as whether we should prefer embedded bitmaps over antialiasing. + * There's no way to express that via a Font at present. + */ + public static FontUIResource getFontConfigFUIR(String fcFamily, + int style, int size) { + + String mapped = mapFcName(fcFamily); + if (mapped == null) { + mapped = "sansserif"; + } + + FontUIResource fuir; + FontManager fm = FontManagerFactory.getInstance(); + if (fm instanceof SunFontManager) { + SunFontManager sfm = (SunFontManager) fm; + fuir = sfm.getFontConfigFUIR(mapped, style, size); + } else { + fuir = new FontUIResource(mapped, style, size); + } + return fuir; + } + + + /** + * Used by windows printing to assess if a font is likely to + * be layout compatible with JDK + * TrueType fonts should be, but if they have no GPOS table, + * but do have a GSUB table, then they are probably older + * fonts GDI handles differently. + */ + public static boolean textLayoutIsCompatible(Font font) { + + Font2D font2D = getFont2D(font); + if (font2D instanceof TrueTypeFont) { + TrueTypeFont ttf = (TrueTypeFont) font2D; + return + ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null || + ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null; + } else { + return false; + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/sun/font/SunFontManager.java Fri Aug 07 19:36:28 2009 +0200 @@ -0,0 +1,3675 @@ +/* + * Copyright 2008 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.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 java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.plaf.FontUIResource; +import sun.awt.AppContext; +import sun.awt.FontConfiguration; +import sun.awt.SunToolkit; +import sun.java2d.FontSupport; + +/** + * 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)); + } + } + } + + 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); + 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. + */ + private static final int CHANNELPOOLSIZE = 20; + private int lastPoolIndex = 0; + private FileFont fontFileCache[] = new FileFont[CHANNELPOOLSIZE]; + + /* 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. + */ + private ConcurrentHashMap<String, Font2D> + fullNameToFont = new ConcurrentHashMap<String, Font2D>(); + + /* 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 registeredFontFiles = new HashSet(); + + private ArrayList 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() { + + 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() { + 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()) { + Logger 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 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 = (Font2D) + 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 + */ + private PhysicalFont addToFontList(PhysicalFont f, int rank) { + + 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 if (family.getRank() >= rank) { + 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 if (family.getRank() >= rank) { + 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); + 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 i = physicalFonts.values().iterator(); + if (i.hasNext()) { + defaultPhysicalFont = (PhysicalFont)i.next(); + } else { + throw new Error("Probable fatal error:No fonts found."); + } + } + } + return defaultPhysicalFont; + } + + public CompositeFont getDefaultLogicalFont(int style) { + return (CompositeFont)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() { + 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. + */ + 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 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++, true); + // 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) { + } + } + } + + 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() { + Logger 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 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 = (String[])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) { + ConcurrentHashMap<String, Font2D> altNameCache = + (ConcurrentHashMap<String, Font2D>) + AppContext.getAppContext().get(CompositeFont.class); + if (altNameCache != null) { + font = (Font2D)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) { + /* 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(); + familyTable = + (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); + nameTable = + (Hashtable<String,Font2D>)appContext.get(regFullNameKey); + } + + 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); + } + + public abstract String getFontPath(boolean noType1Fonts); + + private Thread fileCloser = null; + 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() { + 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() { + 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; + } + + }); + } + }; + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + /* The thread must be a member of a thread group + * which will not get GCed before VM exit. + * Make its parent the top-level thread group. + */ + ThreadGroup tg = + Thread.currentThread().getThreadGroup(); + for (ThreadGroup tgn = tg; + tgn != null; + tg = tgn, tgn = tg.getParent()); + fileCloser = new Thread(tg, fileCloserRunnable); + 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 = + (Map.Entry[])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 { + mapEntries[i].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) enviroment 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() { + public Object run() { + SecurityManager sm = System.getSecurityManager(); + return new Boolean + (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.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() { + + /* 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 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(); + familyTable = + (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); + fullNameTable = + (Hashtable<String,Font2D>)appContext.get(regFullNameKey); + 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 = (String[])(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(); + familyTable = + (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); + } 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(); + nameTable = + (Hashtable<String,Font2D>)appContext.get(regFullNameKey); + } 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) { + String path = getFontPath(true); + StringTokenizer parser = + new StringTokenizer(path, File.pathSeparator); + ArrayList<String> pathList = new ArrayList<String>(); + try { + while (parser.hasMoreTokens()) { + pathList.add(parser.nextToken()); + } + } catch (NoSuchElementException e) { + } + return pathList.toArray(new String[0]); + } + + /** + * Returns an array of two strings. The first element is the + * name of the font. The second element is the file name. + */ + public 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(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(..) + */ + protected 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 (lucidaFontName) { + if (FontUtilities.debugFonts()) { + Thread.dumpStack(); + FontUtilities.getLogger() + .info("SunGraphicsEnvironment.loadFonts() called"); + } + 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 (! 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); + } + + private void registerFontsInDir(String dirName, boolean useJavaRasterizer, + 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 (lucidaFontName) { + if (FontUtilities.debugFonts()) { + Thread.dumpStack(); + FontUtilities.getLogger().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 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 + || 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 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 = (Font2D)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); + } + } + } + + 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 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; + } + }); + } + + /* 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() { + 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); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/solaris/classes/sun/awt/X11FontManager.java Fri Aug 07 19:36:28 2009 +0200 @@ -0,0 +1,850 @@ +package sun.awt; + +import java.awt.GraphicsEnvironment; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.StreamTokenizer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.logging.Logger; + +import javax.swing.plaf.FontUIResource; +import sun.awt.motif.MFontConfiguration; +import sun.font.CompositeFont; +import sun.font.FontManager; +import sun.font.SunFontManager; +import sun.font.FontConfigManager; +import sun.font.FcFontConfiguration; +import sun.font.FontAccess; +import sun.font.FontUtilities; +import sun.font.NativeFont; + +/** + * The X11 implementation of {@link FontManager}. + */ +public class X11FontManager extends SunFontManager { + + // constants identifying XLFD and font ID fields + private static final int FOUNDRY_FIELD = 1; + private static final int FAMILY_NAME_FIELD = 2; + private static final int WEIGHT_NAME_FIELD = 3; + private static final int SLANT_FIELD = 4; + private static final int SETWIDTH_NAME_FIELD = 5; + private static final int ADD_STYLE_NAME_FIELD = 6; + private static final int PIXEL_SIZE_FIELD = 7; + private static final int POINT_SIZE_FIELD = 8; + private static final int RESOLUTION_X_FIELD = 9; + private static final int RESOLUTION_Y_FIELD = 10; + private static final int SPACING_FIELD = 11; + private static final int AVERAGE_WIDTH_FIELD = 12; + private static final int CHARSET_REGISTRY_FIELD = 13; + private static final int CHARSET_ENCODING_FIELD = 14; + + /* + * fontNameMap is a map from a fontID (which is a substring of an XLFD like + * "-monotype-arial-bold-r-normal-iso8859-7") + * to font file path like + * /usr/openwin/lib/locale/iso_8859_7/X11/fonts/TrueType/ArialBoldItalic.ttf + * It's used in a couple of methods like + * getFileNameFomPlatformName(..) to help locate the font file. + * We use this substring of a full XLFD because the font configuration files + * define the XLFDs in a way that's easier to make into a request. + * E.g., the -0-0-0-0-p-0- reported by X is -*-%d-*-*-p-*- in the font + * configuration files. We need to remove that part for comparisons. + */ + private static Map fontNameMap = new HashMap(); + + /* + * xlfdMap is a map from a platform path like + * /usr/openwin/lib/locale/ja/X11/fonts/TT/HG-GothicB.ttf to an XLFD like + * "-ricoh-hg gothic b-medium-r-normal--0-0-0-0-m-0-jisx0201.1976-0" + * Because there may be multiple native names, because the font is used + * to support multiple X encodings for example, the value of an entry in + * this map is always a vector where we store all the native names. + * For fonts which we don't understand the key isn't a pathname, its + * the full XLFD string like :- + * "-ricoh-hg gothic b-medium-r-normal--0-0-0-0-m-0-jisx0201.1976-0" + */ + private static Map xlfdMap = new HashMap(); + + /* xFontDirsMap is also a map from a font ID to a font filepath. + * The difference from fontNameMap is just that it does not have + * resolved symbolic links. Normally this is not interesting except + * that we need to know the directory in which a font was found to + * add it to the X font server path, since although the files may + * be linked, the fonts.dir is different and specific to the encoding + * handled by that directory. This map is nulled out after use to free + * heap space. If the optimal path is taken, such that all fonts in + * font configuration files are referenced by filename, then the font + * dir can be directly derived as its parent directory. + * If a font is used by two XLFDs, each corresponding to a different + * X11 font directory, then precautions must be taken to include both + * directories. + */ + private static Map xFontDirsMap; + + /* + * This is the set of font directories needed to be on the X font path + * to enable AWT heavyweights to find all of the font configuration fonts. + * It is populated by : + * - awtfontpath entries in the fontconfig.properties + * - parent directories of "core" fonts used in the fontconfig.properties + * - looking up font dirs in the xFontDirsMap where the key is a fontID + * (cut down version of the XLFD read from the font configuration file). + * This set is nulled out after use to free heap space. + */ + private static HashSet<String> fontConfigDirs = null; + + /* These maps are used on Linux where we reference the Lucida oblique + * fonts in fontconfig files even though they aren't in the standard + * font directory. This explicitly remaps the XLFDs for these to the + * correct base font. This is needed to prevent composite fonts from + * defaulting to the Lucida Sans which is a bad substitute for the + * monospaced Lucida Sans Typewriter. Also these maps prevent the + * JRE from doing wasted work at start up. + */ + HashMap<String, String> oblmap = null; + + + /* + * Used to eliminate redundant work. When a font directory is + * registered it added to this list. Subsequent registrations for the + * same directory can then be skipped by checking this Map. + * Access to this map is not synchronised here since creation + * of the singleton GE instance is already synchronised and that is + * the only code path that accesses this map. + */ + private static HashMap registeredDirs = new HashMap(); + + /* Array of directories to be added to the X11 font path. + * Used by static method called from Toolkits which use X11 fonts. + * Specifically this means MToolkit + */ + private static String[] fontdirs = null; + + private static String[] defaultPlatformFont = null; + + private FontConfigManager fcManager = null; + + public static X11FontManager getInstance() { + return (X11FontManager) SunFontManager.getInstance(); + } + + /** + * Takes family name property in the following format: + * "-linotype-helvetica-medium-r-normal-sans-*-%d-*-*-p-*-iso8859-1" + * and returns the name of the corresponding physical font. + * This code is used to resolve font configuration fonts, and expects + * only to get called for these fonts. + */ + @Override + public String getFileNameFromPlatformName(String platName) { + + /* If the FontConfig file doesn't use xlfds, or its + * FcFontConfiguration, this may be already a file name. + */ + if (platName.startsWith("/")) { + return platName; + } + + String fileName = null; + String fontID = specificFontIDForName(platName); + + /* If the font filename has been explicitly assigned in the + * font configuration file, use it. This avoids accessing + * the wrong fonts on Linux, where different fonts (some + * of which may not be usable by 2D) may share the same + * specific font ID. It may also speed up the lookup. + */ + fileName = super.getFileNameFromPlatformName(platName); + if (fileName != null) { + if (isHeadless() && fileName.startsWith("-")) { + /* if it's headless, no xlfd should be used */ + return null; + } + if (fileName.startsWith("/")) { + /* If a path is assigned in the font configuration file, + * it is required that the config file also specify using the + * new awtfontpath key the X11 font directories + * which must be added to the X11 font path to support + * AWT access to that font. For that reason we no longer + * have code here to add the parent directory to the list + * of font config dirs, since the parent directory may not + * be sufficient if fonts are symbolically linked to a + * different directory. + * + * Add this XLFD (platform name) to the list of known + * ones for this file. + */ + Vector xVal = (Vector) xlfdMap.get(fileName); + if (xVal == null) { + /* Try to be robust on Linux distros which move fonts + * around by verifying that the fileName represents a + * file that exists. If it doesn't, set it to null + * to trigger a search. + */ + if (getFontConfiguration().needToSearchForFile(fileName)) { + fileName = null; + } + if (fileName != null) { + xVal = new Vector(); + xVal.add(platName); + xlfdMap.put(fileName, xVal); + } + } else { + if (!xVal.contains(platName)) { + xVal.add(platName); + } + } + } + if (fileName != null) { + fontNameMap.put(fontID, fileName); + return fileName; + } + } + + if (fontID != null) { + fileName = (String)fontNameMap.get(fontID); + /* On Linux check for the Lucida Oblique fonts */ + if (fileName == null && FontUtilities.isLinux && !isOpenJDK()) { + if (oblmap == null) { + initObliqueLucidaFontMap(); + } + String oblkey = getObliqueLucidaFontID(fontID); + if (oblkey != null) { + fileName = oblmap.get(oblkey); + } + } + if (fontPath == null && + (fileName == null || !fileName.startsWith("/"))) { + if (FontUtilities.debugFonts()) { + FontUtilities.getLogger() + .warning("** Registering all font paths because " + + "can't find file for " + platName); + } + fontPath = getPlatformFontPath(noType1Font); + registerFontDirs(fontPath); + if (FontUtilities.debugFonts()) { + FontUtilities.getLogger() + .warning("** Finished registering all font paths"); + } + fileName = (String)fontNameMap.get(fontID); + } + if (fileName == null && !isHeadless()) { + /* Query X11 directly to see if this font is available + * as a native font. + */ + fileName = getX11FontName(platName); + } + if (fileName == null) { + fontID = switchFontIDForName(platName); + fileName = (String)fontNameMap.get(fontID); + } + if (fileName != null) { + fontNameMap.put(fontID, fileName); + } + } + return fileName; + } + + @Override + protected String[] getNativeNames(String fontFileName, + String platformName) { + Vector nativeNames; + if ((nativeNames=(Vector)xlfdMap.get(fontFileName))==null) { + if (platformName == null) { + return null; + } else { + /* back-stop so that at least the name used in the + * font configuration file is known as a native name + */ + String []natNames = new String[1]; + natNames[0] = platformName; + return natNames; + } + } else { + int len = nativeNames.size(); + return (String[])nativeNames.toArray(new String[len]); + } + } + + /* NOTE: this method needs to be executed in a privileged context. + * The superclass constructor which is the primary caller of + * this method executes entirely in such a context. Additionally + * the loadFonts() method does too. So all should be well. + + */ + @Override + protected void registerFontDir(String path) { + /* fonts.dir file format looks like :- + * 47 + * Arial.ttf -monotype-arial-regular-r-normal--0-0-0-0-p-0-iso8859-1 + * Arial-Bold.ttf -monotype-arial-bold-r-normal--0-0-0-0-p-0-iso8859-1 + * ... + */ + if (FontUtilities.debugFonts()) { + FontUtilities.getLogger().info("ParseFontDir " + path); + } + File fontsDotDir = new File(path + File.separator + "fonts.dir"); + FileReader fr = null; + try { + if (fontsDotDir.canRead()) { + fr = new FileReader(fontsDotDir); + BufferedReader br = new BufferedReader(fr, 8192); + StreamTokenizer st = new StreamTokenizer(br); + st.eolIsSignificant(true); + int ttype = st.nextToken(); + if (ttype == StreamTokenizer.TT_NUMBER) { + int numEntries = (int)st.nval; + ttype = st.nextToken(); + if (ttype == StreamTokenizer.TT_EOL) { + st.resetSyntax(); + st.wordChars(32, 127); + st.wordChars(128 + 32, 255); + st.whitespaceChars(0, 31); + + for (int i=0; i < numEntries; i++) { + ttype = st.nextToken(); + if (ttype == StreamTokenizer.TT_EOF) { + break; + } + if (ttype != StreamTokenizer.TT_WORD) { + break; + } + int breakPos = st.sval.indexOf(' '); + if (breakPos <= 0) { + /* On TurboLinux 8.0 a fonts.dir file had + * a line with integer value "24" which + * appeared to be the number of remaining + * entries in the file. This didn't add to + * the value on the first line of the file. + * Seemed like XFree86 didn't like this line + * much either. It failed to parse the file. + * Ignore lines like this completely, and + * don't let them count as an entry. + */ + numEntries++; + ttype = st.nextToken(); + if (ttype != StreamTokenizer.TT_EOL) { + break; + } + + continue; + } + if (st.sval.charAt(0) == '!') { + /* TurboLinux 8.0 comment line: ignore. + * can't use st.commentChar('!') to just + * skip because this line mustn't count + * against numEntries. + */ + numEntries++; + ttype = st.nextToken(); + if (ttype != StreamTokenizer.TT_EOL) { + break; + } + continue; + } + String fileName = st.sval.substring(0, breakPos); + /* TurboLinux 8.0 uses some additional syntax to + * indicate algorithmic styling values. + * Ignore ':' separated files at the beginning + * of the fileName + */ + int lastColon = fileName.lastIndexOf(':'); + if (lastColon > 0) { + if (lastColon+1 >= fileName.length()) { + continue; + } + fileName = fileName.substring(lastColon+1); + } + String fontPart = st.sval.substring(breakPos+1); + String fontID = specificFontIDForName(fontPart); + String sVal = (String) fontNameMap.get(fontID); + + if (FontUtilities.debugFonts()) { + Logger logger = FontUtilities.getLogger(); + logger.info("file=" + fileName + + " xlfd=" + fontPart); + logger.info("fontID=" + fontID + + " sVal=" + sVal); + } + String fullPath = null; + try { + File file = new File(path,fileName); + /* we may have a resolved symbolic link + * this becomes important for an xlfd we + * still need to know the location it was + * found to update the X server font path + * for use by AWT heavyweights - and when 2D + * wants to use the native rasteriser. + */ + if (xFontDirsMap == null) { + xFontDirsMap = new HashMap(); + } + xFontDirsMap.put(fontID, path); + fullPath = file.getCanonicalPath(); + } catch (IOException e) { + fullPath = path + File.separator + fileName; + } + Vector xVal = (Vector) xlfdMap.get(fullPath); + if (FontUtilities.debugFonts()) { + FontUtilities.getLogger() + .info("fullPath=" + fullPath + + " xVal=" + xVal); + } + if ((xVal == null || !xVal.contains(fontPart)) && + (sVal == null) || !sVal.startsWith("/")) { + if (FontUtilities.debugFonts()) { + FontUtilities.getLogger() + .info("Map fontID:"+fontID + + "to file:" + fullPath); + } + fontNameMap.put(fontID, fullPath); + if (xVal == null) { + xVal = new Vector(); + xlfdMap.put (fullPath, xVal); + } + xVal.add(fontPart); + } + + ttype = st.nextToken(); + if (ttype != StreamTokenizer.TT_EOL) { + break; + } + } + } + } + fr.close(); + } + } catch (IOException ioe1) { + } finally { + if (fr != null) { + try { + fr.close(); + } catch (IOException ioe2) { + } + } + } + } + + @Override + public void loadFonts() { + super.loadFonts(); + /* These maps are greatly expanded during a loadFonts but + * can be reset to their initial state afterwards. + * Since preferLocaleFonts() and preferProportionalFonts() will + * trigger a partial repopulating from the FontConfiguration + * it has to be the inital (empty) state for the latter two, not + * simply nulling out. + * xFontDirsMap is a special case in that the implementation + * will typically not ever need to initialise it so it can be null. + */ + xFontDirsMap = null; + xlfdMap = new HashMap(1); + fontNameMap = new HashMap(1); + } + + private String getObliqueLucidaFontID(String fontID) { + if (fontID.startsWith("-lucidasans-medium-i-normal") || + fontID.startsWith("-lucidasans-bold-i-normal") || + fontID.startsWith("-lucidatypewriter-medium-i-normal") || + fontID.startsWith("-lucidatypewriter-bold-i-normal")) { + return fontID.substring(0, fontID.indexOf("-i-")); + } else { + return null; + } + } + + private static String getX11FontName(String platName) { + String xlfd = platName.replaceAll("%d", "*"); + if (NativeFont.fontExists(xlfd)) { + return xlfd; + } else { + return null; + } + } + + private void initObliqueLucidaFontMap() { + oblmap = new HashMap<String, String>(); + oblmap.put("-lucidasans-medium", + jreLibDirName+"/fonts/LucidaSansRegular.ttf"); + oblmap.put("-lucidasans-bold", + jreLibDirName+"/fonts/LucidaSansDemiBold.ttf"); + oblmap.put("-lucidatypewriter-medium", + jreLibDirName+"/fonts/LucidaTypewriterRegular.ttf"); + oblmap.put("-lucidatypewriter-bold", + jreLibDirName+"/fonts/LucidaTypewriterBold.ttf"); + } + + private boolean isHeadless() { + GraphicsEnvironment ge = + GraphicsEnvironment.getLocalGraphicsEnvironment(); + return GraphicsEnvironment.isHeadless(); + } + + private String specificFontIDForName(String name) { + + int[] hPos = new int[14]; + int hyphenCnt = 1; + int pos = 1; + + while (pos != -1 && hyphenCnt < 14) { + pos = name.indexOf('-', pos); + if (pos != -1) { + hPos[hyphenCnt++] = pos; + pos++; + } + } + + if (hyphenCnt != 14) { + if (FontUtilities.debugFonts()) { + FontUtilities.getLogger() + .severe("Font Configuration Font ID is malformed:" + name); + } + return name; // what else can we do? + } + + StringBuffer sb = + new StringBuffer(name.substring(hPos[FAMILY_NAME_FIELD-1], + hPos[SETWIDTH_NAME_FIELD])); + sb.append(name.substring(hPos[CHARSET_REGISTRY_FIELD-1])); + String retval = sb.toString().toLowerCase (Locale.ENGLISH); + return retval; + } + + private String switchFontIDForName(String name) { + + int[] hPos = new int[14]; + int hyphenCnt = 1; + int pos = 1; + + while (pos != -1 && hyphenCnt < 14) { + pos = name.indexOf('-', pos); + if (pos != -1) { + hPos[hyphenCnt++] = pos; + pos++; + } + } + + if (hyphenCnt != 14) { + if (FontUtilities.debugFonts()) { + FontUtilities.getLogger() + .severe("Font Configuration Font ID is malformed:" + name); + } + return name; // what else can we do? + } + + String slant = name.substring(hPos[SLANT_FIELD-1]+1, + hPos[SLANT_FIELD]); + String family = name.substring(hPos[FAMILY_NAME_FIELD-1]+1, + hPos[FAMILY_NAME_FIELD]); + String registry = name.substring(hPos[CHARSET_REGISTRY_FIELD-1]+1, + hPos[CHARSET_REGISTRY_FIELD]); + String encoding = name.substring(hPos[CHARSET_ENCODING_FIELD-1]+1); + + if (slant.equals("i")) { + slant = "o"; + } else if (slant.equals("o")) { + slant = "i"; + } + // workaround for #4471000 + if (family.equals("itc zapfdingbats") + && registry.equals("sun") + && encoding.equals("fontspecific")){ + registry = "adobe"; + } + StringBuffer sb = + new StringBuffer(name.substring(hPos[FAMILY_NAME_FIELD-1], + hPos[SLANT_FIELD-1]+1)); + sb.append(slant); + sb.append(name.substring(hPos[SLANT_FIELD], + hPos[SETWIDTH_NAME_FIELD]+1)); + sb.append(registry); + sb.append(name.substring(hPos[CHARSET_ENCODING_FIELD-1])); + String retval = sb.toString().toLowerCase (Locale.ENGLISH); + return retval; + } + + /** + * Returns the face name for the given XLFD. + */ + public String getFileNameFromXLFD(String name) { + String fileName = null; + String fontID = specificFontIDForName(name); + if (fontID != null) { + fileName = (String)fontNameMap.get(fontID); + if (fileName == null) { + fontID = switchFontIDForName(name); + fileName = (String)fontNameMap.get(fontID); + } + if (fileName == null) { + fileName = getDefaultFontFile(); + } + } + return fileName; + } + + /* Register just the paths, (it doesn't register the fonts). + * If a font configuration file has specified a baseFontPath + * fontPath is just those directories, unless on usage we + * find it doesn't contain what we need for the logical fonts. + * Otherwise, 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. + * + * Registering the directories can now be avoided in the + * font configuration initialisation when filename entries + * exist in the font configuration file for all fonts. + * (Perhaps a little confusingly a filename entry is + * actually keyed using the XLFD used in the font entries, + * and it maps *to* a real filename). + * In the event any are missing, registration of all + * directories will be invoked to find the real files. + * + * But registering the directory performed other + * functions such as filling in the map of all native names + * for the font. So when this method isn't invoked, they still + * must be found. This is mitigated by getNativeNames now + * being able to return at least the platform name, but mostly + * by ensuring that when a filename key is found, that + * xlfd key is stored as one of the set of platform names + * for the font. Its a set because typical font configuration + * files reference the same CJK font files using multiple + * X11 encodings. For the code that adds this to the map + * see X11GE.getFileNameFromPlatformName(..) + * If you don't get all of these then some code points may + * not use the Xserver, and will not get the PCF bitmaps + * that are available for some point sizes. + * So, in the event that there is such a problem, + * unconditionally making this call may be necessary, at + * some cost to JRE start-up + */ + @Override + protected void registerFontDirs(String pathName) { + + StringTokenizer parser = new StringTokenizer(pathName, + File.pathSeparator); + try { + while (parser.hasMoreTokens()) { + String dirPath = parser.nextToken(); + if (dirPath != null && !registeredDirs.containsKey(dirPath)) { + registeredDirs.put(dirPath, null); + registerFontDir(dirPath); + } + } + } catch (NoSuchElementException e) { + } + } + + // An X font spec (xlfd) includes an encoding. The same TrueType font file + // may be referenced from different X font directories in font.dir files + // to support use in multiple encodings by X apps. + // So for the purposes of font configuration logical fonts where AWT + // heavyweights need to access the font via X APIs we need to ensure that + // the directory for precisely the encodings needed by this are added to + // the x font path. This requires that we note the platform names + // specified in font configuration files and use that to identify the + // X font directory that contains a font.dir file for that platform name + // and add it to the X font path (if display is local) + // Here we make use of an already built map of xlfds to font locations + // to add the font location to the set of those required to build the + // x font path needed by AWT. + // These are added to the x font path later. + // All this is necessary because on Solaris the font.dir directories + // may contain not real font files, but symbolic links to the actual + // location but that location is not suitable for the x font path, since + // it probably doesn't have a font.dir at all and certainly not one + // with the required encodings + // If the fontconfiguration file is properly set up so that all fonts + // are mapped to files then we will never trigger initialising + // xFontDirsMap (it will be null). In this case the awtfontpath entries + // must specify all the X11 directories needed by AWT. + @Override + protected void addFontToPlatformFontPath(String platformName) { + // Lazily initialize fontConfigDirs. + getPlatformFontPathFromFontConfig(); + if (xFontDirsMap != null) { + String fontID = specificFontIDForName(platformName); + String dirName = (String)xFontDirsMap.get(fontID); + if (dirName != null) { + fontConfigDirs.add(dirName); + } + } + return; + } + + private void getPlatformFontPathFromFontConfig() { + if (fontConfigDirs == null) { + fontConfigDirs = getFontConfiguration().getAWTFontPathSet(); + if (FontUtilities.debugFonts() && fontConfigDirs != null) { + String[] names = fontConfigDirs.toArray(new String[0]); + for (int i=0;i<names.length;i++) { + FontUtilities.getLogger().info("awtfontpath : " + names[i]); + } + } + } + } + + @Override + protected void registerPlatformFontsUsedByFontConfiguration() { + // Lazily initialize fontConfigDirs. + getPlatformFontPathFromFontConfig(); + if (fontConfigDirs == null) { + return; + } + if (FontUtilities.isLinux) { + fontConfigDirs.add(jreLibDirName+File.separator+"oblique-fonts"); + } + fontdirs = (String[])fontConfigDirs.toArray(new String[0]); + } + + /* Called by MToolkit to set the X11 font path */ + public static void setNativeFontPath() { + if (fontdirs == null) { + return; + } + + // need to register these individually rather than by one call + // to ensure that one bad directory doesn't cause all to be rejected + for (int i=0; i<fontdirs.length; i++) { + if (FontUtilities.debugFonts()) { + FontUtilities.getLogger().info("Add " + fontdirs[i] + " to X11 fontpath"); + } + setNativeFontPath(fontdirs[i]); + } + } + + private synchronized static native void setNativeFontPath(String fontPath); + + + // Implements SunGraphicsEnvironment.createFontConfiguration. + protected FontConfiguration createFontConfiguration() { + /* The logic here decides whether to use a preconfigured + * fontconfig.properties file, or synthesise one using platform APIs. + * On Solaris (as opposed to OpenSolaris) we try to use the + * pre-configured ones, but if the files it specifies are missing + * we fail-safe to synthesising one. This might happen if Solaris + * changes its fonts. + * For OpenSolaris I don't expect us to ever create fontconfig files, + * so it will always synthesise. Note that if we misidentify + * OpenSolaris as Solaris, then the test for the presence of + * Solaris-only font files will correct this. + * For Linux we require an exact match of distro and version to + * use the preconfigured file, and also that it points to + * existent fonts. + * If synthesising fails, we fall back to any preconfigured file + * and do the best we can. For the commercial JDK this will be + * fine as it includes the Lucida fonts. OpenJDK should not hit + * this as the synthesis should always work on its platforms. + */ + FontConfiguration mFontConfig = new MFontConfiguration(this); + if (FontUtilities.isOpenSolaris || + (FontUtilities.isLinux && + (!mFontConfig.foundOsSpecificFile() || + !mFontConfig.fontFilesArePresent()) || + (FontUtilities.isSolaris && !mFontConfig.fontFilesArePresent()))) { + FcFontConfiguration fcFontConfig = + new FcFontConfiguration(this); + if (fcFontConfig.init()) { + return fcFontConfig; + } + } + mFontConfig.init(); + return mFontConfig; + } + public FontConfiguration + createFontConfiguration(boolean preferLocaleFonts, + boolean preferPropFonts) { + + return new MFontConfiguration(this, + preferLocaleFonts, preferPropFonts); + } + + public synchronized native String getFontPath(boolean noType1Fonts); + + public String[] getDefaultPlatformFont() { + if (defaultPlatformFont != null) { + return defaultPlatformFont; + } + String[] info = new String[2]; + getFontConfigManager().initFontConfigFonts(false); + FontConfigManager.FcCompFont[] fontConfigFonts = + getFontConfigManager().getFontConfigFonts(); + for (int i=0; i<fontConfigFonts.length; i++) { + if ("sans".equals(fontConfigFonts[i].fcFamily) && + 0 == fontConfigFonts[i].style) { + info[0] = fontConfigFonts[i].firstFont.familyName; + info[1] = fontConfigFonts[i].firstFont.fontFile; + break; + } + } + /* Absolute last ditch attempt in the face of fontconfig problems. + * If we didn't match, pick the first, or just make something + * up so we don't NPE. + */ + if (info[0] == null) { + if (fontConfigFonts.length > 0 && + fontConfigFonts[0].firstFont.fontFile != null) { + info[0] = fontConfigFonts[0].firstFont.familyName; + info[1] = fontConfigFonts[0].firstFont.fontFile; + } else { + info[0] = "Dialog"; + info[1] = "/dialog.ttf"; + } + } + defaultPlatformFont = info; + return defaultPlatformFont; + } + + public synchronized FontConfigManager getFontConfigManager() { + + if (fcManager == null) { + fcManager = new FontConfigManager(); + } + + return fcManager; + } + + @Override + protected FontUIResource getFontConfigFUIR(String family, int style, int size) { + + CompositeFont font2D = getFontConfigManager().getFontConfigFont(family, style); + + if (font2D == null) { // Not expected, just a precaution. + return new FontUIResource(family, style, size); + } + + /* The name of the font will be that of the physical font in slot, + * but by setting the handle to that of the CompositeFont it + * renders as that CompositeFont. + * It also needs to be marked as a created font which is the + * current mechanism to signal that deriveFont etc must copy + * the handle from the original font. + */ + FontUIResource fuir = + new FontUIResource(font2D.getFamilyName(null), style, size); + FontAccess.getFontAccess().setFont2D(fuir, font2D.handle); + FontAccess.getFontAccess().setCreatedFont(fuir); + return fuir; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/solaris/classes/sun/font/FontConfigManager.java Fri Aug 07 19:36:28 2009 +0200 @@ -0,0 +1,454 @@ +/* + * Copyright 2008 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.font; + +import java.util.Locale; +import java.util.logging.Logger; + +import sun.awt.SunHints; +import sun.awt.SunToolkit; + +/** + * Small utility class to manage FontConfig. + */ +public class FontConfigManager { + + static boolean fontConfigFailed = false; + + /* This is populated by native */ + private static final FontConfigInfo fcInfo = new FontConfigInfo(); + + /* Begin support for GTK Look and Feel - query libfontconfig and + * return a composite Font to Swing that uses the desktop font(s). + */ + + /* These next three classes are just data structures. + */ + public static class FontConfigFont { + public String familyName; // eg Bitstream Vera Sans + public String styleStr; // eg Bold + public String fullName; // eg Bitstream Vera Sans Bold + public String fontFile; // eg /usr/X11/lib/fonts/foo.ttf + } + + public static class FcCompFont { + public String fcName; // eg sans + public String fcFamily; // eg sans + public String jdkName; // eg sansserif + public int style; // eg 0=PLAIN + public FontConfigFont firstFont; + public FontConfigFont[] allFonts; + //boolean preferBitmaps; // if embedded bitmaps preferred over AA + public CompositeFont compFont; // null if not yet created/known. + } + + public static class FontConfigInfo { + public int fcVersion; + public String[] cacheDirs = new String[4]; + } + + /* fontconfig recognises slants roman, italic, as well as oblique, + * and a slew of weights, where the ones that matter here are + * regular and bold. + * To fully qualify what we want, we can for example ask for (eg) + * Font.PLAIN : "serif:regular:roman" + * Font.BOLD : "serif:bold:roman" + * Font.ITALIC : "serif:regular:italic" + * Font.BOLD|Font.ITALIC : "serif:bold:italic" + */ + private static String[] fontConfigNames = { + "sans:regular:roman", + "sans:bold:roman", + "sans:regular:italic", + "sans:bold:italic", + + "serif:regular:roman", + "serif:bold:roman", + "serif:regular:italic", + "serif:bold:italic", + + "monospace:regular:roman", + "monospace:bold:roman", + "monospace:regular:italic", + "monospace:bold:italic", + }; + + /* This array has the array elements created in Java code and is + * passed down to native to be filled in. + */ + private FcCompFont[] fontConfigFonts; + + /** + * Instantiates a new FontConfigManager getting the default instance + * of FontManager from the FontManagerFactory. + */ + public FontConfigManager() { + } + + public static String[] getFontConfigNames() { + return fontConfigNames; + } + + /* Called from code that needs to know what are the AA settings + * that apps using FC would pick up for the default desktop font. + * Note apps can change the default desktop font. etc, so this + * isn't certain to be right but its going to correct for most cases. + * Native return values map to the text aa values in sun.awt.SunHints. + * which is used to look up the renderinghint value object. + */ + public static Object getFontConfigAAHint() { + return getFontConfigAAHint("sans"); + } + + /* This is public solely so that for debugging purposes it can be called + * with other names, which might (eg) include a size, eg "sans-24" + * The return value is a text aa rendering hint value. + * Normally we should call the no-args version. + */ + public static Object getFontConfigAAHint(String fcFamily) { + if (FontUtilities.isWindows) { + return null; + } else { + int hint = getFontConfigAASettings(getFCLocaleStr(), fcFamily); + if (hint < 0) { + return null; + } else { + return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, + hint); + } + } + } + + + private static String getFCLocaleStr() { + Locale l = SunToolkit.getStartupLocale(); + String localeStr = l.getLanguage(); + String country = l.getCountry(); + if (!country.equals("")) { + localeStr = localeStr + "-" + country; + } + return localeStr; + } + + /* This does cause the native libfontconfig to be loaded and unloaded, + * but it does not incur the overhead of initialisation of its + * data structures, so shouldn't have a measurable impact. + */ + public static native int getFontConfigVersion(); + + /* This can be made public if it's needed to force a re-read + * rather than using the cached values. The re-read would be needed + * only if some event signalled that the fontconfig has changed. + * In that event this method would need to return directly the array + * to be used by the caller in case it subsequently changed. + */ + public synchronized void initFontConfigFonts(boolean includeFallbacks) { + + if (fontConfigFonts != null) { + if (!includeFallbacks || (fontConfigFonts[0].allFonts != null)) { + return; + } + } + + if (FontUtilities.isWindows || fontConfigFailed) { + return; + } + + long t0 = 0; + if (FontUtilities.isLogging()) { + t0 = System.nanoTime(); + } + + String[] fontConfigNames = FontConfigManager.getFontConfigNames(); + FcCompFont[] fontArr = new FcCompFont[fontConfigNames.length]; + + for (int i = 0; i< fontArr.length; i++) { + fontArr[i] = new FcCompFont(); + fontArr[i].fcName = fontConfigNames[i]; + int colonPos = fontArr[i].fcName.indexOf(':'); + fontArr[i].fcFamily = fontArr[i].fcName.substring(0, colonPos); + fontArr[i].jdkName = FontUtilities.mapFcName(fontArr[i].fcFamily); + fontArr[i].style = i % 4; // depends on array order. + } + getFontConfig(getFCLocaleStr(), fcInfo, fontArr, includeFallbacks); + /* If don't find anything (eg no libfontconfig), then just return */ + for (int i = 0; i< fontArr.length; i++) { + FcCompFont fci = fontArr[i]; + if (fci.firstFont == null) { + if (FontUtilities.isLogging()) { + Logger logger = FontUtilities.getLogger(); + logger.info("Fontconfig returned no fonts."); + } + fontConfigFailed = true; + return; + } + } + fontConfigFonts = fontArr; + + if (FontUtilities.isLogging()) { + + Logger logger = FontUtilities.getLogger(); + + long t1 = System.nanoTime(); + logger.info("Time spent accessing fontconfig=" + + ((t1 - t0) / 1000000) + "ms."); + + for (int i = 0; i< fontConfigFonts.length; i++) { + FcCompFont fci = fontConfigFonts[i]; + logger.info("FC font " + fci.fcName+" maps to family " + + fci.firstFont.familyName + + " in file " + fci.firstFont.fontFile); + if (fci.allFonts != null) { + for (int f=0;f<fci.allFonts.length;f++) { + FontConfigFont fcf = fci.allFonts[f]; + logger.info("Family=" + fcf.familyName + + " Style="+ fcf.styleStr + + " Fullname="+fcf.fullName + + " File="+fcf.fontFile); + } + } + } + } + } + + public PhysicalFont registerFromFcInfo(FcCompFont fcInfo) { + + SunFontManager fm = SunFontManager.getInstance(); + + /* If it's a TTC file we need to know that as we will need to + * make sure we return the right font */ + String fontFile = fcInfo.firstFont.fontFile; + int offset = fontFile.length()-4; + if (offset <= 0) { + return null; + } + String ext = fontFile.substring(offset).toLowerCase(); + boolean isTTC = ext.equals(".ttc"); + + /* If this file is already registered, can just return its font. + * However we do need to check in case it's a TTC as we need + * a specific font, so rather than directly returning it, let + * findFont2D resolve that. + */ + PhysicalFont physFont = fm.getRegisteredFontFile(fontFile); + if (physFont != null) { + if (isTTC) { + Font2D f2d = fm.findFont2D(fcInfo.firstFont.familyName, + fcInfo.style, + FontManager.NO_FALLBACK); + if (f2d instanceof PhysicalFont) { /* paranoia */ + return (PhysicalFont)f2d; + } else { + return null; + } + } else { + return physFont; + } + } + + /* If the font may hide a JRE font (eg fontconfig says it is + * Lucida Sans), we want to use the JRE version, so make it + * point to the JRE font. + */ + physFont = fm.findJREDeferredFont(fcInfo.firstFont.familyName, + fcInfo.style); + + /* It is also possible the font file is on the "deferred" list, + * in which case we can just initialise it now. + */ + if (physFont == null && + fm.isDeferredFont(fontFile) == true) { + physFont = fm.initialiseDeferredFont(fcInfo.firstFont.fontFile); + /* use findFont2D to get the right font from TTC's */ + if (physFont != null) { + if (isTTC) { + Font2D f2d = fm.findFont2D(fcInfo.firstFont.familyName, + fcInfo.style, + FontManager.NO_FALLBACK); + if (f2d instanceof PhysicalFont) { /* paranoia */ + return (PhysicalFont)f2d; + } else { + return null; + } + } else { + return physFont; + } + } + } + + /* In the majority of cases we reach here, and need to determine + * the type and rank to register the font. + */ + if (physFont == null) { + int fontFormat = SunFontManager.FONTFORMAT_NONE; + int fontRank = Font2D.UNKNOWN_RANK; + + if (ext.equals(".ttf") || isTTC) { + fontFormat = SunFontManager.FONTFORMAT_TRUETYPE; + fontRank = Font2D.TTF_RANK; + } else if (ext.equals(".pfa") || ext.equals(".pfb")) { + fontFormat = SunFontManager.FONTFORMAT_TYPE1; + fontRank = Font2D.TYPE1_RANK; + } + physFont = fm.registerFontFile(fcInfo.firstFont.fontFile, null, + fontFormat, true, fontRank); + } + return physFont; + } + + /* + * We need to return a Composite font which has as the font in + * its first slot one obtained from fontconfig. + */ + public CompositeFont getFontConfigFont(String name, int style) { + + name = name.toLowerCase(); + + initFontConfigFonts(false); + + FcCompFont fcInfo = null; + for (int i=0; i<fontConfigFonts.length; i++) { + if (name.equals(fontConfigFonts[i].fcFamily) && + style == fontConfigFonts[i].style) { + fcInfo = fontConfigFonts[i]; + break; + } + } + if (fcInfo == null) { + fcInfo = fontConfigFonts[0]; + } + + if (FontUtilities.isLogging()) { + FontUtilities.getLogger() + .info("FC name=" + name + " style=" + style + + " uses " + fcInfo.firstFont.familyName + + " in file: " + fcInfo.firstFont.fontFile); + } + + if (fcInfo.compFont != null) { + return fcInfo.compFont; + } + + /* jdkFont is going to be used for slots 1..N and as a fallback. + * Slot 0 will be the physical font from fontconfig. + */ + FontManager fm = FontManagerFactory.getInstance(); + CompositeFont jdkFont = (CompositeFont) + fm.findFont2D(fcInfo.jdkName, style, FontManager.LOGICAL_FALLBACK); + + if (fcInfo.firstFont.familyName == null || + fcInfo.firstFont.fontFile == null) { + return (fcInfo.compFont = jdkFont); + } + + /* First, see if the family and exact style is already registered. + * If it is, use it. If it's not, then try to register it. + * If that registration fails (signalled by null) just return the + * regular JDK composite. + * Algorithmically styled fonts won't match on exact style, so + * will fall through this code, but the regisration code will + * find that file already registered and return its font. + */ + FontFamily family = FontFamily.getFamily(fcInfo.firstFont.familyName); + PhysicalFont physFont = null; + if (family != null) { + Font2D f2D = family.getFontWithExactStyleMatch(fcInfo.style); + if (f2D instanceof PhysicalFont) { + physFont = (PhysicalFont)f2D; + } + } + + if (physFont == null || + !fcInfo.firstFont.fontFile.equals(physFont.platName)) { + physFont = registerFromFcInfo(fcInfo); + if (physFont == null) { + return (fcInfo.compFont = jdkFont); + } + family = FontFamily.getFamily(physFont.getFamilyName(null)); + } + + /* Now register the fonts in the family (the other styles) after + * checking that they aren't already registered and are actually in + * a different file. They may be the same file in CJK cases. + * For cases where they are different font files - eg as is common for + * Latin fonts, then we rely on fontconfig to report these correctly. + * Assume that all styles of this font are found by fontconfig, + * so we can find all the family members which must be registered + * together to prevent synthetic styling. + */ + for (int i=0; i<fontConfigFonts.length; i++) { + FcCompFont fc = fontConfigFonts[i]; + if (fc != fcInfo && + physFont.getFamilyName(null).equals(fc.firstFont.familyName) && + !fc.firstFont.fontFile.equals(physFont.platName) && + family.getFontWithExactStyleMatch(fc.style) == null) { + + registerFromFcInfo(fontConfigFonts[i]); + } + } + + /* Now we have a physical font. We will back this up with the JDK + * logical font (sansserif, serif, or monospaced) that corresponds + * to the Pango/GTK/FC logical font name. + */ + return (fcInfo.compFont = new CompositeFont(physFont, jdkFont)); + } + + /** + * + * @param locale + * @param fcFamily + * @return + */ + public FcCompFont[] getFontConfigFonts() { + return fontConfigFonts; + } + + /* Return an array of FcCompFont structs describing the primary + * font located for each of fontconfig/GTK/Pango's logical font names. + */ + private static native void getFontConfig(String locale, + FontConfigInfo fcInfo, + FcCompFont[] fonts, + boolean includeFallbacks); + + void populateFontConfig(FcCompFont[] fcInfo) { + fontConfigFonts = fcInfo; + } + + FcCompFont[] loadFontConfig() { + initFontConfigFonts(true); + return fontConfigFonts; + } + + FontConfigInfo getFontConfigInfo() { + initFontConfigFonts(true); + return fcInfo; + } + + private static native int + getFontConfigAASettings(String locale, String fcFamily); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/windows/classes/sun/awt/Win32FontManager.java Fri Aug 07 19:36:28 2009 +0200 @@ -0,0 +1,280 @@ +/* + * Copyright 2008 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.awt; + +import java.awt.FontFormatException; +import java.awt.GraphicsEnvironment; +import java.io.File; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import sun.awt.Win32GraphicsEnvironment; +import sun.awt.windows.WFontConfiguration; +import sun.font.FontManager; +import sun.font.SunFontManager; +import sun.font.TrueTypeFont; +import sun.java2d.HeadlessGraphicsEnvironment; +import sun.java2d.SunGraphicsEnvironment; + +/** + * The X11 implementation of {@link FontManager}. + */ +public class Win32FontManager extends SunFontManager { + + private static String[] defaultPlatformFont = null; + + private static TrueTypeFont eudcFont; + + static { + + AccessController.doPrivileged(new PrivilegedAction() { + + public Object run() { + String eudcFile = getEUDCFontFile(); + if (eudcFile != null) { + try { + eudcFont = new TrueTypeFont(eudcFile, null, 0, + true); + } catch (FontFormatException e) { + } + } + return null; + } + + }); + } + + /* Used on Windows to obtain from the windows registry the name + * of a file containing the system EUFC font. If running in one of + * the locales for which this applies, and one is defined, the font + * defined by this file is appended to all composite fonts as a + * fallback component. + */ + private static native String getEUDCFontFile(); + + public Win32FontManager() { + super(); + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + + /* 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); + return null; + } + }); + } + + /* Unlike the shared code version, this expects a base file name - + * not a full path name. + * The font configuration file has base file names and the FontConfiguration + * class reports these back to the GraphicsEnvironment, so these + * are the componentFileNames of CompositeFonts. + */ + protected void registerFontFile(String fontFileName, String[] nativeNames, + int fontRank, boolean defer) { + + // REMIND: case compare depends on platform + if (registeredFontFiles.contains(fontFileName)) { + return; + } + registeredFontFiles.add(fontFileName); + + int fontFormat; + if (getTrueTypeFilter().accept(null, fontFileName)) { + fontFormat = SunFontManager.FONTFORMAT_TRUETYPE; + } else if (getType1Filter().accept(null, fontFileName)) { + fontFormat = SunFontManager.FONTFORMAT_TYPE1; + } else { + /* on windows we don't use/register native fonts */ + return; + } + + if (fontPath == null) { + fontPath = getPlatformFontPath(noType1Font); + } + + /* Look in the JRE font directory first. + * This is playing it safe as we would want to find fonts in the + * JRE font directory ahead of those in the system directory + */ + String tmpFontPath = jreFontDirName+File.pathSeparator+fontPath; + StringTokenizer parser = new StringTokenizer(tmpFontPath, + File.pathSeparator); + + boolean found = false; + try { + while (!found && parser.hasMoreTokens()) { + String newPath = parser.nextToken(); + File theFile = new File(newPath, fontFileName); + if (theFile.canRead()) { + found = true; + String path = theFile.getAbsolutePath(); + if (defer) { + registerDeferredFont(fontFileName, path, + nativeNames, + fontFormat, true, + fontRank); + } else { + registerFontFile(path, nativeNames, + fontFormat, true, + fontRank); + } + break; + } + } + } catch (NoSuchElementException e) { + System.err.println(e); + } + if (!found) { + addToMissingFontFileList(fontFileName); + } + } + + @Override + protected FontConfiguration createFontConfiguration() { + + FontConfiguration fc = new WFontConfiguration(this); + fc.init(); + return fc; + } + + @Override + public FontConfiguration createFontConfiguration(boolean preferLocaleFonts, + boolean preferPropFonts) { + + return new WFontConfiguration(this, + preferLocaleFonts,preferPropFonts); + } + + protected void + populateFontFileNameMap(HashMap<String,String> fontToFileMap, + HashMap<String,String> fontToFamilyNameMap, + HashMap<String,ArrayList<String>> + familyToFontListMap, + Locale locale) { + + populateFontFileNameMap0(fontToFileMap, fontToFamilyNameMap, + familyToFontListMap, locale); + + } + + private static native void + populateFontFileNameMap0(HashMap<String,String> fontToFileMap, + HashMap<String,String> fontToFamilyNameMap, + HashMap<String,ArrayList<String>> + familyToFontListMap, + Locale locale); + + public synchronized native String getFontPath(boolean noType1Fonts); + + public String[] getDefaultPlatformFont() { + + if (defaultPlatformFont != null) { + return defaultPlatformFont; + } + + String[] info = new String[2]; + info[0] = "Arial"; + info[1] = "c:\\windows\\fonts"; + final String[] dirs = getPlatformFontDirs(true); + if (dirs.length > 1) { + String dir = (String) + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + for (int i=0; i<dirs.length; i++) { + String path = + dirs[i] + File.separator + "arial.ttf"; + File file = new File(path); + if (file.exists()) { + return dirs[i]; + } + } + return null; + } + }); + if (dir != null) { + info[1] = dir; + } + } else { + info[1] = dirs[0]; + } + info[1] = info[1] + File.separator + "arial.ttf"; + defaultPlatformFont = info; + return defaultPlatformFont; + } + + /* register only TrueType/OpenType fonts + * Because these need to be registed just for use when printing, + * we defer the actual registration and the static initialiser + * for the printing class makes the call to registerJREFontsForPrinting() + */ + static String fontsForPrinting = null; + protected void registerJREFontsWithPlatform(String pathName) { + fontsForPrinting = pathName; + } + + public static void registerJREFontsForPrinting() { + final String pathName; + synchronized (Win32GraphicsEnvironment.class) { + GraphicsEnvironment.getLocalGraphicsEnvironment(); + if (fontsForPrinting == null) { + return; + } + pathName = fontsForPrinting; + fontsForPrinting = null; + } + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + File f1 = new File(pathName); + String[] ls = f1.list(SunFontManager.getInstance(). + getTrueTypeFilter()); + if (ls == null) { + return null; + } + for (int i=0; i <ls.length; i++ ) { + File fontFile = new File(f1, ls[i]); + registerFontWithPlatform(fontFile.getAbsolutePath()); + } + return null; + } + }); + } + + protected static native void registerFontWithPlatform(String fontName); + + protected static native void deRegisterFontWithPlatform(String fontName); + +}