FontManager.java revision 0
0N/A/*
0N/A * Copyright 2003-2007 Sun Microsystems, Inc. All Rights Reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation. Sun designates this
0N/A * particular file as subject to the "Classpath" exception as provided
0N/A * by Sun in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
0N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0N/A * CA 95054 USA or visit www.sun.com if you need additional information or
0N/A * have any questions.
0N/A */
0N/A
0N/Apackage sun.font;
0N/A
0N/Aimport java.awt.Font;
0N/Aimport java.awt.GraphicsEnvironment;
0N/Aimport java.awt.FontFormatException;
0N/Aimport java.io.File;
0N/Aimport java.io.FilenameFilter;
0N/Aimport java.security.AccessController;
0N/Aimport java.security.PrivilegedAction;
0N/Aimport java.util.ArrayList;
0N/Aimport java.util.HashMap;
0N/Aimport java.util.HashSet;
0N/Aimport java.util.Hashtable;
0N/Aimport java.util.Iterator;
0N/Aimport java.util.Locale;
0N/Aimport java.util.Map;
0N/Aimport java.util.NoSuchElementException;
0N/Aimport java.util.StringTokenizer;
0N/Aimport java.util.TreeMap;
0N/Aimport java.util.Vector;
0N/Aimport java.util.concurrent.ConcurrentHashMap;
0N/Aimport java.util.logging.Level;
0N/Aimport java.util.logging.Logger;
0N/Aimport javax.swing.plaf.FontUIResource;
0N/A
0N/Aimport sun.awt.AppContext;
0N/Aimport sun.awt.FontConfiguration;
0N/Aimport sun.awt.SunHints;
0N/Aimport sun.awt.SunToolkit;
0N/Aimport sun.java2d.HeadlessGraphicsEnvironment;
0N/Aimport sun.java2d.SunGraphicsEnvironment;
0N/A
0N/Aimport java.awt.geom.GeneralPath;
0N/Aimport java.awt.geom.Point2D;
0N/Aimport java.awt.geom.Rectangle2D;
0N/A
0N/Aimport java.lang.reflect.Constructor;
0N/A
0N/Aimport sun.java2d.Disposer;
0N/A
0N/A/*
0N/A * Interface between Java Fonts (java.awt.Font) and the underlying
0N/A * font files/native font resources and the Java and native font scalers.
0N/A */
0N/Apublic final class FontManager {
0N/A
0N/A public static final int FONTFORMAT_NONE = -1;
0N/A public static final int FONTFORMAT_TRUETYPE = 0;
0N/A public static final int FONTFORMAT_TYPE1 = 1;
0N/A public static final int FONTFORMAT_T2K = 2;
0N/A public static final int FONTFORMAT_TTC = 3;
0N/A public static final int FONTFORMAT_COMPOSITE = 4;
0N/A public static final int FONTFORMAT_NATIVE = 5;
0N/A
0N/A public static final int NO_FALLBACK = 0;
0N/A public static final int PHYSICAL_FALLBACK = 1;
0N/A public static final int LOGICAL_FALLBACK = 2;
0N/A
0N/A public static final int QUADPATHTYPE = 1;
0N/A public static final int CUBICPATHTYPE = 2;
0N/A
0N/A /* Pool of 20 font file channels chosen because some UTF-8 locale
0N/A * composite fonts can use up to 16 platform fonts (including the
0N/A * Lucida fall back). This should prevent channel thrashing when
0N/A * dealing with one of these fonts.
0N/A * The pool array stores the fonts, rather than directly referencing
0N/A * the channels, as the font needs to do the open/close work.
0N/A */
0N/A private static final int CHANNELPOOLSIZE = 20;
0N/A private static int lastPoolIndex = 0;
0N/A private static int poolSize = 0;
0N/A private static FileFont fontFileCache[] = new FileFont[CHANNELPOOLSIZE];
0N/A
0N/A /* Need to implement a simple linked list scheme for fast
0N/A * traversal and lookup.
0N/A * Also want to "fast path" dialog so there's minimal overhead.
0N/A */
0N/A /* There are at exactly 20 composite fonts: 5 faces (but some are not
0N/A * usually different), in 4 styles. The array may be auto-expanded
0N/A * later if more are needed, eg for user-defined composites or locale
0N/A * variants.
0N/A */
0N/A private static int maxCompFont = 0;
0N/A private static CompositeFont [] compFonts = new CompositeFont[20];
0N/A private static ConcurrentHashMap<String, CompositeFont>
0N/A compositeFonts = new ConcurrentHashMap<String, CompositeFont>();
0N/A private static ConcurrentHashMap<String, PhysicalFont>
0N/A physicalFonts = new ConcurrentHashMap<String, PhysicalFont>();
0N/A private static ConcurrentHashMap<String, PhysicalFont>
0N/A registeredFontFiles = new ConcurrentHashMap<String, PhysicalFont>();
0N/A
0N/A /* given a full name find the Font. Remind: there's duplication
0N/A * here in that this contains the content of compositeFonts +
0N/A * physicalFonts.
0N/A */
0N/A private static ConcurrentHashMap<String, Font2D>
0N/A fullNameToFont = new ConcurrentHashMap<String, Font2D>();
0N/A
0N/A /* TrueType fonts have localised names. Support searching all
0N/A * of these before giving up on a name.
0N/A */
0N/A private static HashMap<String, TrueTypeFont> localeFullNamesToFont;
0N/A
0N/A private static PhysicalFont defaultPhysicalFont;
0N/A
0N/A /* deprecated, unsupported hack - actually invokes a bug! */
0N/A private static boolean usePlatformFontMetrics = false;
0N/A
0N/A public static Logger logger = null;
0N/A public static boolean logging;
0N/A static boolean longAddresses;
0N/A static String osName;
0N/A static boolean useT2K;
0N/A static boolean isWindows;
0N/A static boolean isSolaris;
0N/A public static boolean isSolaris8; // needed to check for JA wavedash fix.
0N/A public static boolean isSolaris9; // needed to check for songti font usage.
0N/A private static boolean loaded1dot0Fonts = false;
0N/A static SunGraphicsEnvironment sgEnv;
0N/A static boolean loadedAllFonts = false;
0N/A static boolean loadedAllFontFiles = false;
0N/A static TrueTypeFont eudcFont;
0N/A static HashMap<String,String> jreFontMap;
0N/A static HashSet<String> jreLucidaFontFiles;
0N/A static String[] jreOtherFontFiles;
0N/A static boolean noOtherJREFontFiles = false; // initial assumption.
0N/A
0N/A /* Used to indicate required return type from toArray(..); */
0N/A private static String[] STR_ARRAY = new String[0];
0N/A
0N/A private static void initJREFontMap() {
0N/A
0N/A /* Key is familyname+style value as an int.
0N/A * Value is filename containing the font.
0N/A * If no mapping exists, it means there is no font file for the style
0N/A * If the mapping exists but the file doesn't exist in the deferred
0N/A * list then it means its not installed.
0N/A * This looks like a lot of code and strings but if it saves even
0N/A * a single file being opened at JRE start-up there's a big payoff.
0N/A * Lucida Sans is probably the only important case as the others
0N/A * are rarely used. Consider removing the other mappings if there's
0N/A * no evidence they are useful in practice.
0N/A */
0N/A jreFontMap = new HashMap<String,String>();
0N/A jreLucidaFontFiles = new HashSet<String>();
0N/A if (SunGraphicsEnvironment.isOpenJDK()) {
0N/A return;
0N/A }
0N/A /* Lucida Sans Family */
0N/A jreFontMap.put("lucida sans0", "LucidaSansRegular.ttf");
0N/A jreFontMap.put("lucida sans1", "LucidaSansDemiBold.ttf");
0N/A /* Lucida Sans full names (map Bold and DemiBold to same file) */
0N/A jreFontMap.put("lucida sans regular0", "LucidaSansRegular.ttf");
0N/A jreFontMap.put("lucida sans regular1", "LucidaSansDemiBold.ttf");
0N/A jreFontMap.put("lucida sans bold1", "LucidaSansDemiBold.ttf");
0N/A jreFontMap.put("lucida sans demibold1", "LucidaSansDemiBold.ttf");
0N/A
0N/A /* Lucida Sans Typewriter Family */
0N/A jreFontMap.put("lucida sans typewriter0",
0N/A "LucidaTypewriterRegular.ttf");
0N/A jreFontMap.put("lucida sans typewriter1", "LucidaTypewriterBold.ttf");
0N/A /* Typewriter full names (map Bold and DemiBold to same file) */
0N/A jreFontMap.put("lucida sans typewriter regular0",
0N/A "LucidaTypewriter.ttf");
0N/A jreFontMap.put("lucida sans typewriter regular1",
0N/A "LucidaTypewriterBold.ttf");
0N/A jreFontMap.put("lucida sans typewriter bold1",
0N/A "LucidaTypewriterBold.ttf");
0N/A jreFontMap.put("lucida sans typewriter demibold1",
0N/A "LucidaTypewriterBold.ttf");
0N/A
0N/A /* Lucida Bright Family */
0N/A jreFontMap.put("lucida bright0", "LucidaBrightRegular.ttf");
0N/A jreFontMap.put("lucida bright1", "LucidaBrightDemiBold.ttf");
0N/A jreFontMap.put("lucida bright2", "LucidaBrightItalic.ttf");
0N/A jreFontMap.put("lucida bright3", "LucidaBrightDemiItalic.ttf");
0N/A /* Lucida Bright full names (map Bold and DemiBold to same file) */
0N/A jreFontMap.put("lucida bright regular0", "LucidaBrightRegular.ttf");
0N/A jreFontMap.put("lucida bright regular1", "LucidaBrightDemiBold.ttf");
0N/A jreFontMap.put("lucida bright regular2", "LucidaBrightItalic.ttf");
0N/A jreFontMap.put("lucida bright regular3", "LucidaBrightDemiItalic.ttf");
0N/A jreFontMap.put("lucida bright bold1", "LucidaBrightDemiBold.ttf");
0N/A jreFontMap.put("lucida bright bold3", "LucidaBrightDemiItalic.ttf");
0N/A jreFontMap.put("lucida bright demibold1", "LucidaBrightDemiBold.ttf");
0N/A jreFontMap.put("lucida bright demibold3","LucidaBrightDemiItalic.ttf");
0N/A jreFontMap.put("lucida bright italic2", "LucidaBrightItalic.ttf");
0N/A jreFontMap.put("lucida bright italic3", "LucidaBrightDemiItalic.ttf");
0N/A jreFontMap.put("lucida bright bold italic3",
0N/A "LucidaBrightDemiItalic.ttf");
0N/A jreFontMap.put("lucida bright demibold italic3",
0N/A "LucidaBrightDemiItalic.ttf");
0N/A for (String ffile : jreFontMap.values()) {
0N/A jreLucidaFontFiles.add(ffile);
0N/A }
0N/A }
0N/A
0N/A static {
0N/A
0N/A if (SunGraphicsEnvironment.debugFonts) {
0N/A logger = Logger.getLogger("sun.java2d", null);
0N/A logging = logger.getLevel() != Level.OFF;
0N/A }
0N/A initJREFontMap();
0N/A
0N/A java.security.AccessController.doPrivileged(
0N/A new java.security.PrivilegedAction() {
0N/A public Object run() {
0N/A FontManagerNativeLibrary.load();
0N/A
0N/A // JNI throws an exception if a class/method/field is not found,
0N/A // so there's no need to do anything explicit here.
0N/A initIDs();
0N/A
0N/A switch (StrikeCache.nativeAddressSize) {
0N/A case 8: longAddresses = true; break;
0N/A case 4: longAddresses = false; break;
0N/A default: throw new RuntimeException("Unexpected address size");
0N/A }
0N/A
0N/A osName = System.getProperty("os.name", "unknownOS");
0N/A isSolaris = osName.startsWith("SunOS");
0N/A
0N/A if (isSolaris) {
0N/A String t2kStr= System.getProperty("sun.java2d.font.scaler");
0N/A useT2K = "t2k".equals(t2kStr);
0N/A String version = System.getProperty("os.version", "unk");
0N/A isSolaris8 = version.equals("5.8");
0N/A isSolaris9 = version.equals("5.9");
0N/A } else {
0N/A isWindows = osName.startsWith("Windows");
0N/A if (isWindows) {
0N/A String eudcFile =
0N/A SunGraphicsEnvironment.eudcFontFileName;
0N/A if (eudcFile != null) {
0N/A try {
0N/A eudcFont = new TrueTypeFont(eudcFile, null, 0,
0N/A true);
0N/A } catch (FontFormatException e) {
0N/A }
0N/A }
0N/A String prop =
0N/A System.getProperty("java2d.font.usePlatformFont");
0N/A if (("true".equals(prop) || getPlatformFontVar())) {
0N/A usePlatformFontMetrics = true;
0N/A System.out.println("Enabling platform font metrics for win32. This is an unsupported option.");
0N/A System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases.");
0N/A System.out.println("It is appropriate only for use by applications which do not use any Java 2");
0N/A System.out.println("functionality. This property will be removed in a later release.");
0N/A }
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A });
0N/A }
0N/A
0N/A /* Initialise ptrs used by JNI methods */
0N/A private static native void initIDs();
0N/A
0N/A public static void addToPool(FileFont font) {
0N/A boolean added = false;
0N/A synchronized (fontFileCache) {
0N/A /* use poolSize to quickly detect if there's any free slots.
0N/A * This is a performance tweak based on the assumption that
0N/A * if this is executed at all often, its because there are many
0N/A * fonts being used and the pool will be full, and we will save
0N/A * a fruitless iteration
0N/A */
0N/A if (poolSize < CHANNELPOOLSIZE) {
0N/A for (int i=0; i<CHANNELPOOLSIZE; i++) {
0N/A if (fontFileCache[i] == null) {
0N/A fontFileCache[i] = font;
0N/A poolSize++;
0N/A added = true;
0N/A break;
0N/A }
0N/A }
0N/A assert added;
0N/A } else {
0N/A // is it possible for this to be the same font?
0N/A assert fontFileCache[lastPoolIndex] != font;
0N/A /* replace with new font, poolSize is unchanged. */
0N/A fontFileCache[lastPoolIndex].close();
0N/A fontFileCache[lastPoolIndex] = font;
0N/A /* lastPoolIndex is updated so that the least recently opened
0N/A * file will be closed next.
0N/A */
0N/A lastPoolIndex = (lastPoolIndex+1) % CHANNELPOOLSIZE;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /*
0N/A * In the normal course of events, the pool of fonts can remain open
0N/A * ready for quick access to their contents. The pool is sized so
0N/A * that it is not an excessive consumer of system resources whilst
0N/A * facilitating performance by providing ready access to the most
0N/A * recently used set of font files.
0N/A * The only reason to call removeFromPool(..) is for a Font that
0N/A * you want to to have GC'd. Currently this would apply only to fonts
0N/A * created with java.awt.Font.createFont(..).
0N/A * In this case, the caller is expected to have arranged for the file
0N/A * to be closed.
0N/A * REMIND: consider how to know when a createFont created font should
0N/A * be closed.
0N/A */
0N/A public static void removeFromPool(FileFont font) {
0N/A synchronized (fontFileCache) {
0N/A for (int i=0; i<CHANNELPOOLSIZE; i++) {
0N/A if (fontFileCache[i] == font) {
0N/A fontFileCache[i] = null;
0N/A poolSize--;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * This method is provided for internal and exclusive use by Swing.
0N/A *
0N/A * @param font representing a physical font.
0N/A * @return true if the underlying font is a TrueType or OpenType font
0N/A * that claims to support the Microsoft Windows encoding corresponding to
0N/A * the default file.encoding property of this JRE instance.
0N/A * This narrow value is useful for Swing to decide if the font is useful
0N/A * for the the Windows Look and Feel, or, if a composite font should be
0N/A * used instead.
0N/A * The information used to make the decision is obtained from
0N/A * the ulCodePageRange fields in the font.
0N/A * A caller can use isLogicalFont(Font) in this class before calling
0N/A * this method and would not need to call this method if that
0N/A * returns true.
0N/A */
0N/A// static boolean fontSupportsDefaultEncoding(Font font) {
0N/A// String encoding =
0N/A// (String) java.security.AccessController.doPrivileged(
0N/A// new sun.security.action.GetPropertyAction("file.encoding"));
0N/A
0N/A// if (encoding == null || font == null) {
0N/A// return false;
0N/A// }
0N/A
0N/A// encoding = encoding.toLowerCase(Locale.ENGLISH);
0N/A
0N/A// return FontManager.fontSupportsEncoding(font, encoding);
0N/A// }
0N/A
0N/A /* Revise the implementation to in fact mean "font is a composite font.
0N/A * This ensures that Swing components will always benefit from the
0N/A * fall back fonts
0N/A */
0N/A public static boolean fontSupportsDefaultEncoding(Font font) {
0N/A return getFont2D(font) instanceof CompositeFont;
0N/A }
0N/A
0N/A /**
0N/A * This method is provided for internal and exclusive use by Swing.
0N/A *
0N/A * It may be used in conjunction with fontSupportsDefaultEncoding(Font)
0N/A * In the event that a desktop properties font doesn't directly
0N/A * support the default encoding, (ie because the host OS supports
0N/A * adding support for the current locale automatically for native apps),
0N/A * then Swing calls this method to get a font which uses the specified
0N/A * font for the code points it covers, but also supports this locale
0N/A * just as the standard composite fonts do.
0N/A * Note: this will over-ride any setting where an application
0N/A * specifies it prefers locale specific composite fonts.
0N/A * The logic for this, is that this method is used only where the user or
0N/A * application has specified that the native L&F be used, and that
0N/A * we should honour that request to use the same font as native apps use.
0N/A *
0N/A * The behaviour of this method is to construct a new composite
0N/A * Font object that uses the specified physical font as its first
0N/A * component, and adds all the components of "dialog" as fall back
0N/A * components.
0N/A * The method currently assumes that only the size and style attributes
0N/A * are set on the specified font. It doesn't copy the font transform or
0N/A * other attributes because they aren't set on a font created from
0N/A * the desktop. This will need to be fixed if use is broadened.
0N/A *
0N/A * Operations such as Font.deriveFont will work properly on the
0N/A * font returned by this method for deriving a different point size.
0N/A * Additionally it tries to support a different style by calling
0N/A * getNewComposite() below. That also supports replacing slot zero
0N/A * with a different physical font but that is expected to be "rare".
0N/A * Deriving with a different style is needed because its been shown
0N/A * that some applications try to do this for Swing FontUIResources.
0N/A * Also operations such as new Font(font.getFontName(..), Font.PLAIN, 14);
0N/A * will NOT yield the same result, as the new underlying CompositeFont
0N/A * cannot be "looked up" in the font registry.
0N/A * This returns a FontUIResource as that is the Font sub-class needed
0N/A * by Swing.
0N/A * Suggested usage is something like :
0N/A * FontUIResource fuir;
0N/A * Font desktopFont = getDesktopFont(..);
0N/A * // NOTE even if fontSupportsDefaultEncoding returns true because
0N/A * // you get Tahoma and are running in an English locale, you may
0N/A * // still want to just call getCompositeFontUIResource() anyway
0N/A * // as only then will you get fallback fonts - eg for CJK.
0N/A * if (FontManager.fontSupportsDefaultEncoding(desktopFont)) {
0N/A * fuir = new FontUIResource(..);
0N/A * } else {
0N/A * fuir = FontManager.getCompositeFontUIResource(desktopFont);
0N/A * }
0N/A * return fuir;
0N/A */
0N/A public static FontUIResource getCompositeFontUIResource(Font font) {
0N/A
0N/A FontUIResource fuir =
0N/A new FontUIResource(font.getName(),font.getStyle(),font.getSize());
0N/A Font2D font2D = getFont2D(font);
0N/A
0N/A if (!(font2D instanceof PhysicalFont)) {
0N/A /* Swing should only be calling this when a font is obtained
0N/A * from desktop properties, so should generally be a physical font,
0N/A * an exception might be for names like "MS Serif" which are
0N/A * automatically mapped to "Serif", so there's no need to do
0N/A * anything special in that case. But note that suggested usage
0N/A * is first to call fontSupportsDefaultEncoding(Font) and this
0N/A * method should not be called if that were to return true.
0N/A */
0N/A return fuir;
0N/A }
0N/A
0N/A CompositeFont dialog2D =
0N/A (CompositeFont)findFont2D("dialog", font.getStyle(), NO_FALLBACK);
0N/A if (dialog2D == null) { /* shouldn't happen */
0N/A return fuir;
0N/A }
0N/A PhysicalFont physicalFont = (PhysicalFont)font2D;
0N/A CompositeFont compFont = new CompositeFont(physicalFont, dialog2D);
0N/A setFont2D(fuir, compFont.handle);
0N/A /* marking this as a created font is needed as only created fonts
0N/A * copy their creator's handles.
0N/A */
0N/A setCreatedFont(fuir);
0N/A return fuir;
0N/A }
0N/A
0N/A public static Font2DHandle getNewComposite(String family, int style,
0N/A Font2DHandle handle) {
0N/A
0N/A if (!(handle.font2D instanceof CompositeFont)) {
0N/A return handle;
0N/A }
0N/A
0N/A CompositeFont oldComp = (CompositeFont)handle.font2D;
0N/A PhysicalFont oldFont = oldComp.getSlotFont(0);
0N/A
0N/A if (family == null) {
0N/A family = oldFont.getFamilyName(null);
0N/A }
0N/A if (style == -1) {
0N/A style = oldComp.getStyle();
0N/A }
0N/A
0N/A Font2D newFont = findFont2D(family, style, NO_FALLBACK);
0N/A if (!(newFont instanceof PhysicalFont)) {
0N/A newFont = oldFont;
0N/A }
0N/A PhysicalFont physicalFont = (PhysicalFont)newFont;
0N/A CompositeFont dialog2D =
0N/A (CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
0N/A if (dialog2D == null) { /* shouldn't happen */
0N/A return handle;
0N/A }
0N/A CompositeFont compFont = new CompositeFont(physicalFont, dialog2D);
0N/A Font2DHandle newHandle = new Font2DHandle(compFont);
0N/A return newHandle;
0N/A }
0N/A
0N/A public static native void setFont2D(Font font, Font2DHandle font2DHandle);
0N/A
0N/A private static native boolean isCreatedFont(Font font);
0N/A private static native void setCreatedFont(Font font);
0N/A
0N/A public static void registerCompositeFont(String compositeName,
0N/A String[] componentFileNames,
0N/A String[] componentNames,
0N/A int numMetricsSlots,
0N/A int[] exclusionRanges,
0N/A int[] exclusionMaxIndex,
0N/A boolean defer) {
0N/A
0N/A CompositeFont cf = new CompositeFont(compositeName,
0N/A componentFileNames,
0N/A componentNames,
0N/A numMetricsSlots,
0N/A exclusionRanges,
0N/A exclusionMaxIndex, defer);
0N/A addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK);
0N/A synchronized (compFonts) {
0N/A compFonts[maxCompFont++] = cf;
0N/A }
0N/A }
0N/A
0N/A /* This variant is used only when the application specifies
0N/A * a variant of composite fonts which prefers locale specific or
0N/A * proportional fonts.
0N/A */
0N/A public static void registerCompositeFont(String compositeName,
0N/A String[] componentFileNames,
0N/A String[] componentNames,
0N/A int numMetricsSlots,
0N/A int[] exclusionRanges,
0N/A int[] exclusionMaxIndex,
0N/A boolean defer,
0N/A ConcurrentHashMap<String, Font2D>
0N/A altNameCache) {
0N/A
0N/A CompositeFont cf = new CompositeFont(compositeName,
0N/A componentFileNames,
0N/A componentNames,
0N/A numMetricsSlots,
0N/A exclusionRanges,
0N/A exclusionMaxIndex, defer);
0N/A /* if the cache has an existing composite for this case, make
0N/A * its handle point to this new font.
0N/A * This ensures that when the altNameCache that is passed in
0N/A * is the global mapNameCache - ie we are running as an application -
0N/A * that any statically created java.awt.Font instances which already
0N/A * have a Font2D instance will have that re-directed to the new Font
0N/A * on subsequent uses. This is particularly important for "the"
0N/A * default font instance, or similar cases where a UI toolkit (eg
0N/A * Swing) has cached a java.awt.Font. Note that if Swing is using
0N/A * a custom composite APIs which update the standard composites have
0N/A * no effect - this is typically the case only when using the Windows
0N/A * L&F where these APIs would conflict with that L&F anyway.
0N/A */
0N/A Font2D oldFont = (Font2D)
0N/A altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH));
0N/A if (oldFont instanceof CompositeFont) {
0N/A oldFont.handle.font2D = cf;
0N/A }
0N/A altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf);
0N/A }
0N/A
0N/A private static void addCompositeToFontList(CompositeFont f, int rank) {
0N/A
0N/A if (logging) {
0N/A logger.info("Add to Family "+ f.familyName +
0N/A ", Font " + f.fullName + " rank="+rank);
0N/A }
0N/A f.setRank(rank);
0N/A compositeFonts.put(f.fullName, f);
0N/A fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f);
0N/A
0N/A FontFamily family = FontFamily.getFamily(f.familyName);
0N/A if (family == null) {
0N/A family = new FontFamily(f.familyName, true, rank);
0N/A }
0N/A family.setFont(f, f.style);
0N/A }
0N/A
0N/A /*
0N/A * Systems may have fonts with the same name.
0N/A * We want to register only one of such fonts (at least until
0N/A * such time as there might be APIs which can accommodate > 1).
0N/A * Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts,
0N/A * 4) Type1 fonts, 5) native fonts.
0N/A *
0N/A * If the new font has the same name as the old font, the higher
0N/A * ranked font gets added, replacing the lower ranked one.
0N/A * If the fonts are of equal rank, then make a special case of
0N/A * font configuration rank fonts, which are on closer inspection,
0N/A * OT/TT fonts such that the larger font is registered. This is
0N/A * a heuristic since a font may be "larger" in the sense of more
0N/A * code points, or be a larger "file" because it has more bitmaps.
0N/A * So it is possible that using filesize may lead to less glyphs, and
0N/A * using glyphs may lead to lower quality display. Probably number
0N/A * of glyphs is the ideal, but filesize is information we already
0N/A * have and is good enough for the known cases.
0N/A * Also don't want to register fonts that match JRE font families
0N/A * but are coming from a source other than the JRE.
0N/A * This will ensure that we will algorithmically style the JRE
0N/A * plain font and get the same set of glyphs for all styles.
0N/A *
0N/A * Note that this method returns a value
0N/A * if it returns the same object as its argument that means this
0N/A * font was newly registered.
0N/A * If it returns a different object it means this font already exists,
0N/A * and you should use that one.
0N/A * If it returns null means this font was not registered and none
0N/A * in that name is registered. The caller must find a substitute
0N/A */
0N/A private static PhysicalFont addToFontList(PhysicalFont f, int rank) {
0N/A
0N/A String fontName = f.fullName;
0N/A String familyName = f.familyName;
0N/A if (fontName == null || "".equals(fontName)) {
0N/A return null;
0N/A }
0N/A if (compositeFonts.containsKey(fontName)) {
0N/A /* Don't register any font that has the same name as a composite */
0N/A return null;
0N/A }
0N/A f.setRank(rank);
0N/A if (!physicalFonts.containsKey(fontName)) {
0N/A if (logging) {
0N/A logger.info("Add to Family "+familyName +
0N/A ", Font " + fontName + " rank="+rank);
0N/A }
0N/A physicalFonts.put(fontName, f);
0N/A FontFamily family = FontFamily.getFamily(familyName);
0N/A if (family == null) {
0N/A family = new FontFamily(familyName, false, rank);
0N/A family.setFont(f, f.style);
0N/A } else if (family.getRank() >= rank) {
0N/A family.setFont(f, f.style);
0N/A }
0N/A fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f);
0N/A return f;
0N/A } else {
0N/A PhysicalFont newFont = f;
0N/A PhysicalFont oldFont = physicalFonts.get(fontName);
0N/A if (oldFont == null) {
0N/A return null;
0N/A }
0N/A /* If the new font is of an equal or higher rank, it is a
0N/A * candidate to replace the current one, subject to further tests.
0N/A */
0N/A if (oldFont.getRank() >= rank) {
0N/A
0N/A /* All fonts initialise their mapper when first
0N/A * used. If the mapper is non-null then this font
0N/A * has been accessed at least once. In that case
0N/A * do not replace it. This may be overly stringent,
0N/A * but its probably better not to replace a font that
0N/A * someone is already using without a compelling reason.
0N/A * Additionally the primary case where it is known
0N/A * this behaviour is important is in certain composite
0N/A * fonts, and since all the components of a given
0N/A * composite are usually initialised together this
0N/A * is unlikely. For this to be a problem, there would
0N/A * have to be a case where two different composites used
0N/A * different versions of the same-named font, and they
0N/A * were initialised and used at separate times.
0N/A * In that case we continue on and allow the new font to
0N/A * be installed, but replaceFont will continue to allow
0N/A * the original font to be used in Composite fonts.
0N/A */
0N/A if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) {
0N/A return oldFont;
0N/A }
0N/A
0N/A /* Normally we require a higher rank to replace a font,
0N/A * but as a special case, if the two fonts are the same rank,
0N/A * and are instances of TrueTypeFont we want the
0N/A * more complete (larger) one.
0N/A */
0N/A if (oldFont.getRank() == rank) {
0N/A if (oldFont instanceof TrueTypeFont &&
0N/A newFont instanceof TrueTypeFont) {
0N/A TrueTypeFont oldTTFont = (TrueTypeFont)oldFont;
0N/A TrueTypeFont newTTFont = (TrueTypeFont)newFont;
0N/A if (oldTTFont.fileSize >= newTTFont.fileSize) {
0N/A return oldFont;
0N/A }
0N/A } else {
0N/A return oldFont;
0N/A }
0N/A }
0N/A /* Don't replace ever JRE fonts.
0N/A * This test is in case a font configuration references
0N/A * a Lucida font, which has been mapped to a Lucida
0N/A * from the host O/S. The assumption here is that any
0N/A * such font configuration file is probably incorrect, or
0N/A * the host O/S version is for the use of AWT.
0N/A * In other words if we reach here, there's a possible
0N/A * problem with our choice of font configuration fonts.
0N/A */
0N/A if (oldFont.platName.startsWith(
0N/A SunGraphicsEnvironment.jreFontDirName)) {
0N/A if (logging) {
0N/A logger.warning("Unexpected attempt to replace a JRE " +
0N/A " font " + fontName + " from " +
0N/A oldFont.platName +
0N/A " with " + newFont.platName);
0N/A }
0N/A return oldFont;
0N/A }
0N/A
0N/A if (logging) {
0N/A logger.info("Replace in Family " + familyName +
0N/A ",Font " + fontName + " new rank="+rank +
0N/A " from " + oldFont.platName +
0N/A " with " + newFont.platName);
0N/A }
0N/A replaceFont(oldFont, newFont);
0N/A physicalFonts.put(fontName, newFont);
0N/A fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH),
0N/A newFont);
0N/A
0N/A FontFamily family = FontFamily.getFamily(familyName);
0N/A if (family == null) {
0N/A family = new FontFamily(familyName, false, rank);
0N/A family.setFont(newFont, newFont.style);
0N/A } else if (family.getRank() >= rank) {
0N/A family.setFont(newFont, newFont.style);
0N/A }
0N/A return newFont;
0N/A } else {
0N/A return oldFont;
0N/A }
0N/A }
0N/A }
0N/A
0N/A public static Font2D[] getRegisteredFonts() {
0N/A PhysicalFont[] physFonts = getPhysicalFonts();
0N/A int mcf = maxCompFont; /* for MT-safety */
0N/A Font2D[] regFonts = new Font2D[physFonts.length+mcf];
0N/A System.arraycopy(compFonts, 0, regFonts, 0, mcf);
0N/A System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length);
0N/A return regFonts;
0N/A }
0N/A
0N/A public static PhysicalFont[] getPhysicalFonts() {
0N/A return physicalFonts.values().toArray(new PhysicalFont[0]);
0N/A }
0N/A
0N/A
0N/A /* The class FontRegistrationInfo is used when a client says not
0N/A * to register a font immediately. This mechanism is used to defer
0N/A * initialisation of all the components of composite fonts at JRE
0N/A * start-up. The CompositeFont class is "aware" of this and when it
0N/A * is first used it asks for the registration of its components.
0N/A * Also in the event that any physical font is requested the
0N/A * deferred fonts are initialised before triggering a search of the
0N/A * system.
0N/A * Two maps are used. One to track the deferred fonts. The
0N/A * other to track the fonts that have been initialised through this
0N/A * mechanism.
0N/A */
0N/A
0N/A private static final class FontRegistrationInfo {
0N/A
0N/A String fontFilePath;
0N/A String[] nativeNames;
0N/A int fontFormat;
0N/A boolean javaRasterizer;
0N/A int fontRank;
0N/A
0N/A FontRegistrationInfo(String fontPath, String[] names, int format,
0N/A boolean useJavaRasterizer, int rank) {
0N/A this.fontFilePath = fontPath;
0N/A this.nativeNames = names;
0N/A this.fontFormat = format;
0N/A this.javaRasterizer = useJavaRasterizer;
0N/A this.fontRank = rank;
0N/A }
0N/A }
0N/A
0N/A private static final ConcurrentHashMap<String, FontRegistrationInfo>
0N/A deferredFontFiles =
0N/A new ConcurrentHashMap<String, FontRegistrationInfo>();
0N/A private static final ConcurrentHashMap<String, Font2DHandle>
0N/A initialisedFonts = new ConcurrentHashMap<String, Font2DHandle>();
0N/A
0N/A /* Remind: possibly enhance initialiseDeferredFonts() to be
0N/A * optionally given a name and a style and it could stop when it
0N/A * finds that font - but this would be a problem if two of the
0N/A * fonts reference the same font face name (cf the Solaris
0N/A * euro fonts).
0N/A */
0N/A public static synchronized void initialiseDeferredFonts() {
0N/A for (String fileName : deferredFontFiles.keySet()) {
0N/A initialiseDeferredFont(fileName);
0N/A }
0N/A }
0N/A
0N/A public static synchronized void registerDeferredJREFonts(String jreDir) {
0N/A for (FontRegistrationInfo info : deferredFontFiles.values()) {
0N/A if (info.fontFilePath != null &&
0N/A info.fontFilePath.startsWith(jreDir)) {
0N/A initialiseDeferredFont(info.fontFilePath);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /* We keep a map of the files which contain the Lucida fonts so we
0N/A * don't need to search for them.
0N/A * But since we know what fonts these files contain, we can also avoid
0N/A * opening them to look for a font name we don't recognise - see
0N/A * findDeferredFont().
0N/A * For typical cases where the font isn't a JRE one the overhead is
0N/A * this method call, HashMap.get() and null reference test, then
0N/A * a boolean test of noOtherJREFontFiles.
0N/A */
0N/A private static PhysicalFont findJREDeferredFont(String name, int style) {
0N/A
0N/A PhysicalFont physicalFont;
0N/A String nameAndStyle = name.toLowerCase(Locale.ENGLISH) + style;
0N/A String fileName = jreFontMap.get(nameAndStyle);
0N/A if (fileName != null) {
0N/A initSGEnv(); /* ensure jreFontDirName is initialised */
0N/A fileName = SunGraphicsEnvironment.jreFontDirName +
0N/A File.separator + fileName;
0N/A if (deferredFontFiles.get(fileName) != null) {
0N/A physicalFont = initialiseDeferredFont(fileName);
0N/A if (physicalFont != null &&
0N/A (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
0N/A physicalFont.getFamilyName(null).equalsIgnoreCase(name))
0N/A && physicalFont.style == style) {
0N/A return physicalFont;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /* Iterate over the deferred font files looking for any in the
0N/A * jre directory that we didn't recognise, open each of these.
0N/A * In almost all installations this will quickly fall through
0N/A * because only the Lucidas will be present and jreOtherFontFiles
0N/A * will be empty.
0N/A * noOtherJREFontFiles is used so we can skip this block as soon
0N/A * as its determined that its not needed - almost always after the
0N/A * very first time through.
0N/A */
0N/A if (noOtherJREFontFiles) {
0N/A return null;
0N/A }
0N/A synchronized (jreLucidaFontFiles) {
0N/A if (jreOtherFontFiles == null) {
0N/A HashSet<String> otherFontFiles = new HashSet<String>();
0N/A for (String deferredFile : deferredFontFiles.keySet()) {
0N/A File file = new File(deferredFile);
0N/A String dir = file.getParent();
0N/A String fname = file.getName();
0N/A /* skip names which aren't absolute, aren't in the JRE
0N/A * directory, or are known Lucida fonts.
0N/A */
0N/A if (dir == null ||
0N/A !dir.equals(SunGraphicsEnvironment.jreFontDirName) ||
0N/A jreLucidaFontFiles.contains(fname)) {
0N/A continue;
0N/A }
0N/A otherFontFiles.add(deferredFile);
0N/A }
0N/A jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY);
0N/A if (jreOtherFontFiles.length == 0) {
0N/A noOtherJREFontFiles = true;
0N/A }
0N/A }
0N/A
0N/A for (int i=0; i<jreOtherFontFiles.length;i++) {
0N/A fileName = jreOtherFontFiles[i];
0N/A if (fileName == null) {
0N/A continue;
0N/A }
0N/A jreOtherFontFiles[i] = null;
0N/A physicalFont = initialiseDeferredFont(fileName);
0N/A if (physicalFont != null &&
0N/A (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
0N/A physicalFont.getFamilyName(null).equalsIgnoreCase(name))
0N/A && physicalFont.style == style) {
0N/A return physicalFont;
0N/A }
0N/A }
0N/A }
0N/A
0N/A return null;
0N/A }
0N/A
0N/A /* This skips JRE installed fonts. */
0N/A private static PhysicalFont findOtherDeferredFont(String name, int style) {
0N/A for (String fileName : deferredFontFiles.keySet()) {
0N/A File file = new File(fileName);
0N/A String dir = file.getParent();
0N/A String fname = file.getName();
0N/A if (dir != null &&
0N/A dir.equals(SunGraphicsEnvironment.jreFontDirName) &&
0N/A jreLucidaFontFiles.contains(fname)) {
0N/A continue;
0N/A }
0N/A PhysicalFont physicalFont = initialiseDeferredFont(fileName);
0N/A if (physicalFont != null &&
0N/A (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
0N/A physicalFont.getFamilyName(null).equalsIgnoreCase(name)) &&
0N/A physicalFont.style == style) {
0N/A return physicalFont;
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A private static PhysicalFont findDeferredFont(String name, int style) {
0N/A
0N/A PhysicalFont physicalFont = findJREDeferredFont(name, style);
0N/A if (physicalFont != null) {
0N/A return physicalFont;
0N/A } else {
0N/A return findOtherDeferredFont(name, style);
0N/A }
0N/A }
0N/A
0N/A public static void registerDeferredFont(String fileNameKey,
0N/A String fullPathName,
0N/A String[] nativeNames,
0N/A int fontFormat,
0N/A boolean useJavaRasterizer,
0N/A int fontRank) {
0N/A FontRegistrationInfo regInfo =
0N/A new FontRegistrationInfo(fullPathName, nativeNames, fontFormat,
0N/A useJavaRasterizer, fontRank);
0N/A deferredFontFiles.put(fileNameKey, regInfo);
0N/A }
0N/A
0N/A
0N/A public static synchronized
0N/A PhysicalFont initialiseDeferredFont(String fileNameKey) {
0N/A
0N/A if (fileNameKey == null) {
0N/A return null;
0N/A }
0N/A if (logging) {
0N/A logger.info("Opening deferred font file " + fileNameKey);
0N/A }
0N/A
0N/A PhysicalFont physicalFont;
0N/A FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey);
0N/A if (regInfo != null) {
0N/A deferredFontFiles.remove(fileNameKey);
0N/A physicalFont = registerFontFile(regInfo.fontFilePath,
0N/A regInfo.nativeNames,
0N/A regInfo.fontFormat,
0N/A regInfo.javaRasterizer,
0N/A regInfo.fontRank);
0N/A
0N/A
0N/A if (physicalFont != null) {
0N/A /* Store the handle, so that if a font is bad, we
0N/A * retrieve the substituted font.
0N/A */
0N/A initialisedFonts.put(fileNameKey, physicalFont.handle);
0N/A } else {
0N/A initialisedFonts.put(fileNameKey,
0N/A getDefaultPhysicalFont().handle);
0N/A }
0N/A } else {
0N/A Font2DHandle handle = initialisedFonts.get(fileNameKey);
0N/A if (handle == null) {
0N/A /* Probably shouldn't happen, but just in case */
0N/A physicalFont = getDefaultPhysicalFont();
0N/A } else {
0N/A physicalFont = (PhysicalFont)(handle.font2D);
0N/A }
0N/A }
0N/A return physicalFont;
0N/A }
0N/A
0N/A /* Note that the return value from this method is not always
0N/A * derived from this file, and may be null. See addToFontList for
0N/A * some explanation of this.
0N/A */
0N/A public static PhysicalFont registerFontFile(String fileName,
0N/A String[] nativeNames,
0N/A int fontFormat,
0N/A boolean useJavaRasterizer,
0N/A int fontRank) {
0N/A
0N/A PhysicalFont regFont = registeredFontFiles.get(fileName);
0N/A if (regFont != null) {
0N/A return regFont;
0N/A }
0N/A
0N/A PhysicalFont physicalFont = null;
0N/A try {
0N/A String name;
0N/A
0N/A switch (fontFormat) {
0N/A
0N/A case FontManager.FONTFORMAT_TRUETYPE:
0N/A int fn = 0;
0N/A TrueTypeFont ttf;
0N/A do {
0N/A ttf = new TrueTypeFont(fileName, nativeNames, fn++,
0N/A useJavaRasterizer);
0N/A PhysicalFont pf = addToFontList(ttf, fontRank);
0N/A if (physicalFont == null) {
0N/A physicalFont = pf;
0N/A }
0N/A }
0N/A while (fn < ttf.getFontCount());
0N/A break;
0N/A
0N/A case FontManager.FONTFORMAT_TYPE1:
0N/A Type1Font t1f = new Type1Font(fileName, nativeNames);
0N/A physicalFont = addToFontList(t1f, fontRank);
0N/A break;
0N/A
0N/A case FontManager.FONTFORMAT_NATIVE:
0N/A NativeFont nf = new NativeFont(fileName, false);
0N/A physicalFont = addToFontList(nf, fontRank);
0N/A default:
0N/A
0N/A }
0N/A if (logging) {
0N/A logger.info("Registered file " + fileName + " as font " +
0N/A physicalFont + " rank=" + fontRank);
0N/A }
0N/A } catch (FontFormatException ffe) {
0N/A if (logging) {
0N/A logger.warning("Unusable font: " +
0N/A fileName + " " + ffe.toString());
0N/A }
0N/A }
0N/A if (physicalFont != null &&
0N/A fontFormat != FontManager.FONTFORMAT_NATIVE) {
0N/A registeredFontFiles.put(fileName, physicalFont);
0N/A }
0N/A return physicalFont;
0N/A }
0N/A
0N/A public static void registerFonts(String[] fileNames,
0N/A String[][] nativeNames,
0N/A int fontCount,
0N/A int fontFormat,
0N/A boolean useJavaRasterizer,
0N/A int fontRank, boolean defer) {
0N/A
0N/A for (int i=0; i < fontCount; i++) {
0N/A if (defer) {
0N/A registerDeferredFont(fileNames[i],fileNames[i], nativeNames[i],
0N/A fontFormat, useJavaRasterizer, fontRank);
0N/A } else {
0N/A registerFontFile(fileNames[i], nativeNames[i],
0N/A fontFormat, useJavaRasterizer, fontRank);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /*
0N/A * This is the Physical font used when some other font on the system
0N/A * can't be located. There has to be at least one font or the font
0N/A * system is not useful and the graphics environment cannot sustain
0N/A * the Java platform.
0N/A */
0N/A public static PhysicalFont getDefaultPhysicalFont() {
0N/A if (defaultPhysicalFont == null) {
0N/A /* findFont2D will load all fonts before giving up the search.
0N/A * If the JRE Lucida isn't found (eg because the JRE fonts
0N/A * directory is missing), it could find another version of Lucida
0N/A * from the host system. This is OK because at that point we are
0N/A * trying to gracefully handle/recover from a system
0N/A * misconfiguration and this is probably a reasonable substitution.
0N/A */
0N/A defaultPhysicalFont = (PhysicalFont)
0N/A findFont2D("Lucida Sans Regular", Font.PLAIN, NO_FALLBACK);
0N/A if (defaultPhysicalFont == null) {
0N/A defaultPhysicalFont = (PhysicalFont)
0N/A findFont2D("Arial", Font.PLAIN, NO_FALLBACK);
0N/A }
0N/A if (defaultPhysicalFont == null) {
0N/A /* Because of the findFont2D call above, if we reach here, we
0N/A * know all fonts have already been loaded, just accept any
0N/A * match at this point. If this fails we are in real trouble
0N/A * and I don't know how to recover from there being absolutely
0N/A * no fonts anywhere on the system.
0N/A */
0N/A Iterator i = physicalFonts.values().iterator();
0N/A if (i.hasNext()) {
0N/A defaultPhysicalFont = (PhysicalFont)i.next();
0N/A } else {
0N/A throw new Error("Probable fatal error:No fonts found.");
0N/A }
0N/A }
0N/A }
0N/A return defaultPhysicalFont;
0N/A }
0N/A
0N/A public static CompositeFont getDefaultLogicalFont(int style) {
0N/A return (CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
0N/A }
0N/A
0N/A /*
0N/A * return String representation of style prepended with "."
0N/A * This is useful for performance to avoid unnecessary string operations.
0N/A */
0N/A private static String dotStyleStr(int num) {
0N/A switch(num){
0N/A case Font.BOLD:
0N/A return ".bold";
0N/A case Font.ITALIC:
0N/A return ".italic";
0N/A case Font.ITALIC | Font.BOLD:
0N/A return ".bolditalic";
0N/A default:
0N/A return ".plain";
0N/A }
0N/A }
0N/A
0N/A static void initSGEnv() {
0N/A if (sgEnv == null) {
0N/A GraphicsEnvironment ge =
0N/A GraphicsEnvironment.getLocalGraphicsEnvironment();
0N/A if (ge instanceof HeadlessGraphicsEnvironment) {
0N/A HeadlessGraphicsEnvironment hgEnv =
0N/A (HeadlessGraphicsEnvironment)ge;
0N/A sgEnv = (SunGraphicsEnvironment)
0N/A hgEnv.getSunGraphicsEnvironment();
0N/A } else {
0N/A sgEnv = (SunGraphicsEnvironment)ge;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /* This is implemented only on windows and is called from code that
0N/A * executes only on windows. This isn't pretty but its not a precedent
0N/A * in this file. This very probably should be cleaned up at some point.
0N/A */
0N/A private static native void
0N/A populateFontFileNameMap(HashMap<String,String> fontToFileMap,
0N/A HashMap<String,String> fontToFamilyNameMap,
0N/A HashMap<String,ArrayList<String>>
0N/A familyToFontListMap,
0N/A Locale locale);
0N/A
0N/A /* Obtained from Platform APIs (windows only)
0N/A * Map from lower-case font full name to basename of font file.
0N/A * Eg "arial bold" -> ARIALBD.TTF.
0N/A * For TTC files, there is a mapping for each font in the file.
0N/A */
0N/A private static HashMap<String,String> fontToFileMap = null;
0N/A
0N/A /* Obtained from Platform APIs (windows only)
0N/A * Map from lower-case font full name to the name of its font family
0N/A * Eg "arial bold" -> "Arial"
0N/A */
0N/A private static HashMap<String,String> fontToFamilyNameMap = null;
0N/A
0N/A /* Obtained from Platform APIs (windows only)
0N/A * Map from a lower-case family name to a list of full names of
0N/A * the member fonts, eg:
0N/A * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"]
0N/A */
0N/A private static HashMap<String,ArrayList<String>> familyToFontListMap= null;
0N/A
0N/A /* The directories which contain platform fonts */
0N/A private static String[] pathDirs = null;
0N/A
0N/A private static boolean haveCheckedUnreferencedFontFiles;
0N/A
0N/A private static String[] getFontFilesFromPath(boolean noType1) {
0N/A final FilenameFilter filter;
0N/A if (noType1) {
0N/A filter = SunGraphicsEnvironment.ttFilter;
0N/A } else {
0N/A filter = new SunGraphicsEnvironment.TTorT1Filter();
0N/A }
0N/A return (String[])AccessController.doPrivileged(new PrivilegedAction() {
0N/A public Object run() {
0N/A if (pathDirs.length == 1) {
0N/A File dir = new File(pathDirs[0]);
0N/A String[] files = dir.list(filter);
0N/A if (files == null) {
0N/A return new String[0];
0N/A }
0N/A for (int f=0; f<files.length; f++) {
0N/A files[f] = files[f].toLowerCase();
0N/A }
0N/A return files;
0N/A } else {
0N/A ArrayList<String> fileList = new ArrayList<String>();
0N/A for (int i = 0; i< pathDirs.length; i++) {
0N/A File dir = new File(pathDirs[i]);
0N/A String[] files = dir.list(filter);
0N/A if (files == null) {
0N/A continue;
0N/A }
0N/A for (int f=0; f<files.length ; f++) {
0N/A fileList.add(files[f].toLowerCase());
0N/A }
0N/A }
0N/A return fileList.toArray(STR_ARRAY);
0N/A }
0N/A }
0N/A });
0N/A }
0N/A
0N/A /* This is needed since some windows registry names don't match
0N/A * the font names.
0N/A * - UPC styled font names have a double space, but the
0N/A * registry entry mapping to a file doesn't.
0N/A * - Marlett is in a hidden file not listed in the registry
0N/A * - The registry advertises that the file david.ttf contains a
0N/A * font with the full name "David Regular" when in fact its
0N/A * just "David".
0N/A * Directly fix up these known cases as this is faster.
0N/A * If a font which doesn't match these known cases has no file,
0N/A * it may be a font that has been temporarily added to the known set
0N/A * or it may be an installed font with a missing registry entry.
0N/A * Installed fonts are those in the windows font directories.
0N/A * Make a best effort attempt to locate these.
0N/A * We obtain the list of TrueType fonts in these directories and
0N/A * filter out all the font files we already know about from the registry.
0N/A * What remains may be "bad" fonts, duplicate fonts, or perhaps the
0N/A * missing font(s) we are looking for.
0N/A * Open each of these files to find out.
0N/A */
0N/A private static void resolveWindowsFonts() {
0N/A
0N/A ArrayList<String> unmappedFontNames = null;
0N/A for (String font : fontToFamilyNameMap.keySet()) {
0N/A String file = fontToFileMap.get(font);
0N/A if (file == null) {
0N/A if (font.indexOf(" ") > 0) {
0N/A String newName = font.replaceFirst(" ", " ");
0N/A file = fontToFileMap.get(newName);
0N/A /* If this name exists and isn't for a valid name
0N/A * replace the mapping to the file with this font
0N/A */
0N/A if (file != null &&
0N/A !fontToFamilyNameMap.containsKey(newName)) {
0N/A fontToFileMap.remove(newName);
0N/A fontToFileMap.put(font, file);
0N/A }
0N/A } else if (font.equals("marlett")) {
0N/A fontToFileMap.put(font, "marlett.ttf");
0N/A } else if (font.equals("david")) {
0N/A file = fontToFileMap.get("david regular");
0N/A if (file != null) {
0N/A fontToFileMap.remove("david regular");
0N/A fontToFileMap.put("david", file);
0N/A }
0N/A } else {
0N/A if (unmappedFontNames == null) {
0N/A unmappedFontNames = new ArrayList<String>();
0N/A }
0N/A unmappedFontNames.add(font);
0N/A }
0N/A }
0N/A }
0N/A
0N/A if (unmappedFontNames != null) {
0N/A HashSet<String> unmappedFontFiles = new HashSet<String>();
0N/A
0N/A /* Every font key in fontToFileMap ought to correspond to a
0N/A * font key in fontToFamilyNameMap. Entries that don't seem
0N/A * to correspond are likely fonts that were named differently
0N/A * by GDI than in the registry. One known cause of this is when
0N/A * Windows has had its regional settings changed so that from
0N/A * GDI we get a localised (eg Chinese or Japanese) name for the
0N/A * font, but the registry retains the English version of the name
0N/A * that corresponded to the "install" locale for windows.
0N/A * Since we are in this code block because there are unmapped
0N/A * font names, we can look to find unused font->file mappings
0N/A * and then open the files to read the names. We don't generally
0N/A * want to open font files, as its a performance hit, but this
0N/A * occurs only for a small number of fonts on specific system
0N/A * configs - ie is believed that a "true" Japanese windows would
0N/A * have JA names in the registry too.
0N/A * Clone fontToFileMap and remove from the clone all keys which
0N/A * match a fontToFamilyNameMap key. What remains maps to the
0N/A * files we want to open to find the fonts GDI returned.
0N/A * A font in such a file is added to the fontToFileMap after
0N/A * checking its one of the unmappedFontNames we are looking for.
0N/A * The original name that didn't map is removed from fontToFileMap
0N/A * so essentially this "fixes up" fontToFileMap to use the same
0N/A * name as GDI.
0N/A * Also note that typically the fonts for which this occurs in
0N/A * CJK locales are TTC fonts and not all fonts in a TTC may have
0N/A * localised names. Eg MSGOTHIC.TTC contains 3 fonts and one of
0N/A * them "MS UI Gothic" has no JA name whereas the other two do.
0N/A * So not every font in these files is unmapped or new.
0N/A */
0N/A HashMap<String,String> ffmapCopy =
0N/A (HashMap<String,String>)(fontToFileMap.clone());
0N/A for (String key : fontToFamilyNameMap.keySet()) {
0N/A ffmapCopy.remove(key);
0N/A }
0N/A for (String key : ffmapCopy.keySet()) {
0N/A unmappedFontFiles.add(ffmapCopy.get(key));
0N/A fontToFileMap.remove(key);
0N/A }
0N/A
0N/A resolveFontFiles(unmappedFontFiles, unmappedFontNames);
0N/A
0N/A /* If there are still unmapped font names, this means there's
0N/A * something that wasn't in the registry. We need to get all
0N/A * the font files directly and look at the ones that weren't
0N/A * found in the registry.
0N/A */
0N/A if (unmappedFontNames.size() > 0) {
0N/A
0N/A /* getFontFilesFromPath() returns all lower case names.
0N/A * To compare we also need lower case
0N/A * versions of the names from the registry.
0N/A */
0N/A ArrayList<String> registryFiles = new ArrayList<String>();
0N/A
0N/A for (String regFile : fontToFileMap.values()) {
0N/A registryFiles.add(regFile.toLowerCase());
0N/A }
0N/A /* We don't look for Type1 files here as windows will
0N/A * not enumerate these, so aren't useful in reconciling
0N/A * GDI's unmapped files. We do find these later when
0N/A * we enumerate all fonts.
0N/A */
0N/A for (String pathFile : getFontFilesFromPath(true)) {
0N/A if (!registryFiles.contains(pathFile)) {
0N/A unmappedFontFiles.add(pathFile);
0N/A }
0N/A }
0N/A
0N/A resolveFontFiles(unmappedFontFiles, unmappedFontNames);
0N/A }
0N/A
0N/A /* remove from the set of names that will be returned to the
0N/A * user any fonts that can't be mapped to files.
0N/A */
0N/A if (unmappedFontNames.size() > 0) {
0N/A int sz = unmappedFontNames.size();
0N/A for (int i=0; i<sz; i++) {
0N/A String name = unmappedFontNames.get(i);
0N/A String familyName = fontToFamilyNameMap.get(name);
0N/A if (familyName != null) {
0N/A ArrayList family = familyToFontListMap.get(familyName);
0N/A if (family != null) {
0N/A if (family.size() <= 1) {
0N/A familyToFontListMap.remove(familyName);
0N/A }
0N/A }
0N/A }
0N/A fontToFamilyNameMap.remove(name);
0N/A if (logging) {
0N/A logger.info("No file for font:" + name);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * In some cases windows may have fonts in the fonts folder that
0N/A * don't show up in the registry or in the GDI calls to enumerate fonts.
0N/A * The only way to find these is to list the directory. We invoke this
0N/A * only in getAllFonts/Families, so most searches for a specific
0N/A * font that is satisfied by the GDI/registry calls don't take the
0N/A * additional hit of listing the directory. This hit is small enough
0N/A * that its not significant in these 'enumerate all the fonts' cases.
0N/A * The basic approach is to cross-reference the files windows found
0N/A * with the ones in the directory listing approach, and for each
0N/A * in the latter list that is missing from the former list, register it.
0N/A */
0N/A private static synchronized void checkForUnreferencedFontFiles() {
0N/A if (haveCheckedUnreferencedFontFiles) {
0N/A return;
0N/A }
0N/A haveCheckedUnreferencedFontFiles = true;
0N/A if (!isWindows) {
0N/A return;
0N/A }
0N/A /* getFontFilesFromPath() returns all lower case names.
0N/A * To compare we also need lower case
0N/A * versions of the names from the registry.
0N/A */
0N/A ArrayList<String> registryFiles = new ArrayList<String>();
0N/A for (String regFile : fontToFileMap.values()) {
0N/A registryFiles.add(regFile.toLowerCase());
0N/A }
0N/A
0N/A /* To avoid any issues with concurrent modification, create
0N/A * copies of the existing maps, add the new fonts into these
0N/A * and then replace the references to the old ones with the
0N/A * new maps. ConcurrentHashmap is another option but its a lot
0N/A * more changes and with this exception, these maps are intended
0N/A * to be static.
0N/A */
0N/A HashMap<String,String> fontToFileMap2 = null;
0N/A HashMap<String,String> fontToFamilyNameMap2 = null;
0N/A HashMap<String,ArrayList<String>> familyToFontListMap2 = null;;
0N/A
0N/A for (String pathFile : getFontFilesFromPath(false)) {
0N/A if (!registryFiles.contains(pathFile)) {
0N/A if (logging) {
0N/A logger.info("Found non-registry file : " + pathFile);
0N/A }
0N/A PhysicalFont f = registerFontFile(getPathName(pathFile));
0N/A if (f == null) {
0N/A continue;
0N/A }
0N/A if (fontToFileMap2 == null) {
0N/A fontToFileMap2 = new HashMap<String,String>(fontToFileMap);
0N/A fontToFamilyNameMap2 =
0N/A new HashMap<String,String>(fontToFamilyNameMap);
0N/A familyToFontListMap2 = new
0N/A HashMap<String,ArrayList<String>>(familyToFontListMap);
0N/A }
0N/A String fontName = f.getFontName(null);
0N/A String family = f.getFamilyName(null);
0N/A String familyLC = family.toLowerCase();
0N/A fontToFamilyNameMap2.put(fontName, family);
0N/A fontToFileMap2.put(fontName, pathFile);
0N/A ArrayList<String> fonts = familyToFontListMap2.get(familyLC);
0N/A if (fonts == null) {
0N/A fonts = new ArrayList<String>();
0N/A } else {
0N/A fonts = new ArrayList<String>(fonts);
0N/A }
0N/A fonts.add(fontName);
0N/A familyToFontListMap2.put(familyLC, fonts);
0N/A }
0N/A }
0N/A if (fontToFileMap2 != null) {
0N/A fontToFileMap = fontToFileMap2;
0N/A familyToFontListMap = familyToFontListMap2;
0N/A fontToFamilyNameMap = fontToFamilyNameMap2;
0N/A }
0N/A }
0N/A
0N/A private static void resolveFontFiles(HashSet<String> unmappedFiles,
0N/A ArrayList<String> unmappedFonts) {
0N/A
0N/A Locale l = SunToolkit.getStartupLocale();
0N/A
0N/A for (String file : unmappedFiles) {
0N/A try {
0N/A int fn = 0;
0N/A TrueTypeFont ttf;
0N/A String fullPath = getPathName(file);
0N/A if (logging) {
0N/A logger.info("Trying to resolve file " + fullPath);
0N/A }
0N/A do {
0N/A ttf = new TrueTypeFont(fullPath, null, fn++, true);
0N/A // prefer the font's locale name.
0N/A String fontName = ttf.getFontName(l).toLowerCase();
0N/A if (unmappedFonts.contains(fontName)) {
0N/A fontToFileMap.put(fontName, file);
0N/A unmappedFonts.remove(fontName);
0N/A if (logging) {
0N/A logger.info("Resolved absent registry entry for " +
0N/A fontName + " located in " + fullPath);
0N/A }
0N/A }
0N/A }
0N/A while (fn < ttf.getFontCount());
0N/A } catch (Exception e) {
0N/A }
0N/A }
0N/A }
0N/A
0N/A private static synchronized HashMap<String,String> getFullNameToFileMap() {
0N/A if (fontToFileMap == null) {
0N/A
0N/A initSGEnv();
0N/A pathDirs = sgEnv.getPlatformFontDirs();
0N/A
0N/A fontToFileMap = new HashMap<String,String>(100);
0N/A fontToFamilyNameMap = new HashMap<String,String>(100);
0N/A familyToFontListMap = new HashMap<String,ArrayList<String>>(50);
0N/A populateFontFileNameMap(fontToFileMap,
0N/A fontToFamilyNameMap,
0N/A familyToFontListMap,
0N/A Locale.ENGLISH);
0N/A if (isWindows) {
0N/A resolveWindowsFonts();
0N/A }
0N/A if (logging) {
0N/A logPlatformFontInfo();
0N/A }
0N/A }
0N/A return fontToFileMap;
0N/A }
0N/A
0N/A private static void logPlatformFontInfo() {
0N/A for (int i=0; i< pathDirs.length;i++) {
0N/A logger.info("fontdir="+pathDirs[i]);
0N/A }
0N/A for (String keyName : fontToFileMap.keySet()) {
0N/A logger.info("font="+keyName+" file="+ fontToFileMap.get(keyName));
0N/A }
0N/A for (String keyName : fontToFamilyNameMap.keySet()) {
0N/A logger.info("font="+keyName+" family="+
0N/A fontToFamilyNameMap.get(keyName));
0N/A }
0N/A for (String keyName : familyToFontListMap.keySet()) {
0N/A logger.info("family="+keyName+ " fonts="+
0N/A familyToFontListMap.get(keyName));
0N/A }
0N/A }
0N/A
0N/A /* Note this return list excludes logical fonts and JRE fonts */
0N/A public static String[] getFontNamesFromPlatform() {
0N/A if (getFullNameToFileMap().size() == 0) {
0N/A return null;
0N/A }
0N/A checkForUnreferencedFontFiles();
0N/A /* This odd code with TreeMap is used to preserve a historical
0N/A * behaviour wrt the sorting order .. */
0N/A ArrayList<String> fontNames = new ArrayList<String>();
0N/A for (ArrayList<String> a : familyToFontListMap.values()) {
0N/A for (String s : a) {
0N/A fontNames.add(s);
0N/A }
0N/A }
0N/A return fontNames.toArray(STR_ARRAY);
0N/A }
0N/A
0N/A public static boolean gotFontsFromPlatform() {
0N/A return getFullNameToFileMap().size() != 0;
0N/A }
0N/A
0N/A public static String getFileNameForFontName(String fontName) {
0N/A String fontNameLC = fontName.toLowerCase(Locale.ENGLISH);
0N/A return fontToFileMap.get(fontNameLC);
0N/A }
0N/A
0N/A private static PhysicalFont registerFontFile(String file) {
0N/A if (new File(file).isAbsolute() &&
0N/A !registeredFontFiles.contains(file)) {
0N/A int fontFormat = FONTFORMAT_NONE;
0N/A int fontRank = Font2D.UNKNOWN_RANK;
0N/A if (SunGraphicsEnvironment.ttFilter.accept(null, file)) {
0N/A fontFormat = FONTFORMAT_TRUETYPE;
0N/A fontRank = Font2D.TTF_RANK;
0N/A } else if
0N/A (SunGraphicsEnvironment.t1Filter.accept(null, file)) {
0N/A fontFormat = FONTFORMAT_TYPE1;
0N/A fontRank = Font2D.TYPE1_RANK;
0N/A }
0N/A if (fontFormat == FONTFORMAT_NONE) {
0N/A return null;
0N/A }
0N/A return registerFontFile(file, null, fontFormat, false, fontRank);
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /* Used to register any font files that are found by platform APIs
0N/A * that weren't previously found in the standard font locations.
0N/A * the isAbsolute() check is needed since that's whats stored in the
0N/A * set, and on windows, the fonts in the system font directory that
0N/A * are in the fontToFileMap are just basenames. We don't want to try
0N/A * to register those again, but we do want to register other registry
0N/A * installed fonts.
0N/A */
0N/A public static void registerOtherFontFiles(HashSet registeredFontFiles) {
0N/A if (getFullNameToFileMap().size() == 0) {
0N/A return;
0N/A }
0N/A for (String file : fontToFileMap.values()) {
0N/A registerFontFile(file);
0N/A }
0N/A }
0N/A
0N/A public static boolean
0N/A getFamilyNamesFromPlatform(TreeMap<String,String> familyNames,
0N/A Locale requestedLocale) {
0N/A if (getFullNameToFileMap().size() == 0) {
0N/A return false;
0N/A }
0N/A checkForUnreferencedFontFiles();
0N/A for (String name : fontToFamilyNameMap.values()) {
0N/A familyNames.put(name.toLowerCase(requestedLocale), name);
0N/A }
0N/A return true;
0N/A }
0N/A
0N/A /* Path may be absolute or a base file name relative to one of
0N/A * the platform font directories
0N/A */
0N/A private static String getPathName(String s) {
0N/A File f = new File(s);
0N/A if (f.isAbsolute()) {
0N/A return s;
0N/A } else if (pathDirs.length==1) {
0N/A return pathDirs[0] + File.separator + s;
0N/A } else {
0N/A for (int p=0; p<pathDirs.length; p++) {
0N/A f = new File(pathDirs[p] + File.separator + s);
0N/A if (f.exists()) {
0N/A return f.getAbsolutePath();
0N/A }
0N/A }
0N/A }
0N/A return s; // shouldn't happen, but harmless
0N/A }
0N/A
0N/A /* lcName is required to be lower case for use as a key.
0N/A * lcName may be a full name, or a family name, and style may
0N/A * be specified in addition to either of these. So be sure to
0N/A * get the right one. Since an app *could* ask for "Foo Regular"
0N/A * and later ask for "Foo Italic", if we don't register all the
0N/A * styles, then logic in findFont2D may try to style the original
0N/A * so we register the entire family if we get a match here.
0N/A * This is still a big win because this code is invoked where
0N/A * otherwise we would register all fonts.
0N/A * It's also useful for the case where "Foo Bold" was specified with
0N/A * style Font.ITALIC, as we would want in that case to try to return
0N/A * "Foo Bold Italic" if it exists, and it is only by locating "Foo Bold"
0N/A * and opening it that we really "know" it's Bold, and can look for
0N/A * a font that supports that and the italic style.
0N/A * The code in here is not overtly windows-specific but in fact it
0N/A * is unlikely to be useful as is on other platforms. It is maintained
0N/A * in this shared source file to be close to its sole client and
0N/A * because so much of the logic is intertwined with the logic in
0N/A * findFont2D.
0N/A */
0N/A private static Font2D findFontFromPlatform(String lcName, int style) {
0N/A if (getFullNameToFileMap().size() == 0) {
0N/A return null;
0N/A }
0N/A
0N/A ArrayList<String> family = null;
0N/A String fontFile = null;
0N/A String familyName = fontToFamilyNameMap.get(lcName);
0N/A if (familyName != null) {
0N/A fontFile = fontToFileMap.get(lcName);
0N/A family = familyToFontListMap.get
0N/A (familyName.toLowerCase(Locale.ENGLISH));
0N/A } else {
0N/A family = familyToFontListMap.get(lcName); // is lcName is a family?
0N/A if (family != null && family.size() > 0) {
0N/A String lcFontName = family.get(0).toLowerCase(Locale.ENGLISH);
0N/A if (lcFontName != null) {
0N/A familyName = fontToFamilyNameMap.get(lcFontName);
0N/A }
0N/A }
0N/A }
0N/A if (family == null || familyName == null) {
0N/A return null;
0N/A }
0N/A String [] fontList = (String[])family.toArray(STR_ARRAY);
0N/A if (fontList.length == 0) {
0N/A return null;
0N/A }
0N/A
0N/A /* first check that for every font in this family we can find
0N/A * a font file. The specific reason for doing this is that
0N/A * in at least one case on Windows a font has the face name "David"
0N/A * but the registry entry is "David Regular". That is the "unique"
0N/A * name of the font but in other cases the registry contains the
0N/A * "full" name. See the specifications of name ids 3 and 4 in the
0N/A * TrueType 'name' table.
0N/A * In general this could cause a problem that we fail to register
0N/A * if we all members of a family that we may end up mapping to
0N/A * the wrong font member: eg return Bold when Plain is needed.
0N/A */
0N/A for (int f=0;f<fontList.length;f++) {
0N/A String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
0N/A String fileName = fontToFileMap.get(fontNameLC);
0N/A if (fileName == null) {
0N/A if (logging) {
0N/A logger.info("Platform lookup : No file for font " +
0N/A fontList[f] + " in family " +familyName);
0N/A }
0N/A return null;
0N/A }
0N/A }
0N/A
0N/A /* Currently this code only looks for TrueType fonts, so format
0N/A * and rank can be specified without looking at the filename.
0N/A */
0N/A PhysicalFont physicalFont = null;
0N/A if (fontFile != null) {
0N/A physicalFont = registerFontFile(getPathName(fontFile), null,
0N/A FONTFORMAT_TRUETYPE, false,
0N/A Font2D.TTF_RANK);
0N/A }
0N/A /* Register all fonts in this family. */
0N/A for (int f=0;f<fontList.length;f++) {
0N/A String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
0N/A String fileName = fontToFileMap.get(fontNameLC);
0N/A if (fontFile != null && fontFile.equals(fileName)) {
0N/A continue;
0N/A }
0N/A /* Currently this code only looks for TrueType fonts, so format
0N/A * and rank can be specified without looking at the filename.
0N/A */
0N/A registerFontFile(getPathName(fileName), null,
0N/A FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK);
0N/A }
0N/A
0N/A Font2D font = null;
0N/A FontFamily fontFamily = FontFamily.getFamily(familyName);
0N/A /* Handle case where request "MyFont Bold", style=Font.ITALIC */
0N/A if (physicalFont != null) {
0N/A style |= physicalFont.style;
0N/A }
0N/A if (fontFamily != null) {
0N/A font = fontFamily.getFont(style);
0N/A if (font == null) {
0N/A font = fontFamily.getClosestStyle(style);
0N/A }
0N/A }
0N/A return font;
0N/A }
0N/A
0N/A private static ConcurrentHashMap<String, Font2D> fontNameCache =
0N/A new ConcurrentHashMap<String, Font2D>();
0N/A
0N/A /*
0N/A * The client supplies a name and a style.
0N/A * The name could be a family name, or a full name.
0N/A * A font may exist with the specified style, or it may
0N/A * exist only in some other style. For non-native fonts the scaler
0N/A * may be able to emulate the required style.
0N/A */
0N/A public static Font2D findFont2D(String name, int style, int fallback) {
0N/A String lowerCaseName = name.toLowerCase(Locale.ENGLISH);
0N/A String mapName = lowerCaseName + dotStyleStr(style);
0N/A Font2D font;
0N/A
0N/A /* If preferLocaleFonts() or preferProportionalFonts() has been
0N/A * called we may be using an alternate set of composite fonts in this
0N/A * app context. The presence of a pre-built name map indicates whether
0N/A * this is so, and gives access to the alternate composite for the
0N/A * name.
0N/A */
0N/A if (usingPerAppContextComposites) {
0N/A ConcurrentHashMap<String, Font2D> altNameCache =
0N/A (ConcurrentHashMap<String, Font2D>)
0N/A AppContext.getAppContext().get(CompositeFont.class);
0N/A if (altNameCache != null) {
0N/A font = (Font2D)altNameCache.get(mapName);
0N/A } else {
0N/A font = null;
0N/A }
0N/A } else {
0N/A font = fontNameCache.get(mapName);
0N/A }
0N/A if (font != null) {
0N/A return font;
0N/A }
0N/A
0N/A if (logging) {
0N/A logger.info("Search for font: " + name);
0N/A }
0N/A
0N/A // The check below is just so that the bitmap fonts being set by
0N/A // AWT and Swing thru the desktop properties do not trigger the
0N/A // the load fonts case. The two bitmap fonts are now mapped to
0N/A // appropriate equivalents for serif and sansserif.
0N/A // Note that the cost of this comparison is only for the first
0N/A // call until the map is filled.
0N/A if (isWindows) {
0N/A if (lowerCaseName.equals("ms sans serif")) {
0N/A name = "sansserif";
0N/A } else if (lowerCaseName.equals("ms serif")) {
0N/A name = "serif";
0N/A }
0N/A }
0N/A
0N/A /* This isn't intended to support a client passing in the
0N/A * string default, but if a client passes in null for the name
0N/A * the java.awt.Font class internally substitutes this name.
0N/A * So we need to recognise it here to prevent a loadFonts
0N/A * on the unrecognised name. The only potential problem with
0N/A * this is it would hide any real font called "default"!
0N/A * But that seems like a potential problem we can ignore for now.
0N/A */
0N/A if (lowerCaseName.equals("default")) {
0N/A name = "dialog";
0N/A }
0N/A
0N/A /* First see if its a family name. */
0N/A FontFamily family = FontFamily.getFamily(name);
0N/A if (family != null) {
0N/A font = family.getFontWithExactStyleMatch(style);
0N/A if (font == null) {
0N/A font = findDeferredFont(name, style);
0N/A }
0N/A if (font == null) {
0N/A font = family.getFont(style);
0N/A }
0N/A if (font == null) {
0N/A font = family.getClosestStyle(style);
0N/A }
0N/A if (font != null) {
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A }
0N/A }
0N/A
0N/A /* If it wasn't a family name, it should be a full name of
0N/A * either a composite, or a physical font
0N/A */
0N/A font = fullNameToFont.get(lowerCaseName);
0N/A if (font != null) {
0N/A /* Check that the requested style matches the matched font's style.
0N/A * But also match style automatically if the requested style is
0N/A * "plain". This because the existing behaviour is that the fonts
0N/A * listed via getAllFonts etc always list their style as PLAIN.
0N/A * This does lead to non-commutative behaviours where you might
0N/A * start with "Lucida Sans Regular" and ask for a BOLD version
0N/A * and get "Lucida Sans DemiBold" but if you ask for the PLAIN
0N/A * style of "Lucida Sans DemiBold" you get "Lucida Sans DemiBold".
0N/A * This consistent however with what happens if you have a bold
0N/A * version of a font and no plain version exists - alg. styling
0N/A * doesn't "unbolden" the font.
0N/A */
0N/A if (font.style == style || style == Font.PLAIN) {
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A } else {
0N/A /* If it was a full name like "Lucida Sans Regular", but
0N/A * the style requested is "bold", then we want to see if
0N/A * there's the appropriate match against another font in
0N/A * that family before trying to load all fonts, or applying a
0N/A * algorithmic styling
0N/A */
0N/A family = FontFamily.getFamily(font.getFamilyName(null));
0N/A if (family != null) {
0N/A Font2D familyFont = family.getFont(style|font.style);
0N/A /* We exactly matched the requested style, use it! */
0N/A if (familyFont != null) {
0N/A fontNameCache.put(mapName, familyFont);
0N/A return familyFont;
0N/A } else {
0N/A /* This next call is designed to support the case
0N/A * where bold italic is requested, and if we must
0N/A * style, then base it on either bold or italic -
0N/A * not on plain!
0N/A */
0N/A familyFont = family.getClosestStyle(style|font.style);
0N/A if (familyFont != null) {
0N/A /* The next check is perhaps one
0N/A * that shouldn't be done. ie if we get this
0N/A * far we have probably as close a match as we
0N/A * are going to get. We could load all fonts to
0N/A * see if somehow some parts of the family are
0N/A * loaded but not all of it.
0N/A */
0N/A if (familyFont.canDoStyle(style|font.style)) {
0N/A fontNameCache.put(mapName, familyFont);
0N/A return familyFont;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /* If reach here its possible that this is in a client which never
0N/A * loaded the GraphicsEnvironment, so we haven't even loaded ANY of
0N/A * the fonts from the environment. Do so now and recurse.
0N/A */
0N/A if (sgEnv == null) {
0N/A initSGEnv();
0N/A return findFont2D(name, style, fallback);
0N/A }
0N/A
0N/A if (isWindows) {
0N/A /* Don't want Windows to return a Lucida Sans font from
0N/A * C:\Windows\Fonts
0N/A */
0N/A if (deferredFontFiles.size() > 0) {
0N/A font = findJREDeferredFont(lowerCaseName, style);
0N/A if (font != null) {
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A }
0N/A }
0N/A font = findFontFromPlatform(lowerCaseName, style);
0N/A if (font != null) {
0N/A if (logging) {
0N/A logger.info("Found font via platform API for request:\"" +
0N/A name + "\":, style="+style+
0N/A " found font: " + font);
0N/A }
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A }
0N/A }
0N/A
0N/A /* If reach here and no match has been located, then if there are
0N/A * uninitialised deferred fonts, load as many of those as needed
0N/A * to find the deferred font. If none is found through that
0N/A * search continue on.
0N/A * There is possibly a minor issue when more than one
0N/A * deferred font implements the same font face. Since deferred
0N/A * fonts are only those in font configuration files, this is a
0N/A * controlled situation, the known case being Solaris euro_fonts
0N/A * versions of Arial, Times New Roman, Courier New. However
0N/A * the larger font will transparently replace the smaller one
0N/A * - see addToFontList() - when it is needed by the composite font.
0N/A */
0N/A if (deferredFontFiles.size() > 0) {
0N/A font = findDeferredFont(name, style);
0N/A if (font != null) {
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A }
0N/A }
0N/A
0N/A /* Some apps use deprecated 1.0 names such as helvetica and courier. On
0N/A * Solaris these are Type1 fonts in /usr/openwin/lib/X11/fonts/Type1.
0N/A * If running on Solaris will register all the fonts in this
0N/A * directory.
0N/A * May as well register the whole directory without actually testing
0N/A * the font name is one of the deprecated names as the next step would
0N/A * load all fonts which are in this directory anyway.
0N/A * In the event that this lookup is successful it potentially "hides"
0N/A * TrueType versions of such fonts that are elsewhere but since they
0N/A * do not exist on Solaris this is not a problem.
0N/A * Set a flag to indicate we've done this registration to avoid
0N/A * repetition and more seriously, to avoid recursion.
0N/A */
0N/A if (isSolaris&&!loaded1dot0Fonts) {
0N/A /* "timesroman" is a special case since that's not the
0N/A * name of any known font on Solaris or elsewhere.
0N/A */
0N/A if (lowerCaseName.equals("timesroman")) {
0N/A font = findFont2D("serif", style, fallback);
0N/A fontNameCache.put(mapName, font);
0N/A }
0N/A sgEnv.register1dot0Fonts();
0N/A loaded1dot0Fonts = true;
0N/A Font2D ff = findFont2D(name, style, fallback);
0N/A return ff;
0N/A }
0N/A
0N/A /* We check for application registered fonts before
0N/A * explicitly loading all fonts as if necessary the registration
0N/A * code will have done so anyway. And we don't want to needlessly
0N/A * load the actual files for all fonts.
0N/A * Just as for installed fonts we check for family before fullname.
0N/A * We do not add these fonts to fontNameCache for the
0N/A * app context case which eliminates the overhead of a per context
0N/A * cache for these.
0N/A */
0N/A
0N/A if (fontsAreRegistered || fontsAreRegisteredPerAppContext) {
0N/A Hashtable<String, FontFamily> familyTable = null;
0N/A Hashtable<String, Font2D> nameTable;
0N/A
0N/A if (fontsAreRegistered) {
0N/A familyTable = createdByFamilyName;
0N/A nameTable = createdByFullName;
0N/A } else {
0N/A AppContext appContext = AppContext.getAppContext();
0N/A familyTable =
0N/A (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
0N/A nameTable =
0N/A (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
0N/A }
0N/A
0N/A family = familyTable.get(lowerCaseName);
0N/A if (family != null) {
0N/A font = family.getFontWithExactStyleMatch(style);
0N/A if (font == null) {
0N/A font = family.getFont(style);
0N/A }
0N/A if (font == null) {
0N/A font = family.getClosestStyle(style);
0N/A }
0N/A if (font != null) {
0N/A if (fontsAreRegistered) {
0N/A fontNameCache.put(mapName, font);
0N/A }
0N/A return font;
0N/A }
0N/A }
0N/A font = nameTable.get(lowerCaseName);
0N/A if (font != null) {
0N/A if (fontsAreRegistered) {
0N/A fontNameCache.put(mapName, font);
0N/A }
0N/A return font;
0N/A }
0N/A }
0N/A
0N/A /* If reach here and no match has been located, then if all fonts
0N/A * are not yet loaded, do so, and then recurse.
0N/A */
0N/A if (!loadedAllFonts) {
0N/A if (logging) {
0N/A logger.info("Load fonts looking for:" + name);
0N/A }
0N/A sgEnv.loadFonts();
0N/A loadedAllFonts = true;
0N/A return findFont2D(name, style, fallback);
0N/A }
0N/A
0N/A if (!loadedAllFontFiles) {
0N/A if (logging) {
0N/A logger.info("Load font files looking for:" + name);
0N/A }
0N/A sgEnv.loadFontFiles();
0N/A loadedAllFontFiles = true;
0N/A return findFont2D(name, style, fallback);
0N/A }
0N/A
0N/A /* The primary name is the locale default - ie not US/English but
0N/A * whatever is the default in this locale. This is the way it always
0N/A * has been but may be surprising to some developers if "Arial Regular"
0N/A * were hard-coded in their app and yet "Arial Regular" was not the
0N/A * default name. Fortunately for them, as a consequence of the JDK
0N/A * supporting returning names and family names for arbitrary locales,
0N/A * we also need to support searching all localised names for a match.
0N/A * But because this case of the name used to reference a font is not
0N/A * the same as the default for this locale is rare, it makes sense to
0N/A * search a much shorter list of default locale names and only go to
0N/A * a longer list of names in the event that no match was found.
0N/A * So add here code which searches localised names too.
0N/A * As in 1.4.x this happens only after loading all fonts, which
0N/A * is probably the right order.
0N/A */
0N/A if ((font = findFont2DAllLocales(name, style)) != null) {
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A }
0N/A
0N/A /* Perhaps its a "compatibility" name - timesroman, helvetica,
0N/A * or courier, which 1.0 apps used for logical fonts.
0N/A * We look for these "late" after a loadFonts as we must not
0N/A * hide real fonts of these names.
0N/A * Map these appropriately:
0N/A * On windows this means according to the rules specified by the
0N/A * FontConfiguration : do it only for encoding==Cp1252
0N/A *
0N/A * REMIND: this is something we plan to remove.
0N/A */
0N/A if (isWindows) {
0N/A String compatName =
0N/A sgEnv.getFontConfiguration().getFallbackFamilyName(name, null);
0N/A if (compatName != null) {
0N/A font = findFont2D(compatName, style, fallback);
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A }
0N/A } else if (lowerCaseName.equals("timesroman")) {
0N/A font = findFont2D("serif", style, fallback);
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A } else if (lowerCaseName.equals("helvetica")) {
0N/A font = findFont2D("sansserif", style, fallback);
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A } else if (lowerCaseName.equals("courier")) {
0N/A font = findFont2D("monospaced", style, fallback);
0N/A fontNameCache.put(mapName, font);
0N/A return font;
0N/A }
0N/A
0N/A if (logging) {
0N/A logger.info("No font found for:" + name);
0N/A }
0N/A
0N/A switch (fallback) {
0N/A case PHYSICAL_FALLBACK: return getDefaultPhysicalFont();
0N/A case LOGICAL_FALLBACK: return getDefaultLogicalFont(style);
0N/A default: return null;
0N/A }
0N/A }
0N/A
0N/A /* This method can be more efficient as it will only need to
0N/A * do the lookup once, and subsequent calls on the java.awt.Font
0N/A * instance can utilise the cached Font2D on that object.
0N/A * Its unfortunate it needs to be a native method, but the font2D
0N/A * variable has to be private.
0N/A */
0N/A public static native Font2D getFont2D(Font font);
0N/A
0N/A /* Stuff below was in NativeFontWrapper and needed a new home */
0N/A
0N/A /*
0N/A * Workaround for apps which are dependent on a font metrics bug
0N/A * in JDK 1.1. This is an unsupported win32 private setting.
0N/A */
0N/A public static boolean usePlatformFontMetrics() {
0N/A return usePlatformFontMetrics;
0N/A }
0N/A
0N/A static native boolean getPlatformFontVar();
0N/A
0N/A private static final short US_LCID = 0x0409; // US English - default
0N/A private static Map<String, Short> lcidMap;
0N/A
0N/A // Return a Microsoft LCID from the given Locale.
0N/A // Used when getting localized font data.
0N/A
0N/A public static short getLCIDFromLocale(Locale locale) {
0N/A // optimize for common case
0N/A if (locale.equals(Locale.US)) {
0N/A return US_LCID;
0N/A }
0N/A
0N/A if (lcidMap == null) {
0N/A createLCIDMap();
0N/A }
0N/A
0N/A String key = locale.toString();
0N/A while (!"".equals(key)) {
0N/A Short lcidObject = (Short) lcidMap.get(key);
0N/A if (lcidObject != null) {
0N/A return lcidObject.shortValue();
0N/A }
0N/A int pos = key.lastIndexOf('_');
0N/A if (pos < 1) {
0N/A return US_LCID;
0N/A }
0N/A key = key.substring(0, pos);
0N/A }
0N/A
0N/A return US_LCID;
0N/A }
0N/A
0N/A
0N/A private static void addLCIDMapEntry(Map<String, Short> map,
0N/A String key, short value) {
0N/A map.put(key, new Short(value));
0N/A }
0N/A
0N/A private static synchronized void createLCIDMap() {
0N/A if (lcidMap != null) {
0N/A return;
0N/A }
0N/A
0N/A Map<String, Short> map = new HashMap<String, Short>(200);
0N/A
0N/A // the following statements are derived from the langIDMap
0N/A // in src/windows/native/java/lang/java_props_md.c using the following
0N/A // awk script:
0N/A // $1~/\/\*/ { next}
0N/A // $3~/\?\?/ { next }
0N/A // $3!~/_/ { next }
0N/A // $1~/0x0409/ { next }
0N/A // $1~/0x0c0a/ { next }
0N/A // $1~/0x042c/ { next }
0N/A // $1~/0x0443/ { next }
0N/A // $1~/0x0812/ { next }
0N/A // $1~/0x04/ { print " addLCIDMapEntry(map, " substr($3, 0, 3) "\", (short) " substr($1, 0, 6) ");" ; next }
0N/A // $3~/,/ { print " addLCIDMapEntry(map, " $3 " (short) " substr($1, 0, 6) ");" ; next }
0N/A // { print " addLCIDMapEntry(map, " $3 ", (short) " substr($1, 0, 6) ");" ; next }
0N/A // The lines of this script:
0N/A // - eliminate comments
0N/A // - eliminate questionable locales
0N/A // - eliminate language-only locales
0N/A // - eliminate the default LCID value
0N/A // - eliminate a few other unneeded LCID values
0N/A // - print language-only locale entries for x04* LCID values
0N/A // (apparently Microsoft doesn't use language-only LCID values -
0N/A // see http://www.microsoft.com/OpenType/otspec/name.htm
0N/A // - print complete entries for all other LCID values
0N/A // Run
0N/A // awk -f awk-script langIDMap > statements
0N/A addLCIDMapEntry(map, "ar", (short) 0x0401);
0N/A addLCIDMapEntry(map, "bg", (short) 0x0402);
0N/A addLCIDMapEntry(map, "ca", (short) 0x0403);
0N/A addLCIDMapEntry(map, "zh", (short) 0x0404);
0N/A addLCIDMapEntry(map, "cs", (short) 0x0405);
0N/A addLCIDMapEntry(map, "da", (short) 0x0406);
0N/A addLCIDMapEntry(map, "de", (short) 0x0407);
0N/A addLCIDMapEntry(map, "el", (short) 0x0408);
0N/A addLCIDMapEntry(map, "es", (short) 0x040a);
0N/A addLCIDMapEntry(map, "fi", (short) 0x040b);
0N/A addLCIDMapEntry(map, "fr", (short) 0x040c);
0N/A addLCIDMapEntry(map, "iw", (short) 0x040d);
0N/A addLCIDMapEntry(map, "hu", (short) 0x040e);
0N/A addLCIDMapEntry(map, "is", (short) 0x040f);
0N/A addLCIDMapEntry(map, "it", (short) 0x0410);
0N/A addLCIDMapEntry(map, "ja", (short) 0x0411);
0N/A addLCIDMapEntry(map, "ko", (short) 0x0412);
0N/A addLCIDMapEntry(map, "nl", (short) 0x0413);
0N/A addLCIDMapEntry(map, "no", (short) 0x0414);
0N/A addLCIDMapEntry(map, "pl", (short) 0x0415);
0N/A addLCIDMapEntry(map, "pt", (short) 0x0416);
0N/A addLCIDMapEntry(map, "rm", (short) 0x0417);
0N/A addLCIDMapEntry(map, "ro", (short) 0x0418);
0N/A addLCIDMapEntry(map, "ru", (short) 0x0419);
0N/A addLCIDMapEntry(map, "hr", (short) 0x041a);
0N/A addLCIDMapEntry(map, "sk", (short) 0x041b);
0N/A addLCIDMapEntry(map, "sq", (short) 0x041c);
0N/A addLCIDMapEntry(map, "sv", (short) 0x041d);
0N/A addLCIDMapEntry(map, "th", (short) 0x041e);
0N/A addLCIDMapEntry(map, "tr", (short) 0x041f);
0N/A addLCIDMapEntry(map, "ur", (short) 0x0420);
0N/A addLCIDMapEntry(map, "in", (short) 0x0421);
0N/A addLCIDMapEntry(map, "uk", (short) 0x0422);
0N/A addLCIDMapEntry(map, "be", (short) 0x0423);
0N/A addLCIDMapEntry(map, "sl", (short) 0x0424);
0N/A addLCIDMapEntry(map, "et", (short) 0x0425);
0N/A addLCIDMapEntry(map, "lv", (short) 0x0426);
0N/A addLCIDMapEntry(map, "lt", (short) 0x0427);
0N/A addLCIDMapEntry(map, "fa", (short) 0x0429);
0N/A addLCIDMapEntry(map, "vi", (short) 0x042a);
0N/A addLCIDMapEntry(map, "hy", (short) 0x042b);
0N/A addLCIDMapEntry(map, "eu", (short) 0x042d);
0N/A addLCIDMapEntry(map, "mk", (short) 0x042f);
0N/A addLCIDMapEntry(map, "tn", (short) 0x0432);
0N/A addLCIDMapEntry(map, "xh", (short) 0x0434);
0N/A addLCIDMapEntry(map, "zu", (short) 0x0435);
0N/A addLCIDMapEntry(map, "af", (short) 0x0436);
0N/A addLCIDMapEntry(map, "ka", (short) 0x0437);
0N/A addLCIDMapEntry(map, "fo", (short) 0x0438);
0N/A addLCIDMapEntry(map, "hi", (short) 0x0439);
0N/A addLCIDMapEntry(map, "mt", (short) 0x043a);
0N/A addLCIDMapEntry(map, "se", (short) 0x043b);
0N/A addLCIDMapEntry(map, "gd", (short) 0x043c);
0N/A addLCIDMapEntry(map, "ms", (short) 0x043e);
0N/A addLCIDMapEntry(map, "kk", (short) 0x043f);
0N/A addLCIDMapEntry(map, "ky", (short) 0x0440);
0N/A addLCIDMapEntry(map, "sw", (short) 0x0441);
0N/A addLCIDMapEntry(map, "tt", (short) 0x0444);
0N/A addLCIDMapEntry(map, "bn", (short) 0x0445);
0N/A addLCIDMapEntry(map, "pa", (short) 0x0446);
0N/A addLCIDMapEntry(map, "gu", (short) 0x0447);
0N/A addLCIDMapEntry(map, "ta", (short) 0x0449);
0N/A addLCIDMapEntry(map, "te", (short) 0x044a);
0N/A addLCIDMapEntry(map, "kn", (short) 0x044b);
0N/A addLCIDMapEntry(map, "ml", (short) 0x044c);
0N/A addLCIDMapEntry(map, "mr", (short) 0x044e);
0N/A addLCIDMapEntry(map, "sa", (short) 0x044f);
0N/A addLCIDMapEntry(map, "mn", (short) 0x0450);
0N/A addLCIDMapEntry(map, "cy", (short) 0x0452);
0N/A addLCIDMapEntry(map, "gl", (short) 0x0456);
0N/A addLCIDMapEntry(map, "dv", (short) 0x0465);
0N/A addLCIDMapEntry(map, "qu", (short) 0x046b);
0N/A addLCIDMapEntry(map, "mi", (short) 0x0481);
0N/A addLCIDMapEntry(map, "ar_IQ", (short) 0x0801);
0N/A addLCIDMapEntry(map, "zh_CN", (short) 0x0804);
0N/A addLCIDMapEntry(map, "de_CH", (short) 0x0807);
0N/A addLCIDMapEntry(map, "en_GB", (short) 0x0809);
0N/A addLCIDMapEntry(map, "es_MX", (short) 0x080a);
0N/A addLCIDMapEntry(map, "fr_BE", (short) 0x080c);
0N/A addLCIDMapEntry(map, "it_CH", (short) 0x0810);
0N/A addLCIDMapEntry(map, "nl_BE", (short) 0x0813);
0N/A addLCIDMapEntry(map, "no_NO_NY", (short) 0x0814);
0N/A addLCIDMapEntry(map, "pt_PT", (short) 0x0816);
0N/A addLCIDMapEntry(map, "ro_MD", (short) 0x0818);
0N/A addLCIDMapEntry(map, "ru_MD", (short) 0x0819);
0N/A addLCIDMapEntry(map, "sr_CS", (short) 0x081a);
0N/A addLCIDMapEntry(map, "sv_FI", (short) 0x081d);
0N/A addLCIDMapEntry(map, "az_AZ", (short) 0x082c);
0N/A addLCIDMapEntry(map, "se_SE", (short) 0x083b);
0N/A addLCIDMapEntry(map, "ga_IE", (short) 0x083c);
0N/A addLCIDMapEntry(map, "ms_BN", (short) 0x083e);
0N/A addLCIDMapEntry(map, "uz_UZ", (short) 0x0843);
0N/A addLCIDMapEntry(map, "qu_EC", (short) 0x086b);
0N/A addLCIDMapEntry(map, "ar_EG", (short) 0x0c01);
0N/A addLCIDMapEntry(map, "zh_HK", (short) 0x0c04);
0N/A addLCIDMapEntry(map, "de_AT", (short) 0x0c07);
0N/A addLCIDMapEntry(map, "en_AU", (short) 0x0c09);
0N/A addLCIDMapEntry(map, "fr_CA", (short) 0x0c0c);
0N/A addLCIDMapEntry(map, "sr_CS", (short) 0x0c1a);
0N/A addLCIDMapEntry(map, "se_FI", (short) 0x0c3b);
0N/A addLCIDMapEntry(map, "qu_PE", (short) 0x0c6b);
0N/A addLCIDMapEntry(map, "ar_LY", (short) 0x1001);
0N/A addLCIDMapEntry(map, "zh_SG", (short) 0x1004);
0N/A addLCIDMapEntry(map, "de_LU", (short) 0x1007);
0N/A addLCIDMapEntry(map, "en_CA", (short) 0x1009);
0N/A addLCIDMapEntry(map, "es_GT", (short) 0x100a);
0N/A addLCIDMapEntry(map, "fr_CH", (short) 0x100c);
0N/A addLCIDMapEntry(map, "hr_BA", (short) 0x101a);
0N/A addLCIDMapEntry(map, "ar_DZ", (short) 0x1401);
0N/A addLCIDMapEntry(map, "zh_MO", (short) 0x1404);
0N/A addLCIDMapEntry(map, "de_LI", (short) 0x1407);
0N/A addLCIDMapEntry(map, "en_NZ", (short) 0x1409);
0N/A addLCIDMapEntry(map, "es_CR", (short) 0x140a);
0N/A addLCIDMapEntry(map, "fr_LU", (short) 0x140c);
0N/A addLCIDMapEntry(map, "bs_BA", (short) 0x141a);
0N/A addLCIDMapEntry(map, "ar_MA", (short) 0x1801);
0N/A addLCIDMapEntry(map, "en_IE", (short) 0x1809);
0N/A addLCIDMapEntry(map, "es_PA", (short) 0x180a);
0N/A addLCIDMapEntry(map, "fr_MC", (short) 0x180c);
0N/A addLCIDMapEntry(map, "sr_BA", (short) 0x181a);
0N/A addLCIDMapEntry(map, "ar_TN", (short) 0x1c01);
0N/A addLCIDMapEntry(map, "en_ZA", (short) 0x1c09);
0N/A addLCIDMapEntry(map, "es_DO", (short) 0x1c0a);
0N/A addLCIDMapEntry(map, "sr_BA", (short) 0x1c1a);
0N/A addLCIDMapEntry(map, "ar_OM", (short) 0x2001);
0N/A addLCIDMapEntry(map, "en_JM", (short) 0x2009);
0N/A addLCIDMapEntry(map, "es_VE", (short) 0x200a);
0N/A addLCIDMapEntry(map, "ar_YE", (short) 0x2401);
0N/A addLCIDMapEntry(map, "es_CO", (short) 0x240a);
0N/A addLCIDMapEntry(map, "ar_SY", (short) 0x2801);
0N/A addLCIDMapEntry(map, "en_BZ", (short) 0x2809);
0N/A addLCIDMapEntry(map, "es_PE", (short) 0x280a);
0N/A addLCIDMapEntry(map, "ar_JO", (short) 0x2c01);
0N/A addLCIDMapEntry(map, "en_TT", (short) 0x2c09);
0N/A addLCIDMapEntry(map, "es_AR", (short) 0x2c0a);
0N/A addLCIDMapEntry(map, "ar_LB", (short) 0x3001);
0N/A addLCIDMapEntry(map, "en_ZW", (short) 0x3009);
0N/A addLCIDMapEntry(map, "es_EC", (short) 0x300a);
0N/A addLCIDMapEntry(map, "ar_KW", (short) 0x3401);
0N/A addLCIDMapEntry(map, "en_PH", (short) 0x3409);
0N/A addLCIDMapEntry(map, "es_CL", (short) 0x340a);
0N/A addLCIDMapEntry(map, "ar_AE", (short) 0x3801);
0N/A addLCIDMapEntry(map, "es_UY", (short) 0x380a);
0N/A addLCIDMapEntry(map, "ar_BH", (short) 0x3c01);
0N/A addLCIDMapEntry(map, "es_PY", (short) 0x3c0a);
0N/A addLCIDMapEntry(map, "ar_QA", (short) 0x4001);
0N/A addLCIDMapEntry(map, "es_BO", (short) 0x400a);
0N/A addLCIDMapEntry(map, "es_SV", (short) 0x440a);
0N/A addLCIDMapEntry(map, "es_HN", (short) 0x480a);
0N/A addLCIDMapEntry(map, "es_NI", (short) 0x4c0a);
0N/A addLCIDMapEntry(map, "es_PR", (short) 0x500a);
0N/A
0N/A lcidMap = map;
0N/A }
0N/A
0N/A public static int getNumFonts() {
0N/A return physicalFonts.size()+maxCompFont;
0N/A }
0N/A
0N/A private static boolean fontSupportsEncoding(Font font, String encoding) {
0N/A return getFont2D(font).supportsEncoding(encoding);
0N/A }
0N/A
0N/A public synchronized static native String getFontPath(boolean noType1Fonts);
0N/A public synchronized static native void setNativeFontPath(String fontPath);
0N/A
0N/A
0N/A private static Thread fileCloser = null;
0N/A static Vector<File> tmpFontFiles = null;
0N/A
0N/A public static Font2D createFont2D(File fontFile, int fontFormat,
0N/A boolean isCopy)
0N/A throws FontFormatException {
0N/A
0N/A String fontFilePath = fontFile.getPath();
0N/A FileFont font2D = null;
0N/A final File fFile = fontFile;
0N/A try {
0N/A switch (fontFormat) {
0N/A case Font.TRUETYPE_FONT:
0N/A font2D = new TrueTypeFont(fontFilePath, null, 0, true);
0N/A break;
0N/A case Font.TYPE1_FONT:
0N/A font2D = new Type1Font(fontFilePath, null);
0N/A break;
0N/A default:
0N/A throw new FontFormatException("Unrecognised Font Format");
0N/A }
0N/A } catch (FontFormatException e) {
0N/A if (isCopy) {
0N/A java.security.AccessController.doPrivileged(
0N/A new java.security.PrivilegedAction() {
0N/A public Object run() {
0N/A fFile.delete();
0N/A return null;
0N/A }
0N/A });
0N/A }
0N/A throw(e);
0N/A }
0N/A if (isCopy) {
0N/A font2D.setFileToRemove(fontFile);
0N/A synchronized (FontManager.class) {
0N/A
0N/A if (tmpFontFiles == null) {
0N/A tmpFontFiles = new Vector<File>();
0N/A }
0N/A tmpFontFiles.add(fontFile);
0N/A
0N/A if (fileCloser == null) {
0N/A final Runnable fileCloserRunnable = new Runnable() {
0N/A public void run() {
0N/A java.security.AccessController.doPrivileged(
0N/A new java.security.PrivilegedAction() {
0N/A public Object run() {
0N/A
0N/A for (int i=0;i<CHANNELPOOLSIZE;i++) {
0N/A if (fontFileCache[i] != null) {
0N/A try {
0N/A fontFileCache[i].close();
0N/A } catch (Exception e) {
0N/A }
0N/A }
0N/A }
0N/A if (tmpFontFiles != null) {
0N/A File[] files = new File[tmpFontFiles.size()];
0N/A files = tmpFontFiles.toArray(files);
0N/A for (int f=0; f<files.length;f++) {
0N/A try {
0N/A files[f].delete();
0N/A } catch (Exception e) {
0N/A }
0N/A }
0N/A }
0N/A
0N/A return null;
0N/A }
0N/A
0N/A });
0N/A }
0N/A };
0N/A java.security.AccessController.doPrivileged(
0N/A new java.security.PrivilegedAction() {
0N/A public Object run() {
0N/A /* The thread must be a member of a thread group
0N/A * which will not get GCed before VM exit.
0N/A * Make its parent the top-level thread group.
0N/A */
0N/A ThreadGroup tg =
0N/A Thread.currentThread().getThreadGroup();
0N/A for (ThreadGroup tgn = tg;
0N/A tgn != null;
0N/A tg = tgn, tgn = tg.getParent());
0N/A fileCloser = new Thread(tg, fileCloserRunnable);
0N/A Runtime.getRuntime().addShutdownHook(fileCloser);
0N/A return null;
0N/A }
0N/A });
0N/A }
0N/A }
0N/A }
0N/A return font2D;
0N/A }
0N/A
0N/A /* remind: used in X11GraphicsEnvironment and called often enough
0N/A * that we ought to obsolete this code
0N/A */
0N/A public synchronized static String getFullNameByFileName(String fileName) {
0N/A PhysicalFont[] physFonts = getPhysicalFonts();
0N/A for (int i=0;i<physFonts.length;i++) {
0N/A if (physFonts[i].platName.equals(fileName)) {
0N/A return (physFonts[i].getFontName(null));
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /*
0N/A * This is called when font is determined to be invalid/bad.
0N/A * It designed to be called (for example) by the font scaler
0N/A * when in processing a font file it is discovered to be incorrect.
0N/A * This is different than the case where fonts are discovered to
0N/A * be incorrect during initial verification, as such fonts are
0N/A * never registered.
0N/A * Handles to this font held are re-directed to a default font.
0N/A * This default may not be an ideal substitute buts it better than
0N/A * crashing This code assumes a PhysicalFont parameter as it doesn't
0N/A * make sense for a Composite to be "bad".
0N/A */
0N/A public static synchronized void deRegisterBadFont(Font2D font2D) {
0N/A if (!(font2D instanceof PhysicalFont)) {
0N/A /* We should never reach here, but just in case */
0N/A return;
0N/A } else {
0N/A if (logging) {
0N/A logger.severe("Deregister bad font: " + font2D);
0N/A }
0N/A replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont());
0N/A }
0N/A }
0N/A
0N/A /*
0N/A * This encapsulates all the work that needs to be done when a
0N/A * Font2D is replaced by a different Font2D.
0N/A */
0N/A public static synchronized void replaceFont(PhysicalFont oldFont,
0N/A PhysicalFont newFont) {
0N/A
0N/A if (oldFont.handle.font2D != oldFont) {
0N/A /* already done */
0N/A return;
0N/A }
0N/A
0N/A /* If we try to replace the font with itself, that won't work,
0N/A * so pick any alternative physical font
0N/A */
0N/A if (oldFont == newFont) {
0N/A if (logging) {
0N/A logger.severe("Can't replace bad font with itself " + oldFont);
0N/A }
0N/A PhysicalFont[] physFonts = getPhysicalFonts();
0N/A for (int i=0; i<physFonts.length;i++) {
0N/A if (physFonts[i] != newFont) {
0N/A newFont = physFonts[i];
0N/A break;
0N/A }
0N/A }
0N/A if (oldFont == newFont) {
0N/A if (logging) {
0N/A logger.severe("This is bad. No good physicalFonts found.");
0N/A }
0N/A return;
0N/A }
0N/A }
0N/A
0N/A /* eliminate references to this font, so it won't be located
0N/A * by future callers, and will be eligible for GC when all
0N/A * references are removed
0N/A */
0N/A oldFont.handle.font2D = newFont;
0N/A physicalFonts.remove(oldFont.fullName);
0N/A fullNameToFont.remove(oldFont.fullName.toLowerCase(Locale.ENGLISH));
0N/A FontFamily.remove(oldFont);
0N/A
0N/A if (localeFullNamesToFont != null) {
0N/A Map.Entry[] mapEntries =
0N/A (Map.Entry[])localeFullNamesToFont.entrySet().
0N/A toArray(new Map.Entry[0]);
0N/A /* Should I be replacing these, or just I just remove
0N/A * the names from the map?
0N/A */
0N/A for (int i=0; i<mapEntries.length;i++) {
0N/A if (mapEntries[i].getValue() == oldFont) {
0N/A try {
0N/A mapEntries[i].setValue(newFont);
0N/A } catch (Exception e) {
0N/A /* some maps don't support this operation.
0N/A * In this case just give up and remove the entry.
0N/A */
0N/A localeFullNamesToFont.remove(mapEntries[i].getKey());
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A for (int i=0; i<maxCompFont; i++) {
0N/A /* Deferred initialization of composites shouldn't be
0N/A * a problem for this case, since a font must have been
0N/A * initialised to be discovered to be bad.
0N/A * Some JRE composites on Solaris use two versions of the same
0N/A * font. The replaced font isn't bad, just "smaller" so there's
0N/A * no need to make the slot point to the new font.
0N/A * Since composites have a direct reference to the Font2D (not
0N/A * via a handle) making this substitution is not safe and could
0N/A * cause an additional problem and so this substitution is
0N/A * warranted only when a font is truly "bad" and could cause
0N/A * a crash. So we now replace it only if its being substituted
0N/A * with some font other than a fontconfig rank font
0N/A * Since in practice a substitution will have the same rank
0N/A * this may never happen, but the code is safer even if its
0N/A * also now a no-op.
0N/A * The only obvious "glitch" from this stems from the current
0N/A * implementation that when asked for the number of glyphs in a
0N/A * composite it lies and returns the number in slot 0 because
0N/A * composite glyphs aren't contiguous. Since we live with that
0N/A * we can live with the glitch that depending on how it was
0N/A * initialised a composite may return different values for this.
0N/A * Fixing the issues with composite glyph ids is tricky as
0N/A * there are exclusion ranges and unlike other fonts even the
0N/A * true "numGlyphs" isn't a contiguous range. Likely the only
0N/A * solution is an API that returns an array of glyph ranges
0N/A * which takes precedence over the existing API. That might
0N/A * also need to address excluding ranges which represent a
0N/A * code point supported by an earlier component.
0N/A */
0N/A if (newFont.getRank() > Font2D.FONT_CONFIG_RANK) {
0N/A compFonts[i].replaceComponentFont(oldFont, newFont);
0N/A }
0N/A }
0N/A }
0N/A
0N/A private static synchronized void loadLocaleNames() {
0N/A if (localeFullNamesToFont != null) {
0N/A return;
0N/A }
0N/A localeFullNamesToFont = new HashMap<String, TrueTypeFont>();
0N/A Font2D[] fonts = getRegisteredFonts();
0N/A for (int i=0; i<fonts.length; i++) {
0N/A if (fonts[i] instanceof TrueTypeFont) {
0N/A TrueTypeFont ttf = (TrueTypeFont)fonts[i];
0N/A String[] fullNames = ttf.getAllFullNames();
0N/A for (int n=0; n<fullNames.length; n++) {
0N/A localeFullNamesToFont.put(fullNames[n], ttf);
0N/A }
0N/A FontFamily family = FontFamily.getFamily(ttf.familyName);
0N/A if (family != null) {
0N/A FontFamily.addLocaleNames(family, ttf.getAllFamilyNames());
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /* This replicate the core logic of findFont2D but operates on
0N/A * all the locale names. This hasn't been merged into findFont2D to
0N/A * keep the logic simpler and reduce overhead, since this case is
0N/A * almost never used. The main case in which it is called is when
0N/A * a bogus font name is used and we need to check all possible names
0N/A * before returning the default case.
0N/A */
0N/A private static Font2D findFont2DAllLocales(String name, int style) {
0N/A
0N/A if (logging) {
0N/A logger.info("Searching localised font names for:" + name);
0N/A }
0N/A
0N/A /* If reach here and no match has been located, then if we have
0N/A * not yet built the map of localeFullNamesToFont for TT fonts, do so
0N/A * now. This method must be called after all fonts have been loaded.
0N/A */
0N/A if (localeFullNamesToFont == null) {
0N/A loadLocaleNames();
0N/A }
0N/A String lowerCaseName = name.toLowerCase();
0N/A Font2D font = null;
0N/A
0N/A /* First see if its a family name. */
0N/A FontFamily family = FontFamily.getLocaleFamily(lowerCaseName);
0N/A if (family != null) {
0N/A font = family.getFont(style);
0N/A if (font == null) {
0N/A font = family.getClosestStyle(style);
0N/A }
0N/A if (font != null) {
0N/A return font;
0N/A }
0N/A }
0N/A
0N/A /* If it wasn't a family name, it should be a full name. */
0N/A synchronized (FontManager.class) {
0N/A font = localeFullNamesToFont.get(name);
0N/A }
0N/A if (font != null) {
0N/A if (font.style == style || style == Font.PLAIN) {
0N/A return font;
0N/A } else {
0N/A family = FontFamily.getFamily(font.getFamilyName(null));
0N/A if (family != null) {
0N/A Font2D familyFont = family.getFont(style);
0N/A /* We exactly matched the requested style, use it! */
0N/A if (familyFont != null) {
0N/A return familyFont;
0N/A } else {
0N/A familyFont = family.getClosestStyle(style);
0N/A if (familyFont != null) {
0N/A /* The next check is perhaps one
0N/A * that shouldn't be done. ie if we get this
0N/A * far we have probably as close a match as we
0N/A * are going to get. We could load all fonts to
0N/A * see if somehow some parts of the family are
0N/A * loaded but not all of it.
0N/A * This check is commented out for now.
0N/A */
0N/A if (!familyFont.canDoStyle(style)) {
0N/A familyFont = null;
0N/A }
0N/A return familyFont;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A return font;
0N/A }
0N/A
0N/A /* Supporting "alternate" composite fonts on 2D graphics objects
0N/A * is accessed by the application by calling methods on the local
0N/A * GraphicsEnvironment. The overall implementation is described
0N/A * in one place, here, since otherwise the implementation is spread
0N/A * around it may be difficult to track.
0N/A * The methods below call into SunGraphicsEnvironment which creates a
0N/A * new FontConfiguration instance. The FontConfiguration class,
0N/A * and its platform sub-classes are updated to take parameters requesting
0N/A * these behaviours. This is then used to create new composite font
0N/A * instances. Since this calls the initCompositeFont method in
0N/A * SunGraphicsEnvironment it performs the same initialization as is
0N/A * performed normally. There may be some duplication of effort, but
0N/A * that code is already written to be able to perform properly if called
0N/A * to duplicate work. The main difference is that if we detect we are
0N/A * running in an applet/browser/Java plugin environment these new fonts
0N/A * are not placed in the "default" maps but into an AppContext instance.
0N/A * The font lookup mechanism in java.awt.Font.getFont2D() is also updated
0N/A * so that look-up for composite fonts will in that case always
0N/A * do a lookup rather than returning a cached result.
0N/A * This is inefficient but necessary else singleton java.awt.Font
0N/A * instances would not retrieve the correct Font2D for the appcontext.
0N/A * sun.font.FontManager.findFont2D is also updated to that it uses
0N/A * a name map cache specific to that appcontext.
0N/A *
0N/A * Getting an AppContext is expensive, so there is a global variable
0N/A * that records whether these methods have ever been called and can
0N/A * avoid the expense for almost all applications. Once the correct
0N/A * CompositeFont is associated with the Font, everything should work
0N/A * through existing mechanisms.
0N/A * A special case is that GraphicsEnvironment.getAllFonts() must
0N/A * return an AppContext specific list.
0N/A *
0N/A * Calling the methods below is "heavyweight" but it is expected that
0N/A * these methods will be called very rarely.
0N/A *
0N/A * If usingPerAppContextComposites is true, we are in "applet"
0N/A * (eg browser) enviroment and at least one context has selected
0N/A * an alternate composite font behaviour.
0N/A * If usingAlternateComposites is true, we are not in an "applet"
0N/A * environment and the (single) application has selected
0N/A * an alternate composite font behaviour.
0N/A *
0N/A * - Printing: The implementation delegates logical fonts to an AWT
0N/A * mechanism which cannot use these alternate configurations.
0N/A * We can detect that alternate fonts are in use and back-off to 2D, but
0N/A * that uses outlines. Much of this can be fixed with additional work
0N/A * but that may have to wait. The results should be correct, just not
0N/A * optimal.
0N/A */
0N/A private static final Object altJAFontKey = new Object();
0N/A private static final Object localeFontKey = new Object();
0N/A private static final Object proportionalFontKey = new Object();
0N/A public static boolean usingPerAppContextComposites = false;
0N/A private static boolean usingAlternateComposites = false;
0N/A
0N/A /* These values are used only if we are running as a standalone
0N/A * application, as determined by maybeMultiAppContext();
0N/A */
0N/A private static boolean gAltJAFont = false;
0N/A private static boolean gLocalePref = false;
0N/A private static boolean gPropPref = false;
0N/A
0N/A /* This method doesn't check if alternates are selected in this app
0N/A * context. Its used by the FontMetrics caching code which in such
0N/A * a case cannot retrieve a cached metrics solely on the basis of
0N/A * the Font.equals() method since it needs to also check if the Font2D
0N/A * is the same.
0N/A * We also use non-standard composites for Swing native L&F fonts on
0N/A * Windows. In that case the policy is that the metrics reported are
0N/A * based solely on the physical font in the first slot which is the
0N/A * visible java.awt.Font. So in that case the metrics cache which tests
0N/A * the Font does what we want. In the near future when we expand the GTK
0N/A * logical font definitions we may need to revisit this if GTK reports
0N/A * combined metrics instead. For now though this test can be simple.
0N/A */
0N/A static boolean maybeUsingAlternateCompositeFonts() {
0N/A return usingAlternateComposites || usingPerAppContextComposites;
0N/A }
0N/A
0N/A public static boolean usingAlternateCompositeFonts() {
0N/A return (usingAlternateComposites ||
0N/A (usingPerAppContextComposites &&
0N/A AppContext.getAppContext().get(CompositeFont.class) != null));
0N/A }
0N/A
0N/A private static boolean maybeMultiAppContext() {
0N/A Boolean appletSM = (Boolean)
0N/A java.security.AccessController.doPrivileged(
0N/A new java.security.PrivilegedAction() {
0N/A public Object run() {
0N/A SecurityManager sm = System.getSecurityManager();
0N/A return new Boolean
0N/A (sm instanceof sun.applet.AppletSecurity);
0N/A }
0N/A });
0N/A return appletSM.booleanValue();
0N/A }
0N/A
0N/A /* Modifies the behaviour of a subsequent call to preferLocaleFonts()
0N/A * to use Mincho instead of Gothic for dialoginput in JA locales
0N/A * on windows. Not needed on other platforms.
0N/A */
0N/A public static synchronized void useAlternateFontforJALocales() {
0N/A
0N/A if (!isWindows) {
0N/A return;
0N/A }
0N/A
0N/A initSGEnv();
0N/A if (!maybeMultiAppContext()) {
0N/A gAltJAFont = true;
0N/A } else {
0N/A AppContext appContext = AppContext.getAppContext();
0N/A appContext.put(altJAFontKey, altJAFontKey);
0N/A }
0N/A }
0N/A
0N/A public static boolean usingAlternateFontforJALocales() {
0N/A if (!maybeMultiAppContext()) {
0N/A return gAltJAFont;
0N/A } else {
0N/A AppContext appContext = AppContext.getAppContext();
0N/A return appContext.get(altJAFontKey) == altJAFontKey;
0N/A }
0N/A }
0N/A
0N/A public static synchronized void preferLocaleFonts() {
0N/A
0N/A initSGEnv();
0N/A
0N/A /* Test if re-ordering will have any effect */
0N/A if (!FontConfiguration.willReorderForStartupLocale()) {
0N/A return;
0N/A }
0N/A
0N/A if (!maybeMultiAppContext()) {
0N/A if (gLocalePref == true) {
0N/A return;
0N/A }
0N/A gLocalePref = true;
0N/A sgEnv.createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
0N/A usingAlternateComposites = true;
0N/A } else {
0N/A AppContext appContext = AppContext.getAppContext();
0N/A if (appContext.get(localeFontKey) == localeFontKey) {
0N/A return;
0N/A }
0N/A appContext.put(localeFontKey, localeFontKey);
0N/A boolean acPropPref =
0N/A appContext.get(proportionalFontKey) == proportionalFontKey;
0N/A ConcurrentHashMap<String, Font2D>
0N/A altNameCache = new ConcurrentHashMap<String, Font2D> ();
0N/A /* If there is an existing hashtable, we can drop it. */
0N/A appContext.put(CompositeFont.class, altNameCache);
0N/A usingPerAppContextComposites = true;
0N/A sgEnv.createCompositeFonts(altNameCache, true, acPropPref);
0N/A }
0N/A }
0N/A
0N/A public static synchronized void preferProportionalFonts() {
0N/A
0N/A /* If no proportional fonts are configured, there's no need
0N/A * to take any action.
0N/A */
0N/A if (!FontConfiguration.hasMonoToPropMap()) {
0N/A return;
0N/A }
0N/A
0N/A initSGEnv();
0N/A
0N/A if (!maybeMultiAppContext()) {
0N/A if (gPropPref == true) {
0N/A return;
0N/A }
0N/A gPropPref = true;
0N/A sgEnv.createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
0N/A usingAlternateComposites = true;
0N/A } else {
0N/A AppContext appContext = AppContext.getAppContext();
0N/A if (appContext.get(proportionalFontKey) == proportionalFontKey) {
0N/A return;
0N/A }
0N/A appContext.put(proportionalFontKey, proportionalFontKey);
0N/A boolean acLocalePref =
0N/A appContext.get(localeFontKey) == localeFontKey;
0N/A ConcurrentHashMap<String, Font2D>
0N/A altNameCache = new ConcurrentHashMap<String, Font2D> ();
0N/A /* If there is an existing hashtable, we can drop it. */
0N/A appContext.put(CompositeFont.class, altNameCache);
0N/A usingPerAppContextComposites = true;
0N/A sgEnv.createCompositeFonts(altNameCache, acLocalePref, true);
0N/A }
0N/A }
0N/A
0N/A private static HashSet<String> installedNames = null;
0N/A private static HashSet<String> getInstalledNames() {
0N/A if (installedNames == null) {
0N/A Locale l = sgEnv.getSystemStartupLocale();
0N/A String[] installedFamilies = sgEnv.getInstalledFontFamilyNames(l);
0N/A Font[] installedFonts = sgEnv.getAllInstalledFonts();
0N/A HashSet<String> names = new HashSet<String>();
0N/A for (int i=0; i<installedFamilies.length; i++) {
0N/A names.add(installedFamilies[i].toLowerCase(l));
0N/A }
0N/A for (int i=0; i<installedFonts.length; i++) {
0N/A names.add(installedFonts[i].getFontName(l).toLowerCase(l));
0N/A }
0N/A installedNames = names;
0N/A }
0N/A return installedNames;
0N/A }
0N/A
0N/A /* Keys are used to lookup per-AppContext Hashtables */
0N/A private static final Object regFamilyKey = new Object();
0N/A private static final Object regFullNameKey = new Object();
0N/A private static Hashtable<String,FontFamily> createdByFamilyName;
0N/A private static Hashtable<String,Font2D> createdByFullName;
0N/A private static boolean fontsAreRegistered = false;
0N/A private static boolean fontsAreRegisteredPerAppContext = false;
0N/A
0N/A public static boolean registerFont(Font font) {
0N/A /* This method should not be called with "null".
0N/A * It is the caller's responsibility to ensure that.
0N/A */
0N/A if (font == null) {
0N/A return false;
0N/A }
0N/A
0N/A /* Initialise these objects only once we start to use this API */
0N/A synchronized (regFamilyKey) {
0N/A if (createdByFamilyName == null) {
0N/A createdByFamilyName = new Hashtable<String,FontFamily>();
0N/A createdByFullName = new Hashtable<String,Font2D>();
0N/A }
0N/A }
0N/A
0N/A if (!isCreatedFont(font)) {
0N/A return false;
0N/A }
0N/A if (sgEnv == null) {
0N/A initSGEnv();
0N/A }
0N/A /* We want to ensure that this font cannot override existing
0N/A * installed fonts. Check these conditions :
0N/A * - family name is not that of an installed font
0N/A * - full name is not that of an installed font
0N/A * - family name is not the same as the full name of an installed font
0N/A * - full name is not the same as the family name of an installed font
0N/A * The last two of these may initially look odd but the reason is
0N/A * that (unfortunately) Font constructors do not distinuguish these.
0N/A * An extreme example of such a problem would be a font which has
0N/A * family name "Dialog.Plain" and full name of "Dialog".
0N/A * The one arguably overly stringent restriction here is that if an
0N/A * application wants to supply a new member of an existing family
0N/A * It will get rejected. But since the JRE can perform synthetic
0N/A * styling in many cases its not necessary.
0N/A * We don't apply the same logic to registered fonts. If apps want
0N/A * to do this lets assume they have a reason. It won't cause problems
0N/A * except for themselves.
0N/A */
0N/A HashSet<String> names = getInstalledNames();
0N/A Locale l = sgEnv.getSystemStartupLocale();
0N/A String familyName = font.getFamily(l).toLowerCase();
0N/A String fullName = font.getFontName(l).toLowerCase();
0N/A if (names.contains(familyName) || names.contains(fullName)) {
0N/A return false;
0N/A }
0N/A
0N/A /* Checks passed, now register the font */
0N/A Hashtable<String,FontFamily> familyTable;
0N/A Hashtable<String,Font2D> fullNameTable;
0N/A if (!maybeMultiAppContext()) {
0N/A familyTable = createdByFamilyName;
0N/A fullNameTable = createdByFullName;
0N/A fontsAreRegistered = true;
0N/A } else {
0N/A AppContext appContext = AppContext.getAppContext();
0N/A familyTable =
0N/A (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
0N/A fullNameTable =
0N/A (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
0N/A if (familyTable == null) {
0N/A familyTable = new Hashtable<String,FontFamily>();
0N/A fullNameTable = new Hashtable<String,Font2D>();
0N/A appContext.put(regFamilyKey, familyTable);
0N/A appContext.put(regFullNameKey, fullNameTable);
0N/A }
0N/A fontsAreRegisteredPerAppContext = true;
0N/A }
0N/A /* Create the FontFamily and add font to the tables */
0N/A Font2D font2D = getFont2D(font);
0N/A int style = font2D.getStyle();
0N/A FontFamily family = familyTable.get(familyName);
0N/A if (family == null) {
0N/A family = new FontFamily(font.getFamily(l));
0N/A familyTable.put(familyName, family);
0N/A }
0N/A /* Remove name cache entries if not using app contexts.
0N/A * To accommodate a case where code may have registered first a plain
0N/A * family member and then used it and is now registering a bold family
0N/A * member, we need to remove all members of the family, so that the
0N/A * new style can get picked up rather than continuing to synthesise.
0N/A */
0N/A if (fontsAreRegistered) {
0N/A removeFromCache(family.getFont(Font.PLAIN));
0N/A removeFromCache(family.getFont(Font.BOLD));
0N/A removeFromCache(family.getFont(Font.ITALIC));
0N/A removeFromCache(family.getFont(Font.BOLD|Font.ITALIC));
0N/A removeFromCache(fullNameTable.get(fullName));
0N/A }
0N/A family.setFont(font2D, style);
0N/A fullNameTable.put(fullName, font2D);
0N/A return true;
0N/A }
0N/A
0N/A /* Remove from the name cache all references to the Font2D */
0N/A private static void removeFromCache(Font2D font) {
0N/A if (font == null) {
0N/A return;
0N/A }
0N/A String[] keys = (String[])(fontNameCache.keySet().toArray(STR_ARRAY));
0N/A for (int k=0; k<keys.length;k++) {
0N/A if (fontNameCache.get(keys[k]) == font) {
0N/A fontNameCache.remove(keys[k]);
0N/A }
0N/A }
0N/A }
0N/A
0N/A // It may look odd to use TreeMap but its more convenient to the caller.
0N/A public static TreeMap<String, String> getCreatedFontFamilyNames() {
0N/A
0N/A Hashtable<String,FontFamily> familyTable;
0N/A if (fontsAreRegistered) {
0N/A familyTable = createdByFamilyName;
0N/A } else if (fontsAreRegisteredPerAppContext) {
0N/A AppContext appContext = AppContext.getAppContext();
0N/A familyTable =
0N/A (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
0N/A } else {
0N/A return null;
0N/A }
0N/A
0N/A Locale l = sgEnv.getSystemStartupLocale();
0N/A synchronized (familyTable) {
0N/A TreeMap<String, String> map = new TreeMap<String, String>();
0N/A for (FontFamily f : familyTable.values()) {
0N/A Font2D font2D = f.getFont(Font.PLAIN);
0N/A if (font2D == null) {
0N/A font2D = f.getClosestStyle(Font.PLAIN);
0N/A }
0N/A String name = font2D.getFamilyName(l);
0N/A map.put(name.toLowerCase(l), name);
0N/A }
0N/A return map;
0N/A }
0N/A }
0N/A
0N/A public static Font[] getCreatedFonts() {
0N/A
0N/A Hashtable<String,Font2D> nameTable;
0N/A if (fontsAreRegistered) {
0N/A nameTable = createdByFullName;
0N/A } else if (fontsAreRegisteredPerAppContext) {
0N/A AppContext appContext = AppContext.getAppContext();
0N/A nameTable =
0N/A (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
0N/A } else {
0N/A return null;
0N/A }
0N/A
0N/A Locale l = sgEnv.getSystemStartupLocale();
0N/A synchronized (nameTable) {
0N/A Font[] fonts = new Font[nameTable.size()];
0N/A int i=0;
0N/A for (Font2D font2D : nameTable.values()) {
0N/A fonts[i++] = new Font(font2D.getFontName(l), Font.PLAIN, 1);
0N/A }
0N/A return fonts;
0N/A }
0N/A }
0N/A
0N/A /* Begin support for GTK Look and Feel - query libfontconfig and
0N/A * return a composite Font to Swing that uses the desktop font(s).
0N/A */
0N/A
0N/A /* A small "map" from GTK/fontconfig names to the equivalent JDK
0N/A * logical font name.
0N/A */
0N/A private static final String[][] nameMap = {
0N/A {"sans", "sansserif"},
0N/A {"sans-serif", "sansserif"},
0N/A {"serif", "serif"},
0N/A {"monospace", "monospaced"}
0N/A };
0N/A
0N/A public static String mapFcName(String name) {
0N/A for (int i = 0; i < nameMap.length; i++) {
0N/A if (name.equals(nameMap[i][0])) {
0N/A return nameMap[i][1];
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /* fontconfig recognises slants roman, italic, as well as oblique,
0N/A * and a slew of weights, where the ones that matter here are
0N/A * regular and bold.
0N/A * To fully qualify what we want, we can for example ask for (eg)
0N/A * Font.PLAIN : "serif:regular:roman"
0N/A * Font.BOLD : "serif:bold:roman"
0N/A * Font.ITALIC : "serif:regular:italic"
0N/A * Font.BOLD|Font.ITALIC : "serif:bold:italic"
0N/A */
0N/A private static String[] fontConfigNames = {
0N/A "sans:regular:roman",
0N/A "sans:bold:roman",
0N/A "sans:regular:italic",
0N/A "sans:bold:italic",
0N/A
0N/A "serif:regular:roman",
0N/A "serif:bold:roman",
0N/A "serif:regular:italic",
0N/A "serif:bold:italic",
0N/A
0N/A "monospace:regular:roman",
0N/A "monospace:bold:roman",
0N/A "monospace:regular:italic",
0N/A "monospace:bold:italic",
0N/A };
0N/A
0N/A /* This class is just a data structure.
0N/A */
0N/A private static class FontConfigInfo {
0N/A String fcName; // eg sans
0N/A String fcFamily; // eg sans
0N/A String jdkName; // eg sansserif
0N/A int style; // eg 0=PLAIN
0N/A String familyName; // eg Bitstream Vera Sans
0N/A String fontFile; // eg /usr/X11/lib/fonts/foo.ttf
0N/A //boolean preferBitmaps; // if embedded bitmaps preferred over AA
0N/A CompositeFont compFont; // null if not yet created/known.
0N/A }
0N/A
0N/A
0N/A private static String getFCLocaleStr() {
0N/A Locale l = SunToolkit.getStartupLocale();
0N/A String localeStr = l.getLanguage();
0N/A String country = l.getCountry();
0N/A if (!country.equals("")) {
0N/A localeStr = localeStr + "-" + country;
0N/A }
0N/A return localeStr;
0N/A }
0N/A
0N/A private static native int
0N/A getFontConfigAASettings(String locale, String fcFamily);
0N/A
0N/A /* This is public solely so that for debugging purposes it can be called
0N/A * with other names, which might (eg) include a size, eg "sans-24"
0N/A * The return value is a text aa rendering hint value.
0N/A * Normally we should call the no-args version.
0N/A */
0N/A public static Object getFontConfigAAHint(String fcFamily) {
0N/A if (isWindows) {
0N/A return null;
0N/A } else {
0N/A int hint = getFontConfigAASettings(getFCLocaleStr(), fcFamily);
0N/A if (hint < 0) {
0N/A return null;
0N/A } else {
0N/A return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
0N/A hint);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /* Called from code that needs to know what are the AA settings
0N/A * that apps using FC would pick up for the default desktop font.
0N/A * Note apps can change the default desktop font. etc, so this
0N/A * isn't certain to be right but its going to correct for most cases.
0N/A * Native return values map to the text aa values in sun.awt.SunHints.
0N/A * which is used to look up the renderinghint value object.
0N/A */
0N/A public static Object getFontConfigAAHint() {
0N/A return getFontConfigAAHint("sans");
0N/A }
0N/A
0N/A /* This array has the array elements created in Java code and is
0N/A * passed down to native to be filled in.
0N/A */
0N/A private static FontConfigInfo[] fontConfigFonts;
0N/A
0N/A /* Return an array of FontConfigInfo structs describing the primary
0N/A * font located for each of fontconfig/GTK/Pango's logical font names.
0N/A */
0N/A private static native void getFontConfig(String locale,
0N/A FontConfigInfo[] fonts);
0N/A
0N/A
0N/A /* This can be made public if it's needed to force a re-read
0N/A * rather than using the cached values. The re-read would be needed
0N/A * only if some event signalled that the fontconfig has changed.
0N/A * In that event this method would need to return directly the array
0N/A * to be used by the caller in case it subsequently changed.
0N/A */
0N/A private static void initFontConfigFonts() {
0N/A
0N/A if (fontConfigFonts != null) {
0N/A return;
0N/A }
0N/A
0N/A if (isWindows) {
0N/A return;
0N/A }
0N/A
0N/A long t0 = 0;
0N/A if (logging) {
0N/A t0 = System.currentTimeMillis();
0N/A }
0N/A
0N/A FontConfigInfo[] fontArr = new FontConfigInfo[fontConfigNames.length];
0N/A for (int i = 0; i< fontArr.length; i++) {
0N/A fontArr[i] = new FontConfigInfo();
0N/A fontArr[i].fcName = fontConfigNames[i];
0N/A int colonPos = fontArr[i].fcName.indexOf(':');
0N/A fontArr[i].fcFamily = fontArr[i].fcName.substring(0, colonPos);
0N/A fontArr[i].jdkName = mapFcName(fontArr[i].fcFamily);
0N/A fontArr[i].style = i % 4; // depends on array order.
0N/A }
0N/A getFontConfig(getFCLocaleStr(), fontArr);
0N/A fontConfigFonts = fontArr;
0N/A
0N/A if (logging) {
0N/A long t1 = System.currentTimeMillis();
0N/A logger.info("Time spent accessing fontconfig="+(t1-t0)+"ms.");
0N/A
0N/A for (int i = 0; i< fontConfigFonts.length; i++) {
0N/A FontConfigInfo fci = fontConfigFonts[i];
0N/A logger.info("FC font " + fci.fcName+" maps to family " +
0N/A fci.familyName + " in file " + fci.fontFile);
0N/A }
0N/A }
0N/A }
0N/A
0N/A private static PhysicalFont registerFromFcInfo(FontConfigInfo fcInfo) {
0N/A
0N/A /* If it's a TTC file we need to know that as we will need to
0N/A * make sure we return the right font */
0N/A int offset = fcInfo.fontFile.length()-4;
0N/A if (offset <= 0) {
0N/A return null;
0N/A }
0N/A String ext = fcInfo.fontFile.substring(offset).toLowerCase();
0N/A boolean isTTC = ext.equals(".ttc");
0N/A
0N/A /* If this file is already registered, can just return its font.
0N/A * However we do need to check in case it's a TTC as we need
0N/A * a specific font, so rather than directly returning it, let
0N/A * findFont2D resolve that.
0N/A */
0N/A PhysicalFont physFont = registeredFontFiles.get(fcInfo.fontFile);
0N/A if (physFont != null) {
0N/A if (isTTC) {
0N/A Font2D f2d = findFont2D(fcInfo.familyName,
0N/A fcInfo.style, NO_FALLBACK);
0N/A if (f2d instanceof PhysicalFont) { /* paranoia */
0N/A return (PhysicalFont)f2d;
0N/A } else {
0N/A return null;
0N/A }
0N/A } else {
0N/A return physFont;
0N/A }
0N/A }
0N/A
0N/A /* If the font may hide a JRE font (eg fontconfig says it is
0N/A * Lucida Sans), we want to use the JRE version, so make it
0N/A * point to the JRE font.
0N/A */
0N/A physFont = findJREDeferredFont(fcInfo.familyName, fcInfo.style);
0N/A
0N/A /* It is also possible the font file is on the "deferred" list,
0N/A * in which case we can just initialise it now.
0N/A */
0N/A if (physFont == null &&
0N/A deferredFontFiles.get(fcInfo.fontFile) != null) {
0N/A physFont = initialiseDeferredFont(fcInfo.fontFile);
0N/A /* use findFont2D to get the right font from TTC's */
0N/A if (physFont != null) {
0N/A if (isTTC) {
0N/A Font2D f2d = findFont2D(fcInfo.familyName,
0N/A fcInfo.style, NO_FALLBACK);
0N/A if (f2d instanceof PhysicalFont) { /* paranoia */
0N/A return (PhysicalFont)f2d;
0N/A } else {
0N/A return null;
0N/A }
0N/A } else {
0N/A return physFont;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /* In the majority of cases we reach here, and need to determine
0N/A * the type and rank to register the font.
0N/A */
0N/A if (physFont == null) {
0N/A int fontFormat = FONTFORMAT_NONE;
0N/A int fontRank = Font2D.UNKNOWN_RANK;
0N/A
0N/A if (ext.equals(".ttf") || isTTC) {
0N/A fontFormat = FONTFORMAT_TRUETYPE;
0N/A fontRank = Font2D.TTF_RANK;
0N/A } else if (ext.equals(".pfa") || ext.equals(".pfb")) {
0N/A fontFormat = FONTFORMAT_TYPE1;
0N/A fontRank = Font2D.TYPE1_RANK;
0N/A }
0N/A physFont = registerFontFile(fcInfo.fontFile, null,
0N/A fontFormat, true, fontRank);
0N/A }
0N/A return physFont;
0N/A }
0N/A
0N/A private static String[] getPlatformFontDirs() {
0N/A String path = getFontPath(true);
0N/A StringTokenizer parser =
0N/A new StringTokenizer(path, File.pathSeparator);
0N/A ArrayList<String> pathList = new ArrayList<String>();
0N/A try {
0N/A while (parser.hasMoreTokens()) {
0N/A pathList.add(parser.nextToken());
0N/A }
0N/A } catch (NoSuchElementException e) {
0N/A }
0N/A return pathList.toArray(new String[0]);
0N/A }
0N/A
0N/A /** returns an array of two strings. The first element is the
0N/A * name of the font. The second element is the file name.
0N/A */
0N/A private static String[] defaultPlatformFont = null;
0N/A public static String[] getDefaultPlatformFont() {
0N/A
0N/A if (defaultPlatformFont != null) {
0N/A return defaultPlatformFont;
0N/A }
0N/A
0N/A String[] info = new String[2];
0N/A if (isWindows) {
0N/A info[0] = "Arial";
0N/A info[1] = "c:\\windows\\fonts";
0N/A final String[] dirs = getPlatformFontDirs();
0N/A if (dirs.length > 1) {
0N/A String dir = (String)
0N/A AccessController.doPrivileged(new PrivilegedAction() {
0N/A public Object run() {
0N/A for (int i=0; i<dirs.length; i++) {
0N/A String path =
0N/A dirs[i] + File.separator + "arial.ttf";
0N/A File file = new File(path);
0N/A if (file.exists()) {
0N/A return dirs[i];
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A });
0N/A if (dir != null) {
0N/A info[1] = dir;
0N/A }
0N/A } else {
0N/A info[1] = dirs[0];
0N/A }
0N/A info[1] = info[1] + File.separator + "arial.ttf";
0N/A } else {
0N/A initFontConfigFonts();
0N/A for (int i=0; i<fontConfigFonts.length; i++) {
0N/A if ("sans".equals(fontConfigFonts[i].fcFamily) &&
0N/A 0 == fontConfigFonts[i].style) {
0N/A info[0] = fontConfigFonts[i].familyName;
0N/A info[1] = fontConfigFonts[i].fontFile;
0N/A break;
0N/A }
0N/A }
0N/A /* Absolute last ditch attempt in the face of fontconfig problems.
0N/A * If we didn't match, pick the first, or just make something
0N/A * up so we don't NPE.
0N/A */
0N/A if (info[0] == null) {
0N/A if (fontConfigFonts.length > 0 &&
0N/A fontConfigFonts[0].fontFile != null) {
0N/A info[0] = fontConfigFonts[0].familyName;
0N/A info[1] = fontConfigFonts[0].fontFile;
0N/A } else {
0N/A info[0] = "Dialog";
0N/A info[1] = "/dialog.ttf";
0N/A }
0N/A }
0N/A }
0N/A defaultPlatformFont = info;
0N/A return defaultPlatformFont;
0N/A }
0N/A
0N/A private FontConfigInfo getFontConfigInfo() {
0N/A initFontConfigFonts();
0N/A for (int i=0; i<fontConfigFonts.length; i++) {
0N/A if ("sans".equals(fontConfigFonts[i].fcFamily) &&
0N/A 0 == fontConfigFonts[i].style) {
0N/A return fontConfigFonts[i];
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A /*
0N/A * We need to return a Composite font which has as the font in
0N/A * its first slot one obtained from fontconfig.
0N/A */
0N/A private static CompositeFont getFontConfigFont(String name, int style) {
0N/A
0N/A name = name.toLowerCase();
0N/A
0N/A initFontConfigFonts();
0N/A
0N/A FontConfigInfo fcInfo = null;
0N/A for (int i=0; i<fontConfigFonts.length; i++) {
0N/A if (name.equals(fontConfigFonts[i].fcFamily) &&
0N/A style == fontConfigFonts[i].style) {
0N/A fcInfo = fontConfigFonts[i];
0N/A break;
0N/A }
0N/A }
0N/A if (fcInfo == null) {
0N/A fcInfo = fontConfigFonts[0];
0N/A }
0N/A
0N/A if (logging) {
0N/A logger.info("FC name=" + name + " style=" + style + " uses " +
0N/A fcInfo.familyName + " in file: " + fcInfo.fontFile);
0N/A }
0N/A
0N/A if (fcInfo.compFont != null) {
0N/A return fcInfo.compFont;
0N/A }
0N/A
0N/A /* jdkFont is going to be used for slots 1..N and as a fallback.
0N/A * Slot 0 will be the physical font from fontconfig.
0N/A */
0N/A CompositeFont jdkFont = (CompositeFont)
0N/A findFont2D(fcInfo.jdkName, style, LOGICAL_FALLBACK);
0N/A
0N/A if (fcInfo.familyName == null || fcInfo.fontFile == null) {
0N/A return (fcInfo.compFont = jdkFont);
0N/A }
0N/A
0N/A /* First, see if the family and exact style is already registered.
0N/A * If it is, use it. If it's not, then try to register it.
0N/A * If that registration fails (signalled by null) just return the
0N/A * regular JDK composite.
0N/A * Algorithmically styled fonts won't match on exact style, so
0N/A * will fall through this code, but the regisration code will
0N/A * find that file already registered and return its font.
0N/A */
0N/A FontFamily family = FontFamily.getFamily(fcInfo.familyName);
0N/A PhysicalFont physFont = null;
0N/A if (family != null) {
0N/A Font2D f2D = family.getFontWithExactStyleMatch(fcInfo.style);
0N/A if (f2D instanceof PhysicalFont) {
0N/A physFont = (PhysicalFont)f2D;
0N/A }
0N/A }
0N/A
0N/A if (physFont == null || !fcInfo.fontFile.equals(physFont.platName)) {
0N/A physFont = registerFromFcInfo(fcInfo);
0N/A if (physFont == null) {
0N/A return (fcInfo.compFont = jdkFont);
0N/A }
0N/A family = FontFamily.getFamily(physFont.getFamilyName(null));
0N/A }
0N/A
0N/A /* Now register the fonts in the family (the other styles) after
0N/A * checking that they aren't already registered and are actually in
0N/A * a different file. They may be the same file in CJK cases.
0N/A * For cases where they are different font files - eg as is common for
0N/A * Latin fonts, then we rely on fontconfig to report these correctly.
0N/A * Assume that all styles of this font are found by fontconfig,
0N/A * so we can find all the family members which must be registered
0N/A * together to prevent synthetic styling.
0N/A */
0N/A for (int i=0; i<fontConfigFonts.length; i++) {
0N/A FontConfigInfo fc = fontConfigFonts[i];
0N/A if (fc != fcInfo &&
0N/A physFont.getFamilyName(null).equals(fc.familyName) &&
0N/A !fc.fontFile.equals(physFont.platName) &&
0N/A family.getFontWithExactStyleMatch(fc.style) == null) {
0N/A
0N/A registerFromFcInfo(fontConfigFonts[i]);
0N/A }
0N/A }
0N/A
0N/A /* Now we have a physical font. We will back this up with the JDK
0N/A * logical font (sansserif, serif, or monospaced) that corresponds
0N/A * to the Pango/GTK/FC logical font name.
0N/A */
0N/A return (fcInfo.compFont = new CompositeFont(physFont, jdkFont));
0N/A }
0N/A
0N/A /* This is called by Swing passing in a fontconfig family name
0N/A * such as "sans". In return Swing gets a FontUIResource instance
0N/A * that has queried fontconfig to resolve the font(s) used for this.
0N/A * Fontconfig will if asked return a list of fonts to give the largest
0N/A * possible code point coverage.
0N/A * For now we use only the first font returned by fontconfig, and
0N/A * back it up with the most closely matching JDK logical font.
0N/A * Essentially this means pre-pending what we return now with fontconfig's
0N/A * preferred physical font. This could lead to some duplication in cases,
0N/A * if we already included that font later. We probably should remove such
0N/A * duplicates, but it is not a significant problem. It can be addressed
0N/A * later as part of creating a Composite which uses more of the
0N/A * same fonts as fontconfig. At that time we also should pay more
0N/A * attention to the special rendering instructions fontconfig returns,
0N/A * such as whether we should prefer embedded bitmaps over antialiasing.
0N/A * There's no way to express that via a Font at present.
0N/A */
0N/A public static FontUIResource getFontConfigFUIR(String fcFamily,
0N/A int style, int size) {
0N/A
0N/A String mappedName = mapFcName(fcFamily);
0N/A if (mappedName == null) {
0N/A mappedName = "sansserif";
0N/A }
0N/A
0N/A /* If GTK L&F were to be used on windows, we need to return
0N/A * something. Since on windows Swing won't have the code to
0N/A * call fontconfig, even if it is present, fcFamily and mapped
0N/A * name will default to sans and therefore sansserif so this
0N/A * should be fine.
0N/A */
0N/A if (isWindows) {
0N/A return new FontUIResource(mappedName, style, size);
0N/A }
0N/A
0N/A CompositeFont font2D = getFontConfigFont(fcFamily, style);
0N/A if (font2D == null) { // Not expected, just a precaution.
0N/A return new FontUIResource(mappedName, style, size);
0N/A }
0N/A
0N/A /* The name of the font will be that of the physical font in slot,
0N/A * but by setting the handle to that of the CompositeFont it
0N/A * renders as that CompositeFont.
0N/A * It also needs to be marked as a created font which is the
0N/A * current mechanism to signal that deriveFont etc must copy
0N/A * the handle from the original font.
0N/A */
0N/A FontUIResource fuir =
0N/A new FontUIResource(font2D.getFamilyName(null), style, size);
0N/A setFont2D(fuir, font2D.handle);
0N/A setCreatedFont(fuir);
0N/A return fuir;
0N/A }
0N/A
0N/A /* The following fields and methods which relate to layout
0N/A * perhaps belong in some other class but FontManager is already
0N/A * widely used as an entry point for other JDK code that needs
0N/A * access to the font system internals.
0N/A */
0N/A
0N/A /**
0N/A * Referenced by code in the JDK which wants to test for the
0N/A * minimum char code for which layout may be required.
0N/A * Note that even basic latin text can benefit from ligatures,
0N/A * eg "ffi" but we presently apply those only if explicitly
0N/A * requested with TextAttribute.LIGATURES_ON.
0N/A * The value here indicates the lowest char code for which failing
0N/A * to invoke layout would prevent acceptable rendering.
0N/A */
0N/A public static final int MIN_LAYOUT_CHARCODE = 0x0300;
0N/A
0N/A /**
0N/A * Referenced by code in the JDK which wants to test for the
0N/A * maximum char code for which layout may be required.
0N/A * Note this does not account for supplementary characters
0N/A * where the caller interprets 'layout' to mean any case where
0N/A * one 'char' (ie the java type char) does not map to one glyph
0N/A */
0N/A public static final int MAX_LAYOUT_CHARCODE = 0x206F;
0N/A
0N/A /* If the character code falls into any of a number of unicode ranges
0N/A * where we know that simple left->right layout mapping chars to glyphs
0N/A * 1:1 and accumulating advances is going to produce incorrect results,
0N/A * we want to know this so the caller can use a more intelligent layout
0N/A * approach. A caller who cares about optimum performance may want to
0N/A * check the first case and skip the method call if its in that range.
0N/A * Although there's a lot of tests in here, knowing you can skip
0N/A * CTL saves a great deal more. The rest of the checks are ordered
0N/A * so that rather than checking explicitly if (>= start & <= end)
0N/A * which would mean all ranges would need to be checked so be sure
0N/A * CTL is not needed, the method returns as soon as it recognises
0N/A * the code point is outside of a CTL ranges.
0N/A * NOTE: Since this method accepts an 'int' it is asssumed to properly
0N/A * represent a CHARACTER. ie it assumes the caller has already
0N/A * converted surrogate pairs into supplementary characters, and so
0N/A * can handle this case and doesn't need to be told such a case is
0N/A * 'complex'.
0N/A */
0N/A static boolean isComplexCharCode(int code) {
0N/A
0N/A if (code < MIN_LAYOUT_CHARCODE || code > MAX_LAYOUT_CHARCODE) {
0N/A return false;
0N/A }
0N/A else if (code <= 0x036f) {
0N/A // Trigger layout for combining diacriticals 0x0300->0x036f
0N/A return true;
0N/A }
0N/A else if (code < 0x0590) {
0N/A // No automatic layout for Greek, Cyrillic, Armenian.
0N/A return false;
0N/A }
0N/A else if (code <= 0x06ff) {
0N/A // Hebrew 0590 - 05ff
0N/A // Arabic 0600 - 06ff
0N/A return true;
0N/A }
0N/A else if (code < 0x0900) {
0N/A return false; // Syriac and Thaana
0N/A }
0N/A else if (code <= 0x0e7f) {
0N/A // if Indic, assume shaping for conjuncts, reordering:
0N/A // 0900 - 097F Devanagari
0N/A // 0980 - 09FF Bengali
0N/A // 0A00 - 0A7F Gurmukhi
0N/A // 0A80 - 0AFF Gujarati
0N/A // 0B00 - 0B7F Oriya
0N/A // 0B80 - 0BFF Tamil
0N/A // 0C00 - 0C7F Telugu
0N/A // 0C80 - 0CFF Kannada
0N/A // 0D00 - 0D7F Malayalam
0N/A // 0D80 - 0DFF Sinhala
0N/A // 0E00 - 0E7F if Thai, assume shaping for vowel, tone marks
0N/A return true;
0N/A }
0N/A else if (code < 0x1780) {
0N/A return false;
0N/A }
0N/A else if (code <= 0x17ff) { // 1780 - 17FF Khmer
0N/A return true;
0N/A }
0N/A else if (code < 0x200c) {
0N/A return false;
0N/A }
0N/A else if (code <= 0x200d) { // zwj or zwnj
0N/A return true;
0N/A }
0N/A else if (code >= 0x202a && code <= 0x202e) { // directional control
0N/A return true;
0N/A }
0N/A else if (code >= 0x206a && code <= 0x206f) { // directional control
0N/A return true;
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A /* This is almost the same as the method above, except it takes a
0N/A * char which means it may include undecoded surrogate pairs.
0N/A * The distinction is made so that code which needs to identify all
0N/A * cases in which we do not have a simple mapping from
0N/A * char->unicode character->glyph can be be identified.
0N/A * For example measurement cannot simply sum advances of 'chars',
0N/A * the caret in editable text cannot advance one 'char' at a time, etc.
0N/A * These callers really are asking for more than whether 'layout'
0N/A * needs to be run, they need to know if they can assume 1->1
0N/A * char->glyph mapping.
0N/A */
0N/A static boolean isNonSimpleChar(char ch) {
0N/A return
0N/A isComplexCharCode(ch) ||
0N/A (ch >= CharToGlyphMapper.HI_SURROGATE_START &&
0N/A ch <= CharToGlyphMapper.LO_SURROGATE_END);
0N/A }
0N/A
0N/A /**
0N/A * If there is anything in the text which triggers a case
0N/A * where char->glyph does not map 1:1 in straightforward
0N/A * left->right ordering, then this method returns true.
0N/A * Scripts which might require it but are not treated as such
0N/A * due to JDK implementations will not return true.
0N/A * ie a 'true' return is an indication of the treatment by
0N/A * the implementation.
0N/A * Whether supplementary characters should be considered is dependent
0N/A * on the needs of the caller. Since this method accepts the 'char' type
0N/A * then such chars are always represented by a pair. From a rendering
0N/A * perspective these will all (in the cases I know of) still be one
0N/A * unicode character -> one glyph. But if a caller is using this to
0N/A * discover any case where it cannot make naive assumptions about
0N/A * the number of chars, and how to index through them, then it may
0N/A * need the option to have a 'true' return in such a case.
0N/A */
0N/A public static boolean isComplexText(char [] chs, int start, int limit) {
0N/A
0N/A for (int i = start; i < limit; i++) {
0N/A if (chs[i] < MIN_LAYOUT_CHARCODE) {
0N/A continue;
0N/A }
0N/A else if (isNonSimpleChar(chs[i])) {
0N/A return true;
0N/A }
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A /**
0N/A * Used by windows printing to assess if a font is likely to
0N/A * be layout compatible with JDK
0N/A * TrueType fonts should be, but if they have no GPOS table,
0N/A * but do have a GSUB table, then they are probably older
0N/A * fonts GDI handles differently.
0N/A */
0N/A public static boolean textLayoutIsCompatible(Font font) {
0N/A
0N/A Font2D font2D = FontManager.getFont2D(font);
0N/A if (font2D instanceof TrueTypeFont) {
0N/A TrueTypeFont ttf = (TrueTypeFont)font2D;
0N/A return
0N/A ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null ||
0N/A ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null;
0N/A } else {
0N/A return false;
0N/A }
0N/A }
0N/A
0N/A private static FontScaler nullScaler = null;
0N/A private static Constructor<FontScaler> scalerConstructor = null;
0N/A
0N/A //Find preferred font scaler
0N/A //
0N/A //NB: we can allow property based preferences
0N/A // (theoretically logic can be font type specific)
0N/A static {
0N/A Class scalerClass = null;
0N/A Class arglst[] = new Class[] {Font2D.class, int.class,
0N/A boolean.class, int.class};
0N/A
0N/A try {
0N/A if (SunGraphicsEnvironment.isOpenJDK()) {
0N/A scalerClass = Class.forName("sun.font.FreetypeFontScaler");
0N/A } else {
0N/A scalerClass = Class.forName("sun.font.T2KFontScaler");
0N/A }
0N/A } catch (ClassNotFoundException e) {
0N/A scalerClass = NullFontScaler.class;
0N/A }
0N/A
0N/A //NB: rewrite using factory? constructor is ugly way
0N/A try {
0N/A scalerConstructor = scalerClass.getConstructor(arglst);
0N/A } catch (NoSuchMethodException e) {
0N/A //should not happen
0N/A }
0N/A }
0N/A
0N/A /* At the moment it is harmless to create 2 null scalers
0N/A so, technically, syncronized keyword is not needed.
0N/A
0N/A But it is safer to keep it to avoid subtle problems if we will be
0N/A adding checks like whether scaler is null scaler. */
0N/A public synchronized static FontScaler getNullScaler() {
0N/A if (nullScaler == null) {
0N/A nullScaler = new NullFontScaler();
0N/A }
0N/A return nullScaler;
0N/A }
0N/A
0N/A /* This is the only place to instantiate new FontScaler.
0N/A * Therefore this is very convinient place to register
0N/A * scaler with Disposer as well as trigger deregistring bad font
0N/A * in case when scaler reports this.
0N/A */
0N/A
0N/A public static FontScaler getScaler(Font2D font,
0N/A int indexInCollection,
0N/A boolean supportsCJK,
0N/A int filesize) {
0N/A FontScaler scaler = null;
0N/A
0N/A try {
0N/A Object args[] = new Object[] {font, indexInCollection,
0N/A supportsCJK, filesize};
0N/A scaler = scalerConstructor.newInstance(args);
0N/A Disposer.addObjectRecord(font, scaler);
0N/A } catch (Throwable e) {
0N/A scaler = nullScaler;
0N/A
0N/A //if we can not instantiate scaler assume bad font
0N/A //NB: technically it could be also because of internal scaler
0N/A // error but here we are assuming scaler is ok.
0N/A deRegisterBadFont(font);
0N/A }
0N/A return scaler;
0N/A }
0N/A}