/* * Copyright 2003-2007 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.font; import java.awt.Font; import java.awt.GraphicsEnvironment; import java.awt.FontFormatException; import java.io.File; import java.io.FilenameFilter; 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.SunHints; import sun.awt.SunToolkit; import sun.java2d.HeadlessGraphicsEnvironment; import sun.java2d.SunGraphicsEnvironment; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.lang.reflect.Constructor; import sun.java2d.Disposer; /* * Interface between Java Fonts (java.awt.Font) and the underlying * font files/native font resources and the Java and native font scalers. */ public final class FontManager { 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; public static final int NO_FALLBACK = 0; public static final int PHYSICAL_FALLBACK = 1; public static final int LOGICAL_FALLBACK = 2; public static final int QUADPATHTYPE = 1; public static final int CUBICPATHTYPE = 2; /* 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 static int lastPoolIndex = 0; private static int poolSize = 0; private static 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 static int maxCompFont = 0; private static CompositeFont [] compFonts = new CompositeFont[20]; private static ConcurrentHashMap compositeFonts = new ConcurrentHashMap(); private static ConcurrentHashMap physicalFonts = new ConcurrentHashMap(); private static ConcurrentHashMap registeredFontFiles = new ConcurrentHashMap(); /* given a full name find the Font. Remind: there's duplication * here in that this contains the content of compositeFonts + * physicalFonts. */ private static ConcurrentHashMap fullNameToFont = new ConcurrentHashMap(); /* TrueType fonts have localised names. Support searching all * of these before giving up on a name. */ private static HashMap localeFullNamesToFont; private static PhysicalFont defaultPhysicalFont; /* deprecated, unsupported hack - actually invokes a bug! */ private static boolean usePlatformFontMetrics = false; public static Logger logger = null; public static boolean logging; static boolean longAddresses; static String osName; static boolean useT2K; static boolean isWindows; static boolean isSolaris; public static boolean isSolaris8; // needed to check for JA wavedash fix. public static boolean isSolaris9; // needed to check for songti font usage. private static boolean loaded1dot0Fonts = false; static SunGraphicsEnvironment sgEnv; static boolean loadedAllFonts = false; static boolean loadedAllFontFiles = false; static TrueTypeFont eudcFont; static HashMap jreFontMap; static HashSet jreLucidaFontFiles; static String[] jreOtherFontFiles; static boolean noOtherJREFontFiles = false; // initial assumption. /* Used to indicate required return type from toArray(..); */ private static String[] STR_ARRAY = new String[0]; private static 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 (SunGraphicsEnvironment.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 { if (SunGraphicsEnvironment.debugFonts) { logger = Logger.getLogger("sun.java2d", null); logging = logger.getLevel() != Level.OFF; } initJREFontMap(); 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"); } osName = System.getProperty("os.name", "unknownOS"); isSolaris = osName.startsWith("SunOS"); if (isSolaris) { String t2kStr= System.getProperty("sun.java2d.font.scaler"); useT2K = "t2k".equals(t2kStr); String version = System.getProperty("os.version", "unk"); isSolaris8 = version.equals("5.8"); isSolaris9 = version.equals("5.9"); } else { isWindows = osName.startsWith("Windows"); if (isWindows) { String eudcFile = SunGraphicsEnvironment.eudcFontFileName; if (eudcFile != null) { try { eudcFont = new TrueTypeFont(eudcFile, null, 0, true); } catch (FontFormatException e) { } } String prop = System.getProperty("java2d.font.usePlatformFont"); if (("true".equals(prop) || getPlatformFontVar())) { 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."); } } } return null; } }); } /* Initialise ptrs used by JNI methods */ private static native void initIDs(); public static void addToPool(FileFont font) { boolean added = false; synchronized (fontFileCache) { /* use poolSize to quickly detect if there's any free slots. * This is a performance tweak based on the assumption that * if this is executed at all often, its because there are many * fonts being used and the pool will be full, and we will save * a fruitless iteration */ if (poolSize < CHANNELPOOLSIZE) { for (int i=0; i altNameCache) { CompositeFont cf = new CompositeFont(compositeName, componentFileNames, componentNames, numMetricsSlots, exclusionRanges, exclusionMaxIndex, defer); /* 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 static void addCompositeToFontList(CompositeFont f, int rank) { if (logging) { logger.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 static 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 (logging) { logger.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( SunGraphicsEnvironment.jreFontDirName)) { if (logging) { logger.warning("Unexpected attempt to replace a JRE " + " font " + fontName + " from " + oldFont.platName + " with " + newFont.platName); } return oldFont; } if (logging) { logger.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 static 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; } public static 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 static final ConcurrentHashMap deferredFontFiles = new ConcurrentHashMap(); private static 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). */ public static synchronized void initialiseDeferredFonts() { for (String fileName : deferredFontFiles.keySet()) { initialiseDeferredFont(fileName); } } public static synchronized void registerDeferredJREFonts(String jreDir) { for (FontRegistrationInfo info : deferredFontFiles.values()) { if (info.fontFilePath != null && info.fontFilePath.startsWith(jreDir)) { initialiseDeferredFont(info.fontFilePath); } } } /* 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. */ private static PhysicalFont findJREDeferredFont(String name, int style) { PhysicalFont physicalFont; String nameAndStyle = name.toLowerCase(Locale.ENGLISH) + style; String fileName = jreFontMap.get(nameAndStyle); if (fileName != null) { initSGEnv(); /* ensure jreFontDirName is initialised */ fileName = SunGraphicsEnvironment.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(SunGraphicsEnvironment.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 static 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 static 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 static HashMap> familyToFontListMap= null; /* The directories which contain platform fonts */ private static String[] pathDirs = null; private static boolean haveCheckedUnreferencedFontFiles; private static String[] getFontFilesFromPath(boolean noType1) { final FilenameFilter filter; if (noType1) { filter = SunGraphicsEnvironment.ttFilter; } else { filter = new SunGraphicsEnvironment.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