src/share/classes/sun/font/FontManager.java

Print this page
rev 1297 : [mq]: fontmanager.patch

*** 20,3852 **** * * 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 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<String, CompositeFont> - compositeFonts = new ConcurrentHashMap<String, CompositeFont>(); - private static ConcurrentHashMap<String, PhysicalFont> - physicalFonts = new ConcurrentHashMap<String, PhysicalFont>(); - private static ConcurrentHashMap<String, PhysicalFont> - registeredFontFiles = 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 static 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 static HashMap<String, TrueTypeFont> 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<String,String> jreFontMap; - static HashSet<String> jreLucidaFontFiles; - static String[] jreOtherFontFiles; - static boolean noOtherJREFontFiles = false; // initial assumption. - static boolean fontConfigFailed = false; - - /* 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<String,String>(); - jreLucidaFontFiles = new HashSet<String>(); - 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"); - - String t2kStr = System.getProperty("sun.java2d.font.scaler"); - if (t2kStr != null) { - useT2K = "t2k".equals(t2kStr); - } - if (isSolaris) { - 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) { - - 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(); - } - } - - /* - * In the normal course of events, the pool of fonts can remain open - * ready for quick access to their contents. The pool is sized so - * that it is not an excessive consumer of system resources whilst - * facilitating performance by providing ready access to the most - * recently used set of font files. - * The only reason to call removeFromPool(..) is for a Font that - * you want to to have GC'd. Currently this would apply only to fonts - * created with java.awt.Font.createFont(..). - * In this case, the caller is expected to have arranged for the file - * to be closed. - * REMIND: consider how to know when a createFont created font should - * be closed. - */ - public static void removeFromPool(FileFont font) { - synchronized (fontFileCache) { - for (int i=0; i<CHANNELPOOLSIZE; i++) { - if (fontFileCache[i] == font) { - fontFileCache[i] = null; - } - } - } - } - /** ! * 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); ! // } ! ! /* 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 = 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; - } - - CompositeFont dialog2D = - (CompositeFont)findFont2D("dialog", font.getStyle(), NO_FALLBACK); - if (dialog2D == null) { /* shouldn't happen */ - return fuir; - } - PhysicalFont physicalFont = (PhysicalFont)font2D; - CompositeFont compFont = new CompositeFont(physicalFont, dialog2D); - setFont2D(fuir, compFont.handle); - /* marking this as a created font is needed as only created fonts - * copy their creator's handles. - */ - setCreatedFont(fuir); - return fuir; - } - - public static 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; - } - - public static native void setFont2D(Font font, Font2DHandle font2DHandle); - - private static native boolean isCreatedFont(Font font); - private static native void setCreatedFont(Font font); - - public static 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); - 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. - */ - public 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); - /* 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<String, FontRegistrationInfo> - deferredFontFiles = - new ConcurrentHashMap<String, FontRegistrationInfo>(); - private static 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). - */ - 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<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(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<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 static 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(SunGraphicsEnvironment.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 static PhysicalFont findDeferredFont(String name, int style) { - - PhysicalFont physicalFont = findJREDeferredFont(name, style); - if (physicalFont != null) { - return physicalFont; - } else { - return findOtherDeferredFont(name, style); - } - } - - public static 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 static synchronized - PhysicalFont initialiseDeferredFont(String fileNameKey) { - - if (fileNameKey == null) { - return null; - } - if (logging) { - logger.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; - } - - /* 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 static PhysicalFont registerFontFile(String fileName, - String[] nativeNames, - int fontFormat, - boolean useJavaRasterizer, - int fontRank) { - - PhysicalFont regFont = registeredFontFiles.get(fileName); - if (regFont != null) { - return regFont; - } - - PhysicalFont physicalFont = null; - try { - String name; - - switch (fontFormat) { - - case FontManager.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 FontManager.FONTFORMAT_TYPE1: - Type1Font t1f = new Type1Font(fileName, nativeNames); - physicalFont = addToFontList(t1f, fontRank); - break; - - case FontManager.FONTFORMAT_NATIVE: - NativeFont nf = new NativeFont(fileName, false); - physicalFont = addToFontList(nf, fontRank); - default: - - } - if (logging) { - logger.info("Registered file " + fileName + " as font " + - physicalFont + " rank=" + fontRank); - } - } catch (FontFormatException ffe) { - if (logging) { - logger.warning("Unusable font: " + - fileName + " " + ffe.toString()); - } - } - if (physicalFont != null && - fontFormat != FontManager.FONTFORMAT_NATIVE) { - registeredFontFiles.put(fileName, physicalFont); - } - return physicalFont; - } - - public static 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 static 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 static 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"; - } - } - - static void initSGEnv() { - if (sgEnv == null) { - GraphicsEnvironment ge = - GraphicsEnvironment.getLocalGraphicsEnvironment(); - if (ge instanceof HeadlessGraphicsEnvironment) { - HeadlessGraphicsEnvironment hgEnv = - (HeadlessGraphicsEnvironment)ge; - sgEnv = (SunGraphicsEnvironment) - hgEnv.getSunGraphicsEnvironment(); - } else { - sgEnv = (SunGraphicsEnvironment)ge; - } - } - } - - /* 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. - */ - private static native 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 static 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 static 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 static HashMap<String,ArrayList<String>> 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<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 static 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 (logging) { - logger.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 static synchronized void checkForUnreferencedFontFiles() { - if (haveCheckedUnreferencedFontFiles) { - return; - } - haveCheckedUnreferencedFontFiles = true; - if (!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 (logging) { - logger.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 static 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 (logging) { - logger.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 (logging) { - logger.info("Resolved absent registry entry for " + - fontName + " located in " + fullPath); - } - } - } - while (fn < ttf.getFontCount()); - } catch (Exception e) { - } - } - } - - private static synchronized HashMap<String,String> getFullNameToFileMap() { - if (fontToFileMap == null) { - - initSGEnv(); - pathDirs = sgEnv.getPlatformFontDirs(); - - 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 (isWindows) { - resolveWindowsFonts(); - } - if (logging) { - logPlatformFontInfo(); - } - } - return fontToFileMap; - } - - private static void logPlatformFontInfo() { - 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 */ - public static 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 static boolean gotFontsFromPlatform() { - return getFullNameToFileMap().size() != 0; - } - - public static String getFileNameForFontName(String fontName) { - String fontNameLC = fontName.toLowerCase(Locale.ENGLISH); - return fontToFileMap.get(fontNameLC); - } - - private static PhysicalFont registerFontFile(String file) { - if (new File(file).isAbsolute() && - !registeredFontFiles.contains(file)) { - int fontFormat = FONTFORMAT_NONE; - int fontRank = Font2D.UNKNOWN_RANK; - if (SunGraphicsEnvironment.ttFilter.accept(null, file)) { - fontFormat = FONTFORMAT_TRUETYPE; - fontRank = Font2D.TTF_RANK; - } else if - (SunGraphicsEnvironment.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. - */ - public static void registerOtherFontFiles(HashSet registeredFontFiles) { - if (getFullNameToFileMap().size() == 0) { - return; - } - for (String file : fontToFileMap.values()) { - registerFontFile(file); - } - } - - public static 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 static 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 static 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 (logging) { - logger.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 static 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 static 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 (logging) { ! logger.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 (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 reach here its possible that this is in a client which never ! * loaded the GraphicsEnvironment, so we haven't even loaded ANY of ! * the fonts from the environment. Do so now and recurse. ! */ ! if (sgEnv == null) { ! initSGEnv(); ! return findFont2D(name, style, fallback); ! } ! ! if (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 (logging) { ! logger.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 (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); ! } ! sgEnv.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 (logging) { ! logger.info("Load fonts looking for:" + name); ! } ! sgEnv.loadFonts(); ! loadedAllFonts = true; ! return findFont2D(name, style, fallback); ! } ! ! if (!loadedAllFontFiles) { ! if (logging) { ! logger.info("Load font files looking for:" + name); ! } ! sgEnv.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 (isWindows) { ! String compatName = ! sgEnv.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 (logging) { ! logger.info("No font found for:" + name); ! } ! ! switch (fallback) { ! case PHYSICAL_FALLBACK: return getDefaultPhysicalFont(); ! case LOGICAL_FALLBACK: return getDefaultLogicalFont(style); ! default: return null; ! } ! } ! ! /* This method can be more efficient as it will only need to ! * do the lookup once, and subsequent calls on the java.awt.Font ! * instance can utilise the cached Font2D on that object. ! * Its unfortunate it needs to be a native method, but the font2D ! * variable has to be private. ! */ ! public static native Font2D getFont2D(Font font); ! ! /* Stuff below was in NativeFontWrapper and needed a new home */ ! ! /* ! * Workaround for apps which are dependent on a font metrics bug ! * in JDK 1.1. This is an unsupported win32 private setting. ! */ ! public static boolean usePlatformFontMetrics() { ! return usePlatformFontMetrics; ! } ! ! static native boolean getPlatformFontVar(); ! ! private static final short US_LCID = 0x0409; // US English - default ! private static Map<String, Short> lcidMap; ! ! // Return a Microsoft LCID from the given Locale. ! // Used when getting localized font data. ! ! public static short getLCIDFromLocale(Locale locale) { ! // optimize for common case ! if (locale.equals(Locale.US)) { ! return US_LCID; ! } ! ! if (lcidMap == null) { ! createLCIDMap(); ! } ! ! String key = locale.toString(); ! while (!"".equals(key)) { ! Short lcidObject = (Short) lcidMap.get(key); ! if (lcidObject != null) { ! return lcidObject.shortValue(); ! } ! int pos = key.lastIndexOf('_'); ! if (pos < 1) { ! return US_LCID; ! } ! key = key.substring(0, pos); ! } ! ! return US_LCID; ! } ! ! ! private static void addLCIDMapEntry(Map<String, Short> map, ! String key, short value) { ! map.put(key, Short.valueOf(value)); ! } ! ! private static synchronized void createLCIDMap() { ! if (lcidMap != null) { ! return; ! } ! ! Map<String, Short> map = new HashMap<String, Short>(200); ! ! // the following statements are derived from the langIDMap ! // in src/windows/native/java/lang/java_props_md.c using the following ! // awk script: ! // $1~/\/\*/ { next} ! // $3~/\?\?/ { next } ! // $3!~/_/ { next } ! // $1~/0x0409/ { next } ! // $1~/0x0c0a/ { next } ! // $1~/0x042c/ { next } ! // $1~/0x0443/ { next } ! // $1~/0x0812/ { next } ! // $1~/0x04/ { print " addLCIDMapEntry(map, " substr($3, 0, 3) "\", (short) " substr($1, 0, 6) ");" ; next } ! // $3~/,/ { print " addLCIDMapEntry(map, " $3 " (short) " substr($1, 0, 6) ");" ; next } ! // { print " addLCIDMapEntry(map, " $3 ", (short) " substr($1, 0, 6) ");" ; next } ! // The lines of this script: ! // - eliminate comments ! // - eliminate questionable locales ! // - eliminate language-only locales ! // - eliminate the default LCID value ! // - eliminate a few other unneeded LCID values ! // - print language-only locale entries for x04* LCID values ! // (apparently Microsoft doesn't use language-only LCID values - ! // see http://www.microsoft.com/OpenType/otspec/name.htm ! // - print complete entries for all other LCID values ! // Run ! // awk -f awk-script langIDMap > statements ! addLCIDMapEntry(map, "ar", (short) 0x0401); ! addLCIDMapEntry(map, "bg", (short) 0x0402); ! addLCIDMapEntry(map, "ca", (short) 0x0403); ! addLCIDMapEntry(map, "zh", (short) 0x0404); ! addLCIDMapEntry(map, "cs", (short) 0x0405); ! addLCIDMapEntry(map, "da", (short) 0x0406); ! addLCIDMapEntry(map, "de", (short) 0x0407); ! addLCIDMapEntry(map, "el", (short) 0x0408); ! addLCIDMapEntry(map, "es", (short) 0x040a); ! addLCIDMapEntry(map, "fi", (short) 0x040b); ! addLCIDMapEntry(map, "fr", (short) 0x040c); ! addLCIDMapEntry(map, "iw", (short) 0x040d); ! addLCIDMapEntry(map, "hu", (short) 0x040e); ! addLCIDMapEntry(map, "is", (short) 0x040f); ! addLCIDMapEntry(map, "it", (short) 0x0410); ! addLCIDMapEntry(map, "ja", (short) 0x0411); ! addLCIDMapEntry(map, "ko", (short) 0x0412); ! addLCIDMapEntry(map, "nl", (short) 0x0413); ! addLCIDMapEntry(map, "no", (short) 0x0414); ! addLCIDMapEntry(map, "pl", (short) 0x0415); ! addLCIDMapEntry(map, "pt", (short) 0x0416); ! addLCIDMapEntry(map, "rm", (short) 0x0417); ! addLCIDMapEntry(map, "ro", (short) 0x0418); ! addLCIDMapEntry(map, "ru", (short) 0x0419); ! addLCIDMapEntry(map, "hr", (short) 0x041a); ! addLCIDMapEntry(map, "sk", (short) 0x041b); ! addLCIDMapEntry(map, "sq", (short) 0x041c); ! addLCIDMapEntry(map, "sv", (short) 0x041d); ! addLCIDMapEntry(map, "th", (short) 0x041e); ! addLCIDMapEntry(map, "tr", (short) 0x041f); ! addLCIDMapEntry(map, "ur", (short) 0x0420); ! addLCIDMapEntry(map, "in", (short) 0x0421); ! addLCIDMapEntry(map, "uk", (short) 0x0422); ! addLCIDMapEntry(map, "be", (short) 0x0423); ! addLCIDMapEntry(map, "sl", (short) 0x0424); ! addLCIDMapEntry(map, "et", (short) 0x0425); ! addLCIDMapEntry(map, "lv", (short) 0x0426); ! addLCIDMapEntry(map, "lt", (short) 0x0427); ! addLCIDMapEntry(map, "fa", (short) 0x0429); ! addLCIDMapEntry(map, "vi", (short) 0x042a); ! addLCIDMapEntry(map, "hy", (short) 0x042b); ! addLCIDMapEntry(map, "eu", (short) 0x042d); ! addLCIDMapEntry(map, "mk", (short) 0x042f); ! addLCIDMapEntry(map, "tn", (short) 0x0432); ! addLCIDMapEntry(map, "xh", (short) 0x0434); ! addLCIDMapEntry(map, "zu", (short) 0x0435); ! addLCIDMapEntry(map, "af", (short) 0x0436); ! addLCIDMapEntry(map, "ka", (short) 0x0437); ! addLCIDMapEntry(map, "fo", (short) 0x0438); ! addLCIDMapEntry(map, "hi", (short) 0x0439); ! addLCIDMapEntry(map, "mt", (short) 0x043a); ! addLCIDMapEntry(map, "se", (short) 0x043b); ! addLCIDMapEntry(map, "gd", (short) 0x043c); ! addLCIDMapEntry(map, "ms", (short) 0x043e); ! addLCIDMapEntry(map, "kk", (short) 0x043f); ! addLCIDMapEntry(map, "ky", (short) 0x0440); ! addLCIDMapEntry(map, "sw", (short) 0x0441); ! addLCIDMapEntry(map, "tt", (short) 0x0444); ! addLCIDMapEntry(map, "bn", (short) 0x0445); ! addLCIDMapEntry(map, "pa", (short) 0x0446); ! addLCIDMapEntry(map, "gu", (short) 0x0447); ! addLCIDMapEntry(map, "ta", (short) 0x0449); ! addLCIDMapEntry(map, "te", (short) 0x044a); ! addLCIDMapEntry(map, "kn", (short) 0x044b); ! addLCIDMapEntry(map, "ml", (short) 0x044c); ! addLCIDMapEntry(map, "mr", (short) 0x044e); ! addLCIDMapEntry(map, "sa", (short) 0x044f); ! addLCIDMapEntry(map, "mn", (short) 0x0450); ! addLCIDMapEntry(map, "cy", (short) 0x0452); ! addLCIDMapEntry(map, "gl", (short) 0x0456); ! addLCIDMapEntry(map, "dv", (short) 0x0465); ! addLCIDMapEntry(map, "qu", (short) 0x046b); ! addLCIDMapEntry(map, "mi", (short) 0x0481); ! addLCIDMapEntry(map, "ar_IQ", (short) 0x0801); ! addLCIDMapEntry(map, "zh_CN", (short) 0x0804); ! addLCIDMapEntry(map, "de_CH", (short) 0x0807); ! addLCIDMapEntry(map, "en_GB", (short) 0x0809); ! addLCIDMapEntry(map, "es_MX", (short) 0x080a); ! addLCIDMapEntry(map, "fr_BE", (short) 0x080c); ! addLCIDMapEntry(map, "it_CH", (short) 0x0810); ! addLCIDMapEntry(map, "nl_BE", (short) 0x0813); ! addLCIDMapEntry(map, "no_NO_NY", (short) 0x0814); ! addLCIDMapEntry(map, "pt_PT", (short) 0x0816); ! addLCIDMapEntry(map, "ro_MD", (short) 0x0818); ! addLCIDMapEntry(map, "ru_MD", (short) 0x0819); ! addLCIDMapEntry(map, "sr_CS", (short) 0x081a); ! addLCIDMapEntry(map, "sv_FI", (short) 0x081d); ! addLCIDMapEntry(map, "az_AZ", (short) 0x082c); ! addLCIDMapEntry(map, "se_SE", (short) 0x083b); ! addLCIDMapEntry(map, "ga_IE", (short) 0x083c); ! addLCIDMapEntry(map, "ms_BN", (short) 0x083e); ! addLCIDMapEntry(map, "uz_UZ", (short) 0x0843); ! addLCIDMapEntry(map, "qu_EC", (short) 0x086b); ! addLCIDMapEntry(map, "ar_EG", (short) 0x0c01); ! addLCIDMapEntry(map, "zh_HK", (short) 0x0c04); ! addLCIDMapEntry(map, "de_AT", (short) 0x0c07); ! addLCIDMapEntry(map, "en_AU", (short) 0x0c09); ! addLCIDMapEntry(map, "fr_CA", (short) 0x0c0c); ! addLCIDMapEntry(map, "sr_CS", (short) 0x0c1a); ! addLCIDMapEntry(map, "se_FI", (short) 0x0c3b); ! addLCIDMapEntry(map, "qu_PE", (short) 0x0c6b); ! addLCIDMapEntry(map, "ar_LY", (short) 0x1001); ! addLCIDMapEntry(map, "zh_SG", (short) 0x1004); ! addLCIDMapEntry(map, "de_LU", (short) 0x1007); ! addLCIDMapEntry(map, "en_CA", (short) 0x1009); ! addLCIDMapEntry(map, "es_GT", (short) 0x100a); ! addLCIDMapEntry(map, "fr_CH", (short) 0x100c); ! addLCIDMapEntry(map, "hr_BA", (short) 0x101a); ! addLCIDMapEntry(map, "ar_DZ", (short) 0x1401); ! addLCIDMapEntry(map, "zh_MO", (short) 0x1404); ! addLCIDMapEntry(map, "de_LI", (short) 0x1407); ! addLCIDMapEntry(map, "en_NZ", (short) 0x1409); ! addLCIDMapEntry(map, "es_CR", (short) 0x140a); ! addLCIDMapEntry(map, "fr_LU", (short) 0x140c); ! addLCIDMapEntry(map, "bs_BA", (short) 0x141a); ! addLCIDMapEntry(map, "ar_MA", (short) 0x1801); ! addLCIDMapEntry(map, "en_IE", (short) 0x1809); ! addLCIDMapEntry(map, "es_PA", (short) 0x180a); ! addLCIDMapEntry(map, "fr_MC", (short) 0x180c); ! addLCIDMapEntry(map, "sr_BA", (short) 0x181a); ! addLCIDMapEntry(map, "ar_TN", (short) 0x1c01); ! addLCIDMapEntry(map, "en_ZA", (short) 0x1c09); ! addLCIDMapEntry(map, "es_DO", (short) 0x1c0a); ! addLCIDMapEntry(map, "sr_BA", (short) 0x1c1a); ! addLCIDMapEntry(map, "ar_OM", (short) 0x2001); ! addLCIDMapEntry(map, "en_JM", (short) 0x2009); ! addLCIDMapEntry(map, "es_VE", (short) 0x200a); ! addLCIDMapEntry(map, "ar_YE", (short) 0x2401); ! addLCIDMapEntry(map, "es_CO", (short) 0x240a); ! addLCIDMapEntry(map, "ar_SY", (short) 0x2801); ! addLCIDMapEntry(map, "en_BZ", (short) 0x2809); ! addLCIDMapEntry(map, "es_PE", (short) 0x280a); ! addLCIDMapEntry(map, "ar_JO", (short) 0x2c01); ! addLCIDMapEntry(map, "en_TT", (short) 0x2c09); ! addLCIDMapEntry(map, "es_AR", (short) 0x2c0a); ! addLCIDMapEntry(map, "ar_LB", (short) 0x3001); ! addLCIDMapEntry(map, "en_ZW", (short) 0x3009); ! addLCIDMapEntry(map, "es_EC", (short) 0x300a); ! addLCIDMapEntry(map, "ar_KW", (short) 0x3401); ! addLCIDMapEntry(map, "en_PH", (short) 0x3409); ! addLCIDMapEntry(map, "es_CL", (short) 0x340a); ! addLCIDMapEntry(map, "ar_AE", (short) 0x3801); ! addLCIDMapEntry(map, "es_UY", (short) 0x380a); ! addLCIDMapEntry(map, "ar_BH", (short) 0x3c01); ! addLCIDMapEntry(map, "es_PY", (short) 0x3c0a); ! addLCIDMapEntry(map, "ar_QA", (short) 0x4001); ! addLCIDMapEntry(map, "es_BO", (short) 0x400a); ! addLCIDMapEntry(map, "es_SV", (short) 0x440a); ! addLCIDMapEntry(map, "es_HN", (short) 0x480a); ! addLCIDMapEntry(map, "es_NI", (short) 0x4c0a); ! addLCIDMapEntry(map, "es_PR", (short) 0x500a); ! ! lcidMap = map; ! } ! ! public static int getNumFonts() { ! return physicalFonts.size()+maxCompFont; ! } ! ! private static boolean fontSupportsEncoding(Font font, String encoding) { ! return getFont2D(font).supportsEncoding(encoding); ! } ! ! public synchronized static native String getFontPath(boolean noType1Fonts); ! public synchronized static native void setNativeFontPath(String fontPath); ! ! ! private static Thread fileCloser = null; ! static Vector<File> tmpFontFiles = null; ! ! public static 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 static 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 static synchronized void deRegisterBadFont(Font2D font2D) { ! if (!(font2D instanceof PhysicalFont)) { ! /* We should never reach here, but just in case */ ! return; ! } else { ! if (logging) { ! logger.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 static 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 (logging) { ! logger.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 (logging) { ! logger.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 static 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 static Font2D findFont2DAllLocales(String name, int style) { ! ! if (logging) { ! logger.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 (FontManager.class) { ! 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(); ! public static boolean usingPerAppContextComposites = false; ! private static 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 static boolean gLocalePref = false; - private static 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. - */ - static boolean maybeUsingAlternateCompositeFonts() { - return usingAlternateComposites || usingPerAppContextComposites; - } - - public static 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 static synchronized void useAlternateFontforJALocales() { - - if (!isWindows) { - return; - } - - initSGEnv(); - if (!maybeMultiAppContext()) { - gAltJAFont = true; - } else { - AppContext appContext = AppContext.getAppContext(); - appContext.put(altJAFontKey, altJAFontKey); - } - } - - public static boolean usingAlternateFontforJALocales() { - if (!maybeMultiAppContext()) { - return gAltJAFont; - } else { - AppContext appContext = AppContext.getAppContext(); - return appContext.get(altJAFontKey) == altJAFontKey; - } - } - - public static synchronized void preferLocaleFonts() { - - initSGEnv(); - - /* Test if re-ordering will have any effect */ - if (!FontConfiguration.willReorderForStartupLocale()) { - return; - } - - if (!maybeMultiAppContext()) { - if (gLocalePref == true) { - return; - } - gLocalePref = true; - sgEnv.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; - sgEnv.createCompositeFonts(altNameCache, true, acPropPref); - } - } - - public static synchronized void preferProportionalFonts() { - - /* If no proportional fonts are configured, there's no need - * to take any action. - */ - if (!FontConfiguration.hasMonoToPropMap()) { - return; - } - - initSGEnv(); - - if (!maybeMultiAppContext()) { - if (gPropPref == true) { - return; - } - gPropPref = true; - sgEnv.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; - sgEnv.createCompositeFonts(altNameCache, acLocalePref, true); - } - } - - private static HashSet<String> installedNames = null; - private static HashSet<String> getInstalledNames() { - if (installedNames == null) { - Locale l = sgEnv.getSystemStartupLocale(); - String[] installedFamilies = sgEnv.getInstalledFontFamilyNames(l); - Font[] installedFonts = sgEnv.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 static Hashtable<String,FontFamily> createdByFamilyName; - private static Hashtable<String,Font2D> createdByFullName; - private static boolean fontsAreRegistered = false; - private static boolean fontsAreRegisteredPerAppContext = false; - - public static 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 (!isCreatedFont(font)) { - return false; - } - if (sgEnv == null) { - initSGEnv(); - } - /* 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 = sgEnv.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 = 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 static 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 static 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 = sgEnv.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 static 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 = sgEnv.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; - } - } - - /* Begin support for GTK Look and Feel - query libfontconfig and - * return a composite Font to Swing that uses the desktop font(s). - */ - - /* 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; - } - - /* 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", - }; - - /* These next three classes are just data structures. - */ - static class FontConfigFont { - String familyName; // eg Bitstream Vera Sans - String styleStr; // eg Bold - String fullName; // eg Bitstream Vera Sans Bold - String fontFile; // eg /usr/X11/lib/fonts/foo.ttf - } - - static class FcCompFont { - String fcName; // eg sans - String fcFamily; // eg sans - String jdkName; // eg sansserif - int style; // eg 0=PLAIN - FontConfigFont firstFont; - FontConfigFont[] allFonts; - //boolean preferBitmaps; // if embedded bitmaps preferred over AA - CompositeFont compFont; // null if not yet created/known. - } - - static class FontConfigInfo { - int fcVersion; - String[] cacheDirs = new String[4]; - } - - 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(); - - private static native int - getFontConfigAASettings(String locale, String fcFamily); - - /* 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 (isWindows) { - return null; - } else { - int hint = getFontConfigAASettings(getFCLocaleStr(), fcFamily); - if (hint < 0) { - return null; - } else { - return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, - hint); - } - } - } - - /* 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 populated by native */ - private static final FontConfigInfo fcInfo = new FontConfigInfo(); - - /* This array has the array elements created in Java code and is - * passed down to native to be filled in. - */ - private static FcCompFont[] 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); - - static void populateFontConfig(FcCompFont[] fcInfo) { - fontConfigFonts = fcInfo; - } - - static FcCompFont[] loadFontConfig() { - initFontConfigFonts(true); - return fontConfigFonts; - } - - static FontConfigInfo getFontConfigInfo() { - initFontConfigFonts(true); - return fcInfo; - } - - /* 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. - */ - private static synchronized void - initFontConfigFonts(boolean includeFallbacks) { - - if (fontConfigFonts != null) { - if (!includeFallbacks || (fontConfigFonts[0].allFonts != null)) { - return; - } - } - - if (isWindows || fontConfigFailed) { - return; - } - - long t0 = 0; - if (logging) { - t0 = System.nanoTime(); - } - - 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 = 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 (logging) { - logger.info("Fontconfig returned no fonts."); - } - fontConfigFailed = true; - return; - } - } - fontConfigFonts = fontArr; - - if (logging) { - 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); - } - } - } - } - } - - private static PhysicalFont registerFromFcInfo(FcCompFont fcInfo) { - - /* 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 = registeredFontFiles.get(fontFile); - if (physFont != null) { - if (isTTC) { - Font2D f2d = findFont2D(fcInfo.firstFont.familyName, - fcInfo.style, 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 = 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 && - deferredFontFiles.get(fontFile) != null) - { - physFont = initialiseDeferredFont(fcInfo.firstFont.fontFile); - /* use findFont2D to get the right font from TTC's */ - if (physFont != null) { - if (isTTC) { - Font2D f2d = findFont2D(fcInfo.firstFont.familyName, - fcInfo.style, 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 = FONTFORMAT_NONE; - int fontRank = Font2D.UNKNOWN_RANK; - - if (ext.equals(".ttf") || ext.equals(".otf") || isTTC) { - fontFormat = FONTFORMAT_TRUETYPE; - fontRank = Font2D.TTF_RANK; - } else if (ext.equals(".pfa") || ext.equals(".pfb")) { - fontFormat = FONTFORMAT_TYPE1; - fontRank = Font2D.TYPE1_RANK; - } - physFont = registerFontFile(fcInfo.firstFont.fontFile, null, - fontFormat, true, fontRank); - } - return physFont; - } - - private static String[] getPlatformFontDirs() { - 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. - */ - private static String[] defaultPlatformFont = null; - public static String[] getDefaultPlatformFont() { - - if (defaultPlatformFont != null) { - return defaultPlatformFont; - } - - String[] info = new String[2]; - if (isWindows) { - info[0] = "Arial"; - info[1] = "c:\\windows\\fonts"; - final String[] dirs = getPlatformFontDirs(); - 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"; - } else { - initFontConfigFonts(false); - 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; - } - - private FcCompFont getFcCompFont() { - initFontConfigFonts(false); - for (int i=0; i<fontConfigFonts.length; i++) { - if ("sans".equals(fontConfigFonts[i].fcFamily) && - 0 == fontConfigFonts[i].style) { - return fontConfigFonts[i]; - } - } - return null; - } - /* - * We need to return a Composite font which has as the font in - * its first slot one obtained from fontconfig. - */ - private static 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 (logging) { - logger.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. - */ - CompositeFont jdkFont = (CompositeFont) - findFont2D(fcInfo.jdkName, style, 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)); - } - - /* 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 mappedName = mapFcName(fcFamily); - if (mappedName == null) { - mappedName = "sansserif"; - } - - /* If GTK L&F were to be used on windows, we need to return - * something. Since on windows Swing won't have the code to - * call fontconfig, even if it is present, fcFamily and mapped - * name will default to sans and therefore sansserif so this - * should be fine. - */ - if (isWindows) { - return new FontUIResource(mappedName, style, size); - } - - CompositeFont font2D = getFontConfigFont(fcFamily, style); - if (font2D == null) { // Not expected, just a precaution. - return new FontUIResource(mappedName, 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); - setFont2D(fuir, font2D.handle); - setCreatedFont(fuir); - return fuir; - } - - /* The following fields and methods which relate to layout - * perhaps belong in some other class but FontManager is already - * widely used as an entry point for other JDK code that needs - * access to the font system internals. - */ - /** ! * 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; - /* 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'. - */ - 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; - } - - /* 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. - */ - static boolean isNonSimpleChar(char ch) { - return - isComplexCharCode(ch) || - (ch >= CharToGlyphMapper.HI_SURROGATE_START && - ch <= CharToGlyphMapper.LO_SURROGATE_END); - } - /** ! * 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; - } - - /** - * 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 = FontManager.getFont2D(font); - if (font2D instanceof TrueTypeFont) { - TrueTypeFont ttf = (TrueTypeFont)font2D; - return - ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null || - ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null; - } else { - return false; - } - } - - private static FontScaler nullScaler = null; - private static Constructor<FontScaler> scalerConstructor = null; - - //Find preferred font scaler - // - //NB: we can allow property based preferences - // (theoretically logic can be font type specific) - static { - Class scalerClass = null; - Class arglst[] = new Class[] {Font2D.class, int.class, - boolean.class, int.class}; - - try { - if (SunGraphicsEnvironment.isOpenJDK()) { - scalerClass = Class.forName("sun.font.FreetypeFontScaler"); - } else { - scalerClass = Class.forName("sun.font.T2KFontScaler"); - } - } catch (ClassNotFoundException e) { - scalerClass = NullFontScaler.class; - } - - //NB: rewrite using factory? constructor is ugly way - try { - scalerConstructor = scalerClass.getConstructor(arglst); - } catch (NoSuchMethodException e) { - //should not happen - } - } - - /* At the moment it is harmless to create 2 null scalers - so, technically, syncronized keyword is not needed. - - But it is safer to keep it to avoid subtle problems if we will be - adding checks like whether scaler is null scaler. */ - public synchronized static FontScaler getNullScaler() { - if (nullScaler == null) { - nullScaler = new NullFontScaler(); - } - return nullScaler; - } - - /* This is the only place to instantiate new FontScaler. - * Therefore this is very convinient place to register - * scaler with Disposer as well as trigger deregistring bad font - * in case when scaler reports this. - */ - - public static FontScaler getScaler(Font2D font, - int indexInCollection, - boolean supportsCJK, - int filesize) { - FontScaler scaler = null; - - try { - Object args[] = new Object[] {font, indexInCollection, - supportsCJK, filesize}; - scaler = scalerConstructor.newInstance(args); - Disposer.addObjectRecord(font, scaler); - } catch (Throwable e) { - scaler = nullScaler; - - //if we can not instantiate scaler assume bad font - //NB: technically it could be also because of internal scaler - // error but here we are assuming scaler is ok. - deRegisterBadFont(font); - } - return scaler; - } } --- 20,145 ---- * * 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.File; import java.util.Locale; import java.util.TreeMap; ! import javax.swing.plaf.FontUIResource; ! /** * Interface between Java Fonts (java.awt.Font) and the underlying * font files/native font resources and the Java and native font scalers. */ ! public interface FontManager { ! // These constants are used in findFont(). public static final int NO_FALLBACK = 0; public static final int PHYSICAL_FALLBACK = 1; public static final int LOGICAL_FALLBACK = 2; /** ! * Register a new font. Please, note that {@code null} is not a valid ! * argument, and it's caller's responsibility to ensure that, but to keep ! * compatibility, if {@code null} is passed as an argument, {@code false} ! * is returned, and no {@link NullPointerException} ! * is thrown. * ! * As additional note, an implementation should ensure that this font ! * cannot override existing installed fonts. * ! * @param font ! * @return {@code true} is the font is successfully registered, ! * {@code false} otherwise. */ ! public boolean registerFont(Font font); ! public void deRegisterBadFont(Font2D 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); ! /** ! * Creates a Font2D for the specified font file, that is expected ! * to be in the specified font format (according to the constants ! * in java.awt.Font). The parameter {@code isCopy} is set to true ! * when the specified font file is actually a copy of the font data ! * and needs to be deleted afterwards. This method is called ! * for the Font.createFont() methods. * ! * @param fontFile the file holding the font data ! * @param fontFormat the expected font format ! * @param isCopy {@code true} if the file is a copy and needs to be ! * deleted, {@code false} otherwise ! * ! * @return the created Font2D instance */ ! public Font2D createFont2D(File fontFile, int fontFormat, ! boolean isCopy, CreatedFontTracker tracker) ! throws FontFormatException; ! /** * If usingPerAppContextComposites is true, we are in "applet" * (eg browser) enviroment and at least one context has selected * an alternate composite font behaviour. */ ! public boolean usingPerAppContextComposites(); /** ! * Creates a derived composite font from the specified font (handle). ! * ! * @param family the font family of the derived font ! * @param style the font style of the derived font ! * @param handle the original font (handle) ! * ! * @return the handle for the derived font */ ! public Font2DHandle getNewComposite(String family, int style, ! Font2DHandle handle); /** ! * Indicates a preference for locale-specific fonts in the mapping of ! * logical fonts to physical fonts. Calling this method indicates that font ! * rendering should primarily use fonts specific to the primary writing ! * system (the one indicated by the default encoding and the initial ! * default locale). For example, if the primary writing system is ! * Japanese, then characters should be rendered using a Japanese font ! * if possible, and other fonts should only be used for characters for ! * which the Japanese font doesn't have glyphs. ! * <p> ! * The actual change in font rendering behavior resulting from a call ! * to this method is implementation dependent; it may have no effect at ! * all, or the requested behavior may already match the default behavior. ! * The behavior may differ between font rendering in lightweight ! * and peered components. Since calling this method requests a ! * different font, clients should expect different metrics, and may need ! * to recalculate window sizes and layout. Therefore this method should ! * be called before user interface initialisation. ! * ! * @see #preferProportionalFonts() ! * @since 1.5 */ ! public void preferLocaleFonts(); /** ! * preferLocaleFonts() and preferProportionalFonts() are called to inform ! * that the application could be using an alternate set of composite ! * fonts, and so the implementation should try to create a CompositeFonts ! * with this directive in mind. ! * ! * @see #preferLocaleFonts() */ ! public void preferProportionalFonts(); }