diff --git a/jdk/src/share/classes/sun/font/FontAccess.java b/jdk/src/share/classes/sun/font/FontAccess.java new file mode 100644 index 00000000000..442fb5a8fa3 --- /dev/null +++ b/jdk/src/share/classes/sun/font/FontAccess.java @@ -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); +} diff --git a/jdk/src/share/classes/sun/font/FontManagerFactory.java b/jdk/src/share/classes/sun/font/FontManagerFactory.java new file mode 100644 index 00000000000..44bfe68c541 --- /dev/null +++ b/jdk/src/share/classes/sun/font/FontManagerFactory.java @@ -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; + } +} diff --git a/jdk/src/share/classes/sun/font/FontManagerForSGE.java b/jdk/src/share/classes/sun/font/FontManagerForSGE.java new file mode 100644 index 00000000000..05d1971e2ed --- /dev/null +++ b/jdk/src/share/classes/sun/font/FontManagerForSGE.java @@ -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 getCreatedFontFamilyNames(); + + /** + * Returns all fonts installed in this environment. + */ + public Font[] getAllInstalledFonts(); + + public String[] getInstalledFontFamilyNames(Locale requestedLocale); +} diff --git a/jdk/src/share/classes/sun/font/FontUtilities.java b/jdk/src/share/classes/sun/font/FontUtilities.java new file mode 100644 index 00000000000..f6dda4b248a --- /dev/null +++ b/jdk/src/share/classes/sun/font/FontUtilities.java @@ -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; + } + } + +} diff --git a/jdk/src/share/classes/sun/font/SunFontManager.java b/jdk/src/share/classes/sun/font/SunFontManager.java new file mode 100644 index 00000000000..381572ebe02 --- /dev/null +++ b/jdk/src/share/classes/sun/font/SunFontManager.java @@ -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 + compositeFonts = new ConcurrentHashMap(); + private ConcurrentHashMap + physicalFonts = new ConcurrentHashMap(); + private ConcurrentHashMap + registeredFonts = new ConcurrentHashMap(); + + /* given a full name find the Font. Remind: there's duplication + * here in that this contains the content of compositeFonts + + * physicalFonts. + */ + private ConcurrentHashMap + fullNameToFont = new ConcurrentHashMap(); + + /* TrueType fonts have localised names. Support searching all + * of these before giving up on a name. + */ + private HashMap localeFullNamesToFont; + + private PhysicalFont defaultPhysicalFont; + + static boolean longAddresses; + private boolean loaded1dot0Fonts = false; + boolean loadedAllFonts = false; + boolean loadedAllFontFiles = false; + HashMap jreFontMap; + HashSet 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 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(); + jreLucidaFontFiles = new HashSet(); + 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() { + 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 + 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 + deferredFontFiles = + new ConcurrentHashMap(); + private final ConcurrentHashMap + initialisedFonts = new ConcurrentHashMap(); + + /* 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 otherFontFiles = new HashSet(); + 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 fontToFileMap, + HashMap fontToFamilyNameMap, + HashMap> + 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 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 fontToFamilyNameMap = null; + + /* Obtained from Platform APIs (windows only) + * Map from a lower-case family name to a list of full names of + * the member fonts, eg: + * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"] + */ + private HashMap> familyToFontListMap= null; + + /* 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 fileList = new ArrayList(); + 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