| /* |
| * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.font; |
| |
| import java.awt.Font; |
| import java.awt.FontFormatException; |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.StringTokenizer; |
| import java.util.TreeMap; |
| import java.util.Vector; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.swing.plaf.FontUIResource; |
| import sun.awt.AppContext; |
| import sun.awt.FontConfiguration; |
| import sun.awt.SunToolkit; |
| import sun.awt.util.ThreadGroupUtils; |
| import sun.java2d.FontSupport; |
| import sun.util.logging.PlatformLogger; |
| |
| /** |
| * The base implementation of the {@link FontManager} interface. It implements |
| * the platform independent, shared parts of OpenJDK's FontManager |
| * implementations. The platform specific parts are declared as abstract |
| * methods that have to be implemented by specific implementations. |
| */ |
| public abstract class SunFontManager implements FontSupport, FontManagerForSGE { |
| |
| private static class TTFilter implements FilenameFilter { |
| public boolean accept(File dir,String name) { |
| /* all conveniently have the same suffix length */ |
| int offset = name.length()-4; |
| if (offset <= 0) { /* must be at least A.ttf */ |
| return false; |
| } else { |
| return(name.startsWith(".ttf", offset) || |
| name.startsWith(".TTF", offset) || |
| name.startsWith(".ttc", offset) || |
| name.startsWith(".TTC", offset) || |
| name.startsWith(".otf", offset) || |
| name.startsWith(".OTF", offset)); |
| } |
| } |
| } |
| |
| private static class T1Filter implements FilenameFilter { |
| public boolean accept(File dir,String name) { |
| if (noType1Font) { |
| return false; |
| } |
| /* all conveniently have the same suffix length */ |
| int offset = name.length()-4; |
| if (offset <= 0) { /* must be at least A.pfa */ |
| return false; |
| } else { |
| return(name.startsWith(".pfa", offset) || |
| name.startsWith(".pfb", offset) || |
| name.startsWith(".PFA", offset) || |
| name.startsWith(".PFB", offset)); |
| } |
| } |
| } |
| |
| private static class TTorT1Filter implements FilenameFilter { |
| public boolean accept(File dir, String name) { |
| |
| /* all conveniently have the same suffix length */ |
| int offset = name.length()-4; |
| if (offset <= 0) { /* must be at least A.ttf or A.pfa */ |
| return false; |
| } else { |
| boolean isTT = |
| name.startsWith(".ttf", offset) || |
| name.startsWith(".TTF", offset) || |
| name.startsWith(".ttc", offset) || |
| name.startsWith(".TTC", offset) || |
| name.startsWith(".otf", offset) || |
| name.startsWith(".OTF", offset); |
| if (isTT) { |
| return true; |
| } else if (noType1Font) { |
| return false; |
| } else { |
| return(name.startsWith(".pfa", offset) || |
| name.startsWith(".pfb", offset) || |
| name.startsWith(".PFA", offset) || |
| name.startsWith(".PFB", offset)); |
| } |
| } |
| } |
| } |
| |
| public static final int FONTFORMAT_NONE = -1; |
| public static final int FONTFORMAT_TRUETYPE = 0; |
| public static final int FONTFORMAT_TYPE1 = 1; |
| public static final int FONTFORMAT_T2K = 2; |
| public static final int FONTFORMAT_TTC = 3; |
| public static final int FONTFORMAT_COMPOSITE = 4; |
| public static final int FONTFORMAT_NATIVE = 5; |
| |
| /* Pool of 20 font file channels chosen because some UTF-8 locale |
| * composite fonts can use up to 16 platform fonts (including the |
| * Lucida fall back). This should prevent channel thrashing when |
| * dealing with one of these fonts. |
| * The pool array stores the fonts, rather than directly referencing |
| * the channels, as the font needs to do the open/close work. |
| */ |
| // MACOSX begin -- need to access these in subclass |
| protected static final int CHANNELPOOLSIZE = 20; |
| protected FileFont fontFileCache[] = new FileFont[CHANNELPOOLSIZE]; |
| // MACOSX end |
| private int lastPoolIndex = 0; |
| |
| /* Need to implement a simple linked list scheme for fast |
| * traversal and lookup. |
| * Also want to "fast path" dialog so there's minimal overhead. |
| */ |
| /* There are at exactly 20 composite fonts: 5 faces (but some are not |
| * usually different), in 4 styles. The array may be auto-expanded |
| * later if more are needed, eg for user-defined composites or locale |
| * variants. |
| */ |
| private int maxCompFont = 0; |
| private CompositeFont [] compFonts = new CompositeFont[20]; |
| private ConcurrentHashMap<String, CompositeFont> |
| compositeFonts = new ConcurrentHashMap<String, CompositeFont>(); |
| private ConcurrentHashMap<String, PhysicalFont> |
| physicalFonts = new ConcurrentHashMap<String, PhysicalFont>(); |
| private ConcurrentHashMap<String, PhysicalFont> |
| registeredFonts = new ConcurrentHashMap<String, PhysicalFont>(); |
| |
| /* given a full name find the Font. Remind: there's duplication |
| * here in that this contains the content of compositeFonts + |
| * physicalFonts. |
| */ |
| // MACOSX begin -- need to access this in subclass |
| protected ConcurrentHashMap<String, Font2D> |
| fullNameToFont = new ConcurrentHashMap<String, Font2D>(); |
| // MACOSX end |
| |
| /* TrueType fonts have localised names. Support searching all |
| * of these before giving up on a name. |
| */ |
| private HashMap<String, TrueTypeFont> localeFullNamesToFont; |
| |
| private PhysicalFont defaultPhysicalFont; |
| |
| static boolean longAddresses; |
| private boolean loaded1dot0Fonts = false; |
| boolean loadedAllFonts = false; |
| boolean loadedAllFontFiles = false; |
| HashMap<String,String> jreFontMap; |
| HashSet<String> jreLucidaFontFiles; |
| String[] jreOtherFontFiles; |
| boolean noOtherJREFontFiles = false; // initial assumption. |
| |
| public static final String lucidaFontName = "Lucida Sans Regular"; |
| public static String jreLibDirName; |
| public static String jreFontDirName; |
| private static HashSet<String> missingFontFiles = null; |
| private String defaultFontName; |
| private String defaultFontFileName; |
| protected HashSet<String> registeredFontFiles = new HashSet<>(); |
| |
| private ArrayList<String> badFonts; |
| /* fontPath is the location of all fonts on the system, excluding the |
| * JRE's own font directory but including any path specified using the |
| * sun.java2d.fontpath property. Together with that property, it is |
| * initialised by the getPlatformFontPath() method |
| * This call must be followed by a call to registerFontDirs(fontPath) |
| * once any extra debugging path has been appended. |
| */ |
| protected String fontPath; |
| private FontConfiguration fontConfig; |
| /* discoveredAllFonts is set to true when all fonts on the font path are |
| * discovered. This usually also implies opening, validating and |
| * registering, but an implementation may be optimized to avold this. |
| * So see also "loadedAllFontFiles" |
| */ |
| private boolean discoveredAllFonts = false; |
| |
| /* No need to keep consing up new instances - reuse a singleton. |
| * The trade-off is that these objects don't get GC'd. |
| */ |
| private static final FilenameFilter ttFilter = new TTFilter(); |
| private static final FilenameFilter t1Filter = new T1Filter(); |
| |
| private Font[] allFonts; |
| private String[] allFamilies; // cache for default locale only |
| private Locale lastDefaultLocale; |
| |
| public static boolean noType1Font; |
| |
| /* Used to indicate required return type from toArray(..); */ |
| private static String[] STR_ARRAY = new String[0]; |
| |
| /** |
| * Deprecated, unsupported hack - actually invokes a bug! |
| * Left in for a customer, don't remove. |
| */ |
| private boolean usePlatformFontMetrics = false; |
| |
| /** |
| * Returns the global SunFontManager instance. This is similar to |
| * {@link FontManagerFactory#getInstance()} but it returns a |
| * SunFontManager instance instead. This is only used in internal classes |
| * where we can safely assume that a SunFontManager is to be used. |
| * |
| * @return the global SunFontManager instance |
| */ |
| public static SunFontManager getInstance() { |
| FontManager fm = FontManagerFactory.getInstance(); |
| return (SunFontManager) fm; |
| } |
| |
| public FilenameFilter getTrueTypeFilter() { |
| return ttFilter; |
| } |
| |
| public FilenameFilter getType1Filter() { |
| return t1Filter; |
| } |
| |
| @Override |
| public boolean usingPerAppContextComposites() { |
| return _usingPerAppContextComposites; |
| } |
| |
| private void initJREFontMap() { |
| |
| /* Key is familyname+style value as an int. |
| * Value is filename containing the font. |
| * If no mapping exists, it means there is no font file for the style |
| * If the mapping exists but the file doesn't exist in the deferred |
| * list then it means its not installed. |
| * This looks like a lot of code and strings but if it saves even |
| * a single file being opened at JRE start-up there's a big payoff. |
| * Lucida Sans is probably the only important case as the others |
| * are rarely used. Consider removing the other mappings if there's |
| * no evidence they are useful in practice. |
| */ |
| jreFontMap = new HashMap<String,String>(); |
| jreLucidaFontFiles = new HashSet<String>(); |
| if (isOpenJDK()) { |
| return; |
| } |
| /* Lucida Sans Family */ |
| jreFontMap.put("lucida sans0", "LucidaSansRegular.ttf"); |
| jreFontMap.put("lucida sans1", "LucidaSansDemiBold.ttf"); |
| /* Lucida Sans full names (map Bold and DemiBold to same file) */ |
| jreFontMap.put("lucida sans regular0", "LucidaSansRegular.ttf"); |
| jreFontMap.put("lucida sans regular1", "LucidaSansDemiBold.ttf"); |
| jreFontMap.put("lucida sans bold1", "LucidaSansDemiBold.ttf"); |
| jreFontMap.put("lucida sans demibold1", "LucidaSansDemiBold.ttf"); |
| |
| /* Lucida Sans Typewriter Family */ |
| jreFontMap.put("lucida sans typewriter0", |
| "LucidaTypewriterRegular.ttf"); |
| jreFontMap.put("lucida sans typewriter1", "LucidaTypewriterBold.ttf"); |
| /* Typewriter full names (map Bold and DemiBold to same file) */ |
| jreFontMap.put("lucida sans typewriter regular0", |
| "LucidaTypewriter.ttf"); |
| jreFontMap.put("lucida sans typewriter regular1", |
| "LucidaTypewriterBold.ttf"); |
| jreFontMap.put("lucida sans typewriter bold1", |
| "LucidaTypewriterBold.ttf"); |
| jreFontMap.put("lucida sans typewriter demibold1", |
| "LucidaTypewriterBold.ttf"); |
| |
| /* Lucida Bright Family */ |
| jreFontMap.put("lucida bright0", "LucidaBrightRegular.ttf"); |
| jreFontMap.put("lucida bright1", "LucidaBrightDemiBold.ttf"); |
| jreFontMap.put("lucida bright2", "LucidaBrightItalic.ttf"); |
| jreFontMap.put("lucida bright3", "LucidaBrightDemiItalic.ttf"); |
| /* Lucida Bright full names (map Bold and DemiBold to same file) */ |
| jreFontMap.put("lucida bright regular0", "LucidaBrightRegular.ttf"); |
| jreFontMap.put("lucida bright regular1", "LucidaBrightDemiBold.ttf"); |
| jreFontMap.put("lucida bright regular2", "LucidaBrightItalic.ttf"); |
| jreFontMap.put("lucida bright regular3", "LucidaBrightDemiItalic.ttf"); |
| jreFontMap.put("lucida bright bold1", "LucidaBrightDemiBold.ttf"); |
| jreFontMap.put("lucida bright bold3", "LucidaBrightDemiItalic.ttf"); |
| jreFontMap.put("lucida bright demibold1", "LucidaBrightDemiBold.ttf"); |
| jreFontMap.put("lucida bright demibold3","LucidaBrightDemiItalic.ttf"); |
| jreFontMap.put("lucida bright italic2", "LucidaBrightItalic.ttf"); |
| jreFontMap.put("lucida bright italic3", "LucidaBrightDemiItalic.ttf"); |
| jreFontMap.put("lucida bright bold italic3", |
| "LucidaBrightDemiItalic.ttf"); |
| jreFontMap.put("lucida bright demibold italic3", |
| "LucidaBrightDemiItalic.ttf"); |
| for (String ffile : jreFontMap.values()) { |
| jreLucidaFontFiles.add(ffile); |
| } |
| } |
| |
| static { |
| |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| |
| public Object run() { |
| FontManagerNativeLibrary.load(); |
| |
| // JNI throws an exception if a class/method/field is not found, |
| // so there's no need to do anything explicit here. |
| initIDs(); |
| |
| switch (StrikeCache.nativeAddressSize) { |
| case 8: longAddresses = true; break; |
| case 4: longAddresses = false; break; |
| default: throw new RuntimeException("Unexpected address size"); |
| } |
| |
| noType1Font = |
| "true".equals(System.getProperty("sun.java2d.noType1Font")); |
| jreLibDirName = |
| System.getProperty("java.home","") + File.separator + "lib"; |
| jreFontDirName = jreLibDirName + File.separator + "fonts"; |
| File lucidaFile = |
| new File(jreFontDirName + File.separator + FontUtilities.LUCIDA_FILE_NAME); |
| |
| return null; |
| } |
| }); |
| } |
| |
| /** |
| * If the module image layout changes the location of JDK fonts, |
| * this will be updated to reflect that. |
| */ |
| public static final String getJDKFontDir() { |
| return jreFontDirName; |
| } |
| |
| public TrueTypeFont getEUDCFont() { |
| // Overridden in Windows. |
| return null; |
| } |
| |
| /* Initialise ptrs used by JNI methods */ |
| private static native void initIDs(); |
| |
| @SuppressWarnings("unchecked") |
| protected SunFontManager() { |
| |
| initJREFontMap(); |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| public Object run() { |
| File badFontFile = |
| new File(jreFontDirName + File.separator + |
| "badfonts.txt"); |
| if (badFontFile.exists()) { |
| FileInputStream fis = null; |
| try { |
| badFonts = new ArrayList<>(); |
| fis = new FileInputStream(badFontFile); |
| InputStreamReader isr = new InputStreamReader(fis); |
| BufferedReader br = new BufferedReader(isr); |
| while (true) { |
| String name = br.readLine(); |
| if (name == null) { |
| break; |
| } else { |
| if (FontUtilities.debugFonts()) { |
| FontUtilities.getLogger().warning("read bad font: " + |
| name); |
| } |
| badFonts.add(name); |
| } |
| } |
| } catch (IOException e) { |
| try { |
| if (fis != null) { |
| fis.close(); |
| } |
| } catch (IOException ioe) { |
| } |
| } |
| } |
| |
| /* Here we get the fonts in jre/lib/fonts and register |
| * them so they are always available and preferred over |
| * other fonts. This needs to be registered before the |
| * composite fonts as otherwise some native font that |
| * corresponds may be found as we don't have a way to |
| * handle two fonts of the same name, so the JRE one |
| * must be the first one registered. Pass "true" to |
| * registerFonts method as on-screen these JRE fonts |
| * always go through the T2K rasteriser. |
| */ |
| if (FontUtilities.isLinux) { |
| /* Linux font configuration uses these fonts */ |
| registerFontDir(jreFontDirName); |
| } |
| registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK, |
| true, false); |
| |
| /* Create the font configuration and get any font path |
| * that might be specified. |
| */ |
| fontConfig = createFontConfiguration(); |
| if (isOpenJDK()) { |
| String[] fontInfo = getDefaultPlatformFont(); |
| defaultFontName = fontInfo[0]; |
| defaultFontFileName = fontInfo[1]; |
| } |
| |
| String extraFontPath = fontConfig.getExtraFontPath(); |
| |
| /* In prior releases the debugging font path replaced |
| * all normally located font directories except for the |
| * JRE fonts dir. This directory is still always located |
| * and placed at the head of the path but as an |
| * augmentation to the previous behaviour the |
| * changes below allow you to additionally append to |
| * the font path by starting with append: or prepend by |
| * starting with a prepend: sign. Eg: to append |
| * -Dsun.java2d.fontpath=append:/usr/local/myfonts |
| * and to prepend |
| * -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp |
| * |
| * If there is an appendedfontpath it in the font |
| * configuration it is used instead of searching the |
| * system for dirs. |
| * The behaviour of append and prepend is then similar |
| * to the normal case. ie it goes after what |
| * you prepend and * before what you append. If the |
| * sun.java2d.fontpath property is used, but it |
| * neither the append or prepend syntaxes is used then |
| * as except for the JRE dir the path is replaced and it |
| * is up to you to make sure that all the right |
| * directories are located. This is platform and |
| * locale-specific so its almost impossible to get |
| * right, so it should be used with caution. |
| */ |
| boolean prependToPath = false; |
| boolean appendToPath = false; |
| String dbgFontPath = |
| System.getProperty("sun.java2d.fontpath"); |
| |
| if (dbgFontPath != null) { |
| if (dbgFontPath.startsWith("prepend:")) { |
| prependToPath = true; |
| dbgFontPath = |
| dbgFontPath.substring("prepend:".length()); |
| } else if (dbgFontPath.startsWith("append:")) { |
| appendToPath = true; |
| dbgFontPath = |
| dbgFontPath.substring("append:".length()); |
| } |
| } |
| |
| if (FontUtilities.debugFonts()) { |
| PlatformLogger logger = FontUtilities.getLogger(); |
| logger.info("JRE font directory: " + jreFontDirName); |
| logger.info("Extra font path: " + extraFontPath); |
| logger.info("Debug font path: " + dbgFontPath); |
| } |
| |
| if (dbgFontPath != null) { |
| /* In debugging mode we register all the paths |
| * Caution: this is a very expensive call on Solaris:- |
| */ |
| fontPath = getPlatformFontPath(noType1Font); |
| |
| if (extraFontPath != null) { |
| fontPath = |
| extraFontPath + File.pathSeparator + fontPath; |
| } |
| if (appendToPath) { |
| fontPath = |
| fontPath + File.pathSeparator + dbgFontPath; |
| } else if (prependToPath) { |
| fontPath = |
| dbgFontPath + File.pathSeparator + fontPath; |
| } else { |
| fontPath = dbgFontPath; |
| } |
| registerFontDirs(fontPath); |
| } else if (extraFontPath != null) { |
| /* If the font configuration contains an |
| * "appendedfontpath" entry, it is interpreted as a |
| * set of locations that should always be registered. |
| * It may be additional to locations normally found |
| * for that place, or it may be locations that need |
| * to have all their paths registered to locate all |
| * the needed platform names. |
| * This is typically when the same .TTF file is |
| * referenced from multiple font.dir files and all |
| * of these must be read to find all the native |
| * (XLFD) names for the font, so that X11 font APIs |
| * can be used for as many code points as possible. |
| */ |
| registerFontDirs(extraFontPath); |
| } |
| |
| /* On Solaris, we need to register the Japanese TrueType |
| * directory so that we can find the corresponding |
| * bitmap fonts. This could be done by listing the |
| * directory in the font configuration file, but we |
| * don't want to confuse users with this quirk. There |
| * are no bitmap fonts for other writing systems that |
| * correspond to TrueType fonts and have matching XLFDs. |
| * We need to register the bitmap fonts only in |
| * environments where they're on the X font path, i.e., |
| * in the Japanese locale. Note that if the X Toolkit |
| * is in use the font path isn't set up by JDK, but |
| * users of a JA locale should have it |
| * set up already by their login environment. |
| */ |
| if (FontUtilities.isSolaris && Locale.JAPAN.equals(Locale.getDefault())) { |
| registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT"); |
| } |
| |
| initCompositeFonts(fontConfig, null); |
| |
| return null; |
| } |
| }); |
| |
| boolean platformFont = AccessController.doPrivileged( |
| new PrivilegedAction<Boolean>() { |
| public Boolean run() { |
| String prop = |
| System.getProperty("java2d.font.usePlatformFont"); |
| String env = System.getenv("JAVA2D_USEPLATFORMFONT"); |
| return "true".equals(prop) || env != null; |
| } |
| }); |
| |
| if (platformFont) { |
| usePlatformFontMetrics = true; |
| System.out.println("Enabling platform font metrics for win32. This is an unsupported option."); |
| System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases."); |
| System.out.println("It is appropriate only for use by applications which do not use any Java 2"); |
| System.out.println("functionality. This property will be removed in a later release."); |
| } |
| } |
| |
| public Font2DHandle getNewComposite(String family, int style, |
| Font2DHandle handle) { |
| |
| if (!(handle.font2D instanceof CompositeFont)) { |
| return handle; |
| } |
| |
| CompositeFont oldComp = (CompositeFont)handle.font2D; |
| PhysicalFont oldFont = oldComp.getSlotFont(0); |
| |
| if (family == null) { |
| family = oldFont.getFamilyName(null); |
| } |
| if (style == -1) { |
| style = oldComp.getStyle(); |
| } |
| |
| Font2D newFont = findFont2D(family, style, NO_FALLBACK); |
| if (!(newFont instanceof PhysicalFont)) { |
| newFont = oldFont; |
| } |
| PhysicalFont physicalFont = (PhysicalFont)newFont; |
| CompositeFont dialog2D = |
| (CompositeFont)findFont2D("dialog", style, NO_FALLBACK); |
| if (dialog2D == null) { /* shouldn't happen */ |
| return handle; |
| } |
| CompositeFont compFont = new CompositeFont(physicalFont, dialog2D); |
| Font2DHandle newHandle = new Font2DHandle(compFont); |
| return newHandle; |
| } |
| |
| protected void registerCompositeFont(String compositeName, |
| String[] componentFileNames, |
| String[] componentNames, |
| int numMetricsSlots, |
| int[] exclusionRanges, |
| int[] exclusionMaxIndex, |
| boolean defer) { |
| |
| CompositeFont cf = new CompositeFont(compositeName, |
| componentFileNames, |
| componentNames, |
| numMetricsSlots, |
| exclusionRanges, |
| exclusionMaxIndex, defer, this); |
| addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK); |
| synchronized (compFonts) { |
| compFonts[maxCompFont++] = cf; |
| } |
| } |
| |
| /* This variant is used only when the application specifies |
| * a variant of composite fonts which prefers locale specific or |
| * proportional fonts. |
| */ |
| protected static void registerCompositeFont(String compositeName, |
| String[] componentFileNames, |
| String[] componentNames, |
| int numMetricsSlots, |
| int[] exclusionRanges, |
| int[] exclusionMaxIndex, |
| boolean defer, |
| ConcurrentHashMap<String, Font2D> |
| altNameCache) { |
| |
| CompositeFont cf = new CompositeFont(compositeName, |
| componentFileNames, |
| componentNames, |
| numMetricsSlots, |
| exclusionRanges, |
| exclusionMaxIndex, defer, |
| SunFontManager.getInstance()); |
| |
| /* if the cache has an existing composite for this case, make |
| * its handle point to this new font. |
| * This ensures that when the altNameCache that is passed in |
| * is the global mapNameCache - ie we are running as an application - |
| * that any statically created java.awt.Font instances which already |
| * have a Font2D instance will have that re-directed to the new Font |
| * on subsequent uses. This is particularly important for "the" |
| * default font instance, or similar cases where a UI toolkit (eg |
| * Swing) has cached a java.awt.Font. Note that if Swing is using |
| * a custom composite APIs which update the standard composites have |
| * no effect - this is typically the case only when using the Windows |
| * L&F where these APIs would conflict with that L&F anyway. |
| */ |
| Font2D oldFont =altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH)); |
| if (oldFont instanceof CompositeFont) { |
| oldFont.handle.font2D = cf; |
| } |
| altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf); |
| } |
| |
| private void addCompositeToFontList(CompositeFont f, int rank) { |
| |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger().info("Add to Family "+ f.familyName + |
| ", Font " + f.fullName + " rank="+rank); |
| } |
| f.setRank(rank); |
| compositeFonts.put(f.fullName, f); |
| fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f); |
| |
| FontFamily family = FontFamily.getFamily(f.familyName); |
| if (family == null) { |
| family = new FontFamily(f.familyName, true, rank); |
| } |
| family.setFont(f, f.style); |
| } |
| |
| /* |
| * Systems may have fonts with the same name. |
| * We want to register only one of such fonts (at least until |
| * such time as there might be APIs which can accommodate > 1). |
| * Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts, |
| * 4) Type1 fonts, 5) native fonts. |
| * |
| * If the new font has the same name as the old font, the higher |
| * ranked font gets added, replacing the lower ranked one. |
| * If the fonts are of equal rank, then make a special case of |
| * font configuration rank fonts, which are on closer inspection, |
| * OT/TT fonts such that the larger font is registered. This is |
| * a heuristic since a font may be "larger" in the sense of more |
| * code points, or be a larger "file" because it has more bitmaps. |
| * So it is possible that using filesize may lead to less glyphs, and |
| * using glyphs may lead to lower quality display. Probably number |
| * of glyphs is the ideal, but filesize is information we already |
| * have and is good enough for the known cases. |
| * Also don't want to register fonts that match JRE font families |
| * but are coming from a source other than the JRE. |
| * This will ensure that we will algorithmically style the JRE |
| * plain font and get the same set of glyphs for all styles. |
| * |
| * Note that this method returns a value |
| * if it returns the same object as its argument that means this |
| * font was newly registered. |
| * If it returns a different object it means this font already exists, |
| * and you should use that one. |
| * If it returns null means this font was not registered and none |
| * in that name is registered. The caller must find a substitute |
| */ |
| // MACOSX begin -- need to access this in subclass |
| protected PhysicalFont addToFontList(PhysicalFont f, int rank) { |
| // MACOSX end |
| |
| String fontName = f.fullName; |
| String familyName = f.familyName; |
| if (fontName == null || "".equals(fontName)) { |
| return null; |
| } |
| if (compositeFonts.containsKey(fontName)) { |
| /* Don't register any font that has the same name as a composite */ |
| return null; |
| } |
| f.setRank(rank); |
| if (!physicalFonts.containsKey(fontName)) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger().info("Add to Family "+familyName + |
| ", Font " + fontName + " rank="+rank); |
| } |
| physicalFonts.put(fontName, f); |
| FontFamily family = FontFamily.getFamily(familyName); |
| if (family == null) { |
| family = new FontFamily(familyName, false, rank); |
| family.setFont(f, f.style); |
| } else { |
| family.setFont(f, f.style); |
| } |
| fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f); |
| return f; |
| } else { |
| PhysicalFont newFont = f; |
| PhysicalFont oldFont = physicalFonts.get(fontName); |
| if (oldFont == null) { |
| return null; |
| } |
| /* If the new font is of an equal or higher rank, it is a |
| * candidate to replace the current one, subject to further tests. |
| */ |
| if (oldFont.getRank() >= rank) { |
| |
| /* All fonts initialise their mapper when first |
| * used. If the mapper is non-null then this font |
| * has been accessed at least once. In that case |
| * do not replace it. This may be overly stringent, |
| * but its probably better not to replace a font that |
| * someone is already using without a compelling reason. |
| * Additionally the primary case where it is known |
| * this behaviour is important is in certain composite |
| * fonts, and since all the components of a given |
| * composite are usually initialised together this |
| * is unlikely. For this to be a problem, there would |
| * have to be a case where two different composites used |
| * different versions of the same-named font, and they |
| * were initialised and used at separate times. |
| * In that case we continue on and allow the new font to |
| * be installed, but replaceFont will continue to allow |
| * the original font to be used in Composite fonts. |
| */ |
| if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) { |
| return oldFont; |
| } |
| |
| /* Normally we require a higher rank to replace a font, |
| * but as a special case, if the two fonts are the same rank, |
| * and are instances of TrueTypeFont we want the |
| * more complete (larger) one. |
| */ |
| if (oldFont.getRank() == rank) { |
| if (oldFont instanceof TrueTypeFont && |
| newFont instanceof TrueTypeFont) { |
| TrueTypeFont oldTTFont = (TrueTypeFont)oldFont; |
| TrueTypeFont newTTFont = (TrueTypeFont)newFont; |
| if (oldTTFont.fileSize >= newTTFont.fileSize) { |
| return oldFont; |
| } |
| } else { |
| return oldFont; |
| } |
| } |
| /* Don't replace ever JRE fonts. |
| * This test is in case a font configuration references |
| * a Lucida font, which has been mapped to a Lucida |
| * from the host O/S. The assumption here is that any |
| * such font configuration file is probably incorrect, or |
| * the host O/S version is for the use of AWT. |
| * In other words if we reach here, there's a possible |
| * problem with our choice of font configuration fonts. |
| */ |
| if (oldFont.platName.startsWith(jreFontDirName)) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .warning("Unexpected attempt to replace a JRE " + |
| " font " + fontName + " from " + |
| oldFont.platName + |
| " with " + newFont.platName); |
| } |
| return oldFont; |
| } |
| |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Replace in Family " + familyName + |
| ",Font " + fontName + " new rank="+rank + |
| " from " + oldFont.platName + |
| " with " + newFont.platName); |
| } |
| replaceFont(oldFont, newFont); |
| physicalFonts.put(fontName, newFont); |
| fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), |
| newFont); |
| |
| FontFamily family = FontFamily.getFamily(familyName); |
| if (family == null) { |
| family = new FontFamily(familyName, false, rank); |
| family.setFont(newFont, newFont.style); |
| } else { |
| family.setFont(newFont, newFont.style); |
| } |
| return newFont; |
| } else { |
| return oldFont; |
| } |
| } |
| } |
| |
| public Font2D[] getRegisteredFonts() { |
| PhysicalFont[] physFonts = getPhysicalFonts(); |
| int mcf = maxCompFont; /* for MT-safety */ |
| Font2D[] regFonts = new Font2D[physFonts.length+mcf]; |
| System.arraycopy(compFonts, 0, regFonts, 0, mcf); |
| System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length); |
| return regFonts; |
| } |
| |
| protected PhysicalFont[] getPhysicalFonts() { |
| return physicalFonts.values().toArray(new PhysicalFont[0]); |
| } |
| |
| |
| /* The class FontRegistrationInfo is used when a client says not |
| * to register a font immediately. This mechanism is used to defer |
| * initialisation of all the components of composite fonts at JRE |
| * start-up. The CompositeFont class is "aware" of this and when it |
| * is first used it asks for the registration of its components. |
| * Also in the event that any physical font is requested the |
| * deferred fonts are initialised before triggering a search of the |
| * system. |
| * Two maps are used. One to track the deferred fonts. The |
| * other to track the fonts that have been initialised through this |
| * mechanism. |
| */ |
| |
| private static final class FontRegistrationInfo { |
| |
| String fontFilePath; |
| String[] nativeNames; |
| int fontFormat; |
| boolean javaRasterizer; |
| int fontRank; |
| |
| FontRegistrationInfo(String fontPath, String[] names, int format, |
| boolean useJavaRasterizer, int rank) { |
| this.fontFilePath = fontPath; |
| this.nativeNames = names; |
| this.fontFormat = format; |
| this.javaRasterizer = useJavaRasterizer; |
| this.fontRank = rank; |
| } |
| } |
| |
| private final ConcurrentHashMap<String, FontRegistrationInfo> |
| deferredFontFiles = |
| new ConcurrentHashMap<String, FontRegistrationInfo>(); |
| private final ConcurrentHashMap<String, Font2DHandle> |
| initialisedFonts = new ConcurrentHashMap<String, Font2DHandle>(); |
| |
| /* Remind: possibly enhance initialiseDeferredFonts() to be |
| * optionally given a name and a style and it could stop when it |
| * finds that font - but this would be a problem if two of the |
| * fonts reference the same font face name (cf the Solaris |
| * euro fonts). |
| */ |
| protected synchronized void initialiseDeferredFonts() { |
| for (String fileName : deferredFontFiles.keySet()) { |
| initialiseDeferredFont(fileName); |
| } |
| } |
| |
| protected synchronized void registerDeferredJREFonts(String jreDir) { |
| for (FontRegistrationInfo info : deferredFontFiles.values()) { |
| if (info.fontFilePath != null && |
| info.fontFilePath.startsWith(jreDir)) { |
| initialiseDeferredFont(info.fontFilePath); |
| } |
| } |
| } |
| |
| public boolean isDeferredFont(String fileName) { |
| return deferredFontFiles.containsKey(fileName); |
| } |
| |
| /* We keep a map of the files which contain the Lucida fonts so we |
| * don't need to search for them. |
| * But since we know what fonts these files contain, we can also avoid |
| * opening them to look for a font name we don't recognise - see |
| * findDeferredFont(). |
| * For typical cases where the font isn't a JRE one the overhead is |
| * this method call, HashMap.get() and null reference test, then |
| * a boolean test of noOtherJREFontFiles. |
| */ |
| public |
| /*private*/ PhysicalFont findJREDeferredFont(String name, int style) { |
| |
| PhysicalFont physicalFont; |
| String nameAndStyle = name.toLowerCase(Locale.ENGLISH) + style; |
| String fileName = jreFontMap.get(nameAndStyle); |
| if (fileName != null) { |
| fileName = jreFontDirName + File.separator + fileName; |
| if (deferredFontFiles.get(fileName) != null) { |
| physicalFont = initialiseDeferredFont(fileName); |
| if (physicalFont != null && |
| (physicalFont.getFontName(null).equalsIgnoreCase(name) || |
| physicalFont.getFamilyName(null).equalsIgnoreCase(name)) |
| && physicalFont.style == style) { |
| return physicalFont; |
| } |
| } |
| } |
| |
| /* Iterate over the deferred font files looking for any in the |
| * jre directory that we didn't recognise, open each of these. |
| * In almost all installations this will quickly fall through |
| * because only the Lucidas will be present and jreOtherFontFiles |
| * will be empty. |
| * noOtherJREFontFiles is used so we can skip this block as soon |
| * as its determined that its not needed - almost always after the |
| * very first time through. |
| */ |
| if (noOtherJREFontFiles) { |
| return null; |
| } |
| synchronized (jreLucidaFontFiles) { |
| if (jreOtherFontFiles == null) { |
| HashSet<String> otherFontFiles = new HashSet<String>(); |
| for (String deferredFile : deferredFontFiles.keySet()) { |
| File file = new File(deferredFile); |
| String dir = file.getParent(); |
| String fname = file.getName(); |
| /* skip names which aren't absolute, aren't in the JRE |
| * directory, or are known Lucida fonts. |
| */ |
| if (dir == null || |
| !dir.equals(jreFontDirName) || |
| jreLucidaFontFiles.contains(fname)) { |
| continue; |
| } |
| otherFontFiles.add(deferredFile); |
| } |
| jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY); |
| if (jreOtherFontFiles.length == 0) { |
| noOtherJREFontFiles = true; |
| } |
| } |
| |
| for (int i=0; i<jreOtherFontFiles.length;i++) { |
| fileName = jreOtherFontFiles[i]; |
| if (fileName == null) { |
| continue; |
| } |
| jreOtherFontFiles[i] = null; |
| physicalFont = initialiseDeferredFont(fileName); |
| if (physicalFont != null && |
| (physicalFont.getFontName(null).equalsIgnoreCase(name) || |
| physicalFont.getFamilyName(null).equalsIgnoreCase(name)) |
| && physicalFont.style == style) { |
| return physicalFont; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /* This skips JRE installed fonts. */ |
| private PhysicalFont findOtherDeferredFont(String name, int style) { |
| for (String fileName : deferredFontFiles.keySet()) { |
| File file = new File(fileName); |
| String dir = file.getParent(); |
| String fname = file.getName(); |
| if (dir != null && |
| dir.equals(jreFontDirName) && |
| jreLucidaFontFiles.contains(fname)) { |
| continue; |
| } |
| PhysicalFont physicalFont = initialiseDeferredFont(fileName); |
| if (physicalFont != null && |
| (physicalFont.getFontName(null).equalsIgnoreCase(name) || |
| physicalFont.getFamilyName(null).equalsIgnoreCase(name)) && |
| physicalFont.style == style) { |
| return physicalFont; |
| } |
| } |
| return null; |
| } |
| |
| private PhysicalFont findDeferredFont(String name, int style) { |
| |
| PhysicalFont physicalFont = findJREDeferredFont(name, style); |
| if (physicalFont != null) { |
| return physicalFont; |
| } else { |
| return findOtherDeferredFont(name, style); |
| } |
| } |
| |
| public void registerDeferredFont(String fileNameKey, |
| String fullPathName, |
| String[] nativeNames, |
| int fontFormat, |
| boolean useJavaRasterizer, |
| int fontRank) { |
| FontRegistrationInfo regInfo = |
| new FontRegistrationInfo(fullPathName, nativeNames, fontFormat, |
| useJavaRasterizer, fontRank); |
| deferredFontFiles.put(fileNameKey, regInfo); |
| } |
| |
| |
| public synchronized |
| PhysicalFont initialiseDeferredFont(String fileNameKey) { |
| |
| if (fileNameKey == null) { |
| return null; |
| } |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Opening deferred font file " + fileNameKey); |
| } |
| |
| PhysicalFont physicalFont; |
| FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey); |
| if (regInfo != null) { |
| deferredFontFiles.remove(fileNameKey); |
| physicalFont = registerFontFile(regInfo.fontFilePath, |
| regInfo.nativeNames, |
| regInfo.fontFormat, |
| regInfo.javaRasterizer, |
| regInfo.fontRank); |
| |
| |
| if (physicalFont != null) { |
| /* Store the handle, so that if a font is bad, we |
| * retrieve the substituted font. |
| */ |
| initialisedFonts.put(fileNameKey, physicalFont.handle); |
| } else { |
| initialisedFonts.put(fileNameKey, |
| getDefaultPhysicalFont().handle); |
| } |
| } else { |
| Font2DHandle handle = initialisedFonts.get(fileNameKey); |
| if (handle == null) { |
| /* Probably shouldn't happen, but just in case */ |
| physicalFont = getDefaultPhysicalFont(); |
| } else { |
| physicalFont = (PhysicalFont)(handle.font2D); |
| } |
| } |
| return physicalFont; |
| } |
| |
| public boolean isRegisteredFontFile(String name) { |
| return registeredFonts.containsKey(name); |
| } |
| |
| public PhysicalFont getRegisteredFontFile(String name) { |
| return registeredFonts.get(name); |
| } |
| |
| /* Note that the return value from this method is not always |
| * derived from this file, and may be null. See addToFontList for |
| * some explanation of this. |
| */ |
| public PhysicalFont registerFontFile(String fileName, |
| String[] nativeNames, |
| int fontFormat, |
| boolean useJavaRasterizer, |
| int fontRank) { |
| |
| PhysicalFont regFont = registeredFonts.get(fileName); |
| if (regFont != null) { |
| return regFont; |
| } |
| |
| PhysicalFont physicalFont = null; |
| try { |
| String name; |
| |
| switch (fontFormat) { |
| |
| case FONTFORMAT_TRUETYPE: |
| int fn = 0; |
| TrueTypeFont ttf; |
| do { |
| ttf = new TrueTypeFont(fileName, nativeNames, fn++, |
| useJavaRasterizer); |
| PhysicalFont pf = addToFontList(ttf, fontRank); |
| if (physicalFont == null) { |
| physicalFont = pf; |
| } |
| } |
| while (fn < ttf.getFontCount()); |
| break; |
| |
| case FONTFORMAT_TYPE1: |
| Type1Font t1f = new Type1Font(fileName, nativeNames); |
| physicalFont = addToFontList(t1f, fontRank); |
| break; |
| |
| case FONTFORMAT_NATIVE: |
| NativeFont nf = new NativeFont(fileName, false); |
| physicalFont = addToFontList(nf, fontRank); |
| break; |
| default: |
| |
| } |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Registered file " + fileName + " as font " + |
| physicalFont + " rank=" + fontRank); |
| } |
| } catch (FontFormatException ffe) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger().warning("Unusable font: " + |
| fileName + " " + ffe.toString()); |
| } |
| } |
| if (physicalFont != null && |
| fontFormat != FONTFORMAT_NATIVE) { |
| registeredFonts.put(fileName, physicalFont); |
| } |
| return physicalFont; |
| } |
| |
| public void registerFonts(String[] fileNames, |
| String[][] nativeNames, |
| int fontCount, |
| int fontFormat, |
| boolean useJavaRasterizer, |
| int fontRank, boolean defer) { |
| |
| for (int i=0; i < fontCount; i++) { |
| if (defer) { |
| registerDeferredFont(fileNames[i],fileNames[i], nativeNames[i], |
| fontFormat, useJavaRasterizer, fontRank); |
| } else { |
| registerFontFile(fileNames[i], nativeNames[i], |
| fontFormat, useJavaRasterizer, fontRank); |
| } |
| } |
| } |
| |
| /* |
| * This is the Physical font used when some other font on the system |
| * can't be located. There has to be at least one font or the font |
| * system is not useful and the graphics environment cannot sustain |
| * the Java platform. |
| */ |
| public PhysicalFont getDefaultPhysicalFont() { |
| if (defaultPhysicalFont == null) { |
| /* findFont2D will load all fonts before giving up the search. |
| * If the JRE Lucida isn't found (eg because the JRE fonts |
| * directory is missing), it could find another version of Lucida |
| * from the host system. This is OK because at that point we are |
| * trying to gracefully handle/recover from a system |
| * misconfiguration and this is probably a reasonable substitution. |
| */ |
| defaultPhysicalFont = (PhysicalFont) |
| findFont2D("Lucida Sans Regular", Font.PLAIN, NO_FALLBACK); |
| if (defaultPhysicalFont == null) { |
| defaultPhysicalFont = (PhysicalFont) |
| findFont2D("Arial", Font.PLAIN, NO_FALLBACK); |
| } |
| if (defaultPhysicalFont == null) { |
| /* Because of the findFont2D call above, if we reach here, we |
| * know all fonts have already been loaded, just accept any |
| * match at this point. If this fails we are in real trouble |
| * and I don't know how to recover from there being absolutely |
| * no fonts anywhere on the system. |
| */ |
| Iterator<PhysicalFont> i = physicalFonts.values().iterator(); |
| if (i.hasNext()) { |
| defaultPhysicalFont = i.next(); |
| } else { |
| throw new Error("Probable fatal error:No fonts found."); |
| } |
| } |
| } |
| return defaultPhysicalFont; |
| } |
| |
| public Font2D getDefaultLogicalFont(int style) { |
| return findFont2D("dialog", style, NO_FALLBACK); |
| } |
| |
| /* |
| * return String representation of style prepended with "." |
| * This is useful for performance to avoid unnecessary string operations. |
| */ |
| private static String dotStyleStr(int num) { |
| switch(num){ |
| case Font.BOLD: |
| return ".bold"; |
| case Font.ITALIC: |
| return ".italic"; |
| case Font.ITALIC | Font.BOLD: |
| return ".bolditalic"; |
| default: |
| return ".plain"; |
| } |
| } |
| |
| /* This is implemented only on windows and is called from code that |
| * executes only on windows. This isn't pretty but its not a precedent |
| * in this file. This very probably should be cleaned up at some point. |
| */ |
| protected void |
| populateFontFileNameMap(HashMap<String,String> fontToFileMap, |
| HashMap<String,String> fontToFamilyNameMap, |
| HashMap<String,ArrayList<String>> |
| familyToFontListMap, |
| Locale locale) { |
| } |
| |
| /* Obtained from Platform APIs (windows only) |
| * Map from lower-case font full name to basename of font file. |
| * Eg "arial bold" -> ARIALBD.TTF. |
| * For TTC files, there is a mapping for each font in the file. |
| */ |
| private HashMap<String,String> fontToFileMap = null; |
| |
| /* Obtained from Platform APIs (windows only) |
| * Map from lower-case font full name to the name of its font family |
| * Eg "arial bold" -> "Arial" |
| */ |
| private HashMap<String,String> fontToFamilyNameMap = null; |
| |
| /* Obtained from Platform APIs (windows only) |
| * Map from a lower-case family name to a list of full names of |
| * the member fonts, eg: |
| * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"] |
| */ |
| private HashMap<String,ArrayList<String>> familyToFontListMap= null; |
| |
| /* The directories which contain platform fonts */ |
| private String[] pathDirs = null; |
| |
| private boolean haveCheckedUnreferencedFontFiles; |
| |
| private String[] getFontFilesFromPath(boolean noType1) { |
| final FilenameFilter filter; |
| if (noType1) { |
| filter = ttFilter; |
| } else { |
| filter = new TTorT1Filter(); |
| } |
| return (String[])AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| public Object run() { |
| if (pathDirs.length == 1) { |
| File dir = new File(pathDirs[0]); |
| String[] files = dir.list(filter); |
| if (files == null) { |
| return new String[0]; |
| } |
| for (int f=0; f<files.length; f++) { |
| files[f] = files[f].toLowerCase(); |
| } |
| return files; |
| } else { |
| ArrayList<String> fileList = new ArrayList<String>(); |
| for (int i = 0; i< pathDirs.length; i++) { |
| File dir = new File(pathDirs[i]); |
| String[] files = dir.list(filter); |
| if (files == null) { |
| continue; |
| } |
| for (int f=0; f<files.length ; f++) { |
| fileList.add(files[f].toLowerCase()); |
| } |
| } |
| return fileList.toArray(STR_ARRAY); |
| } |
| } |
| }); |
| } |
| |
| /* This is needed since some windows registry names don't match |
| * the font names. |
| * - UPC styled font names have a double space, but the |
| * registry entry mapping to a file doesn't. |
| * - Marlett is in a hidden file not listed in the registry |
| * - The registry advertises that the file david.ttf contains a |
| * font with the full name "David Regular" when in fact its |
| * just "David". |
| * Directly fix up these known cases as this is faster. |
| * If a font which doesn't match these known cases has no file, |
| * it may be a font that has been temporarily added to the known set |
| * or it may be an installed font with a missing registry entry. |
| * Installed fonts are those in the windows font directories. |
| * Make a best effort attempt to locate these. |
| * We obtain the list of TrueType fonts in these directories and |
| * filter out all the font files we already know about from the registry. |
| * What remains may be "bad" fonts, duplicate fonts, or perhaps the |
| * missing font(s) we are looking for. |
| * Open each of these files to find out. |
| */ |
| private void resolveWindowsFonts() { |
| |
| ArrayList<String> unmappedFontNames = null; |
| for (String font : fontToFamilyNameMap.keySet()) { |
| String file = fontToFileMap.get(font); |
| if (file == null) { |
| if (font.indexOf(" ") > 0) { |
| String newName = font.replaceFirst(" ", " "); |
| file = fontToFileMap.get(newName); |
| /* If this name exists and isn't for a valid name |
| * replace the mapping to the file with this font |
| */ |
| if (file != null && |
| !fontToFamilyNameMap.containsKey(newName)) { |
| fontToFileMap.remove(newName); |
| fontToFileMap.put(font, file); |
| } |
| } else if (font.equals("marlett")) { |
| fontToFileMap.put(font, "marlett.ttf"); |
| } else if (font.equals("david")) { |
| file = fontToFileMap.get("david regular"); |
| if (file != null) { |
| fontToFileMap.remove("david regular"); |
| fontToFileMap.put("david", file); |
| } |
| } else { |
| if (unmappedFontNames == null) { |
| unmappedFontNames = new ArrayList<String>(); |
| } |
| unmappedFontNames.add(font); |
| } |
| } |
| } |
| |
| if (unmappedFontNames != null) { |
| HashSet<String> unmappedFontFiles = new HashSet<String>(); |
| |
| /* Every font key in fontToFileMap ought to correspond to a |
| * font key in fontToFamilyNameMap. Entries that don't seem |
| * to correspond are likely fonts that were named differently |
| * by GDI than in the registry. One known cause of this is when |
| * Windows has had its regional settings changed so that from |
| * GDI we get a localised (eg Chinese or Japanese) name for the |
| * font, but the registry retains the English version of the name |
| * that corresponded to the "install" locale for windows. |
| * Since we are in this code block because there are unmapped |
| * font names, we can look to find unused font->file mappings |
| * and then open the files to read the names. We don't generally |
| * want to open font files, as its a performance hit, but this |
| * occurs only for a small number of fonts on specific system |
| * configs - ie is believed that a "true" Japanese windows would |
| * have JA names in the registry too. |
| * Clone fontToFileMap and remove from the clone all keys which |
| * match a fontToFamilyNameMap key. What remains maps to the |
| * files we want to open to find the fonts GDI returned. |
| * A font in such a file is added to the fontToFileMap after |
| * checking its one of the unmappedFontNames we are looking for. |
| * The original name that didn't map is removed from fontToFileMap |
| * so essentially this "fixes up" fontToFileMap to use the same |
| * name as GDI. |
| * Also note that typically the fonts for which this occurs in |
| * CJK locales are TTC fonts and not all fonts in a TTC may have |
| * localised names. Eg MSGOTHIC.TTC contains 3 fonts and one of |
| * them "MS UI Gothic" has no JA name whereas the other two do. |
| * So not every font in these files is unmapped or new. |
| */ |
| @SuppressWarnings("unchecked") |
| HashMap<String,String> ffmapCopy = |
| (HashMap<String,String>)(fontToFileMap.clone()); |
| for (String key : fontToFamilyNameMap.keySet()) { |
| ffmapCopy.remove(key); |
| } |
| for (String key : ffmapCopy.keySet()) { |
| unmappedFontFiles.add(ffmapCopy.get(key)); |
| fontToFileMap.remove(key); |
| } |
| |
| resolveFontFiles(unmappedFontFiles, unmappedFontNames); |
| |
| /* If there are still unmapped font names, this means there's |
| * something that wasn't in the registry. We need to get all |
| * the font files directly and look at the ones that weren't |
| * found in the registry. |
| */ |
| if (unmappedFontNames.size() > 0) { |
| |
| /* getFontFilesFromPath() returns all lower case names. |
| * To compare we also need lower case |
| * versions of the names from the registry. |
| */ |
| ArrayList<String> registryFiles = new ArrayList<String>(); |
| |
| for (String regFile : fontToFileMap.values()) { |
| registryFiles.add(regFile.toLowerCase()); |
| } |
| /* We don't look for Type1 files here as windows will |
| * not enumerate these, so aren't useful in reconciling |
| * GDI's unmapped files. We do find these later when |
| * we enumerate all fonts. |
| */ |
| for (String pathFile : getFontFilesFromPath(true)) { |
| if (!registryFiles.contains(pathFile)) { |
| unmappedFontFiles.add(pathFile); |
| } |
| } |
| |
| resolveFontFiles(unmappedFontFiles, unmappedFontNames); |
| } |
| |
| /* remove from the set of names that will be returned to the |
| * user any fonts that can't be mapped to files. |
| */ |
| if (unmappedFontNames.size() > 0) { |
| int sz = unmappedFontNames.size(); |
| for (int i=0; i<sz; i++) { |
| String name = unmappedFontNames.get(i); |
| String familyName = fontToFamilyNameMap.get(name); |
| if (familyName != null) { |
| ArrayList<String> family = familyToFontListMap.get(familyName); |
| if (family != null) { |
| if (family.size() <= 1) { |
| familyToFontListMap.remove(familyName); |
| } |
| } |
| } |
| fontToFamilyNameMap.remove(name); |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("No file for font:" + name); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * In some cases windows may have fonts in the fonts folder that |
| * don't show up in the registry or in the GDI calls to enumerate fonts. |
| * The only way to find these is to list the directory. We invoke this |
| * only in getAllFonts/Families, so most searches for a specific |
| * font that is satisfied by the GDI/registry calls don't take the |
| * additional hit of listing the directory. This hit is small enough |
| * that its not significant in these 'enumerate all the fonts' cases. |
| * The basic approach is to cross-reference the files windows found |
| * with the ones in the directory listing approach, and for each |
| * in the latter list that is missing from the former list, register it. |
| */ |
| private synchronized void checkForUnreferencedFontFiles() { |
| if (haveCheckedUnreferencedFontFiles) { |
| return; |
| } |
| haveCheckedUnreferencedFontFiles = true; |
| if (!FontUtilities.isWindows) { |
| return; |
| } |
| /* getFontFilesFromPath() returns all lower case names. |
| * To compare we also need lower case |
| * versions of the names from the registry. |
| */ |
| ArrayList<String> registryFiles = new ArrayList<String>(); |
| for (String regFile : fontToFileMap.values()) { |
| registryFiles.add(regFile.toLowerCase()); |
| } |
| |
| /* To avoid any issues with concurrent modification, create |
| * copies of the existing maps, add the new fonts into these |
| * and then replace the references to the old ones with the |
| * new maps. ConcurrentHashmap is another option but its a lot |
| * more changes and with this exception, these maps are intended |
| * to be static. |
| */ |
| HashMap<String,String> fontToFileMap2 = null; |
| HashMap<String,String> fontToFamilyNameMap2 = null; |
| HashMap<String,ArrayList<String>> familyToFontListMap2 = null;; |
| |
| for (String pathFile : getFontFilesFromPath(false)) { |
| if (!registryFiles.contains(pathFile)) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Found non-registry file : " + pathFile); |
| } |
| PhysicalFont f = registerFontFile(getPathName(pathFile)); |
| if (f == null) { |
| continue; |
| } |
| if (fontToFileMap2 == null) { |
| fontToFileMap2 = new HashMap<String,String>(fontToFileMap); |
| fontToFamilyNameMap2 = |
| new HashMap<String,String>(fontToFamilyNameMap); |
| familyToFontListMap2 = new |
| HashMap<String,ArrayList<String>>(familyToFontListMap); |
| } |
| String fontName = f.getFontName(null); |
| String family = f.getFamilyName(null); |
| String familyLC = family.toLowerCase(); |
| fontToFamilyNameMap2.put(fontName, family); |
| fontToFileMap2.put(fontName, pathFile); |
| ArrayList<String> fonts = familyToFontListMap2.get(familyLC); |
| if (fonts == null) { |
| fonts = new ArrayList<String>(); |
| } else { |
| fonts = new ArrayList<String>(fonts); |
| } |
| fonts.add(fontName); |
| familyToFontListMap2.put(familyLC, fonts); |
| } |
| } |
| if (fontToFileMap2 != null) { |
| fontToFileMap = fontToFileMap2; |
| familyToFontListMap = familyToFontListMap2; |
| fontToFamilyNameMap = fontToFamilyNameMap2; |
| } |
| } |
| |
| private void resolveFontFiles(HashSet<String> unmappedFiles, |
| ArrayList<String> unmappedFonts) { |
| |
| Locale l = SunToolkit.getStartupLocale(); |
| |
| for (String file : unmappedFiles) { |
| try { |
| int fn = 0; |
| TrueTypeFont ttf; |
| String fullPath = getPathName(file); |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Trying to resolve file " + fullPath); |
| } |
| do { |
| ttf = new TrueTypeFont(fullPath, null, fn++, false); |
| // prefer the font's locale name. |
| String fontName = ttf.getFontName(l).toLowerCase(); |
| if (unmappedFonts.contains(fontName)) { |
| fontToFileMap.put(fontName, file); |
| unmappedFonts.remove(fontName); |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Resolved absent registry entry for " + |
| fontName + " located in " + fullPath); |
| } |
| } |
| } |
| while (fn < ttf.getFontCount()); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| /* Hardwire the English names and expected file names of fonts |
| * commonly used at start up. Avoiding until later even the small |
| * cost of calling platform APIs to locate these can help. |
| * The code that registers these fonts needs to "bail" if any |
| * of the files do not exist, so it will verify the existence of |
| * all non-null file names first. |
| * They are added in to a map with nominally the first |
| * word in the name of the family as the key. In all the cases |
| * we are using the family name is a single word, and as is |
| * more or less required the family name is the initial sequence |
| * in a full name. So lookup first finds the matching description, |
| * then registers the whole family, returning the right font. |
| */ |
| public static class FamilyDescription { |
| public String familyName; |
| public String plainFullName; |
| public String boldFullName; |
| public String italicFullName; |
| public String boldItalicFullName; |
| public String plainFileName; |
| public String boldFileName; |
| public String italicFileName; |
| public String boldItalicFileName; |
| } |
| |
| static HashMap<String, FamilyDescription> platformFontMap; |
| |
| /** |
| * default implementation does nothing. |
| */ |
| public HashMap<String, FamilyDescription> populateHardcodedFileNameMap() { |
| return new HashMap<String, FamilyDescription>(0); |
| } |
| |
| Font2D findFontFromPlatformMap(String lcName, int style) { |
| if (platformFontMap == null) { |
| platformFontMap = populateHardcodedFileNameMap(); |
| } |
| |
| if (platformFontMap == null || platformFontMap.size() == 0) { |
| return null; |
| } |
| |
| int spaceIndex = lcName.indexOf(' '); |
| String firstWord = lcName; |
| if (spaceIndex > 0) { |
| firstWord = lcName.substring(0, spaceIndex); |
| } |
| |
| FamilyDescription fd = platformFontMap.get(firstWord); |
| if (fd == null) { |
| return null; |
| } |
| /* Once we've established that its at least the first word, |
| * we need to dig deeper to make sure its a match for either |
| * a full name, or the family name, to make sure its not |
| * a request for some other font that just happens to start |
| * with the same first word. |
| */ |
| int styleIndex = -1; |
| if (lcName.equalsIgnoreCase(fd.plainFullName)) { |
| styleIndex = 0; |
| } else if (lcName.equalsIgnoreCase(fd.boldFullName)) { |
| styleIndex = 1; |
| } else if (lcName.equalsIgnoreCase(fd.italicFullName)) { |
| styleIndex = 2; |
| } else if (lcName.equalsIgnoreCase(fd.boldItalicFullName)) { |
| styleIndex = 3; |
| } |
| if (styleIndex == -1 && !lcName.equalsIgnoreCase(fd.familyName)) { |
| return null; |
| } |
| |
| String plainFile = null, boldFile = null, |
| italicFile = null, boldItalicFile = null; |
| |
| boolean failure = false; |
| /* In a terminal server config, its possible that getPathName() |
| * will return null, if the file doesn't exist, hence the null |
| * checks on return. But in the normal client config we need to |
| * follow this up with a check to see if all the files really |
| * exist for the non-null paths. |
| */ |
| getPlatformFontDirs(noType1Font); |
| |
| if (fd.plainFileName != null) { |
| plainFile = getPathName(fd.plainFileName); |
| if (plainFile == null) { |
| failure = true; |
| } |
| } |
| |
| if (fd.boldFileName != null) { |
| boldFile = getPathName(fd.boldFileName); |
| if (boldFile == null) { |
| failure = true; |
| } |
| } |
| |
| if (fd.italicFileName != null) { |
| italicFile = getPathName(fd.italicFileName); |
| if (italicFile == null) { |
| failure = true; |
| } |
| } |
| |
| if (fd.boldItalicFileName != null) { |
| boldItalicFile = getPathName(fd.boldItalicFileName); |
| if (boldItalicFile == null) { |
| failure = true; |
| } |
| } |
| |
| if (failure) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger(). |
| info("Hardcoded file missing looking for " + lcName); |
| } |
| platformFontMap.remove(firstWord); |
| return null; |
| } |
| |
| /* Some of these may be null,as not all styles have to exist */ |
| final String[] files = { |
| plainFile, boldFile, italicFile, boldItalicFile } ; |
| |
| failure = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Boolean>() { |
| public Boolean run() { |
| for (int i=0; i<files.length; i++) { |
| if (files[i] == null) { |
| continue; |
| } |
| File f = new File(files[i]); |
| if (!f.exists()) { |
| return Boolean.TRUE; |
| } |
| } |
| return Boolean.FALSE; |
| } |
| }); |
| |
| if (failure) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger(). |
| info("Hardcoded file missing looking for " + lcName); |
| } |
| platformFontMap.remove(firstWord); |
| return null; |
| } |
| |
| /* If we reach here we know that we have all the files we |
| * expect, so all should be fine so long as the contents |
| * are what we'd expect. Now on to registering the fonts. |
| * Currently this code only looks for TrueType fonts, so format |
| * and rank can be specified without looking at the filename. |
| */ |
| Font2D font = null; |
| for (int f=0;f<files.length;f++) { |
| if (files[f] == null) { |
| continue; |
| } |
| PhysicalFont pf = |
| registerFontFile(files[f], null, |
| FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK); |
| if (f == styleIndex) { |
| font = pf; |
| } |
| } |
| |
| |
| /* Two general cases need a bit more work here. |
| * 1) If font is null, then it was perhaps a request for a |
| * non-existent font, such as "Tahoma Italic", or a family name - |
| * where family and full name of the plain font differ. |
| * Fall back to finding the closest one in the family. |
| * This could still fail if a client specified "Segoe" instead of |
| * "Segoe UI". |
| * 2) The request is of the form "MyFont Bold", style=Font.ITALIC, |
| * and so we want to see if there's a Bold Italic font, or |
| * "MyFamily", style=Font.BOLD, and we may have matched the plain, |
| * but now need to revise that to the BOLD font. |
| */ |
| FontFamily fontFamily = FontFamily.getFamily(fd.familyName); |
| if (fontFamily != null) { |
| if (font == null) { |
| font = fontFamily.getFont(style); |
| if (font == null) { |
| font = fontFamily.getClosestStyle(style); |
| } |
| } else if (style > 0 && style != font.style) { |
| style |= font.style; |
| font = fontFamily.getFont(style); |
| if (font == null) { |
| font = fontFamily.getClosestStyle(style); |
| } |
| } |
| } |
| |
| return font; |
| } |
| private synchronized HashMap<String,String> getFullNameToFileMap() { |
| if (fontToFileMap == null) { |
| |
| pathDirs = getPlatformFontDirs(noType1Font); |
| |
| fontToFileMap = new HashMap<String,String>(100); |
| fontToFamilyNameMap = new HashMap<String,String>(100); |
| familyToFontListMap = new HashMap<String,ArrayList<String>>(50); |
| populateFontFileNameMap(fontToFileMap, |
| fontToFamilyNameMap, |
| familyToFontListMap, |
| Locale.ENGLISH); |
| if (FontUtilities.isWindows) { |
| resolveWindowsFonts(); |
| } |
| if (FontUtilities.isLogging()) { |
| logPlatformFontInfo(); |
| } |
| } |
| return fontToFileMap; |
| } |
| |
| private void logPlatformFontInfo() { |
| PlatformLogger logger = FontUtilities.getLogger(); |
| for (int i=0; i< pathDirs.length;i++) { |
| logger.info("fontdir="+pathDirs[i]); |
| } |
| for (String keyName : fontToFileMap.keySet()) { |
| logger.info("font="+keyName+" file="+ fontToFileMap.get(keyName)); |
| } |
| for (String keyName : fontToFamilyNameMap.keySet()) { |
| logger.info("font="+keyName+" family="+ |
| fontToFamilyNameMap.get(keyName)); |
| } |
| for (String keyName : familyToFontListMap.keySet()) { |
| logger.info("family="+keyName+ " fonts="+ |
| familyToFontListMap.get(keyName)); |
| } |
| } |
| |
| /* Note this return list excludes logical fonts and JRE fonts */ |
| protected String[] getFontNamesFromPlatform() { |
| if (getFullNameToFileMap().size() == 0) { |
| return null; |
| } |
| checkForUnreferencedFontFiles(); |
| /* This odd code with TreeMap is used to preserve a historical |
| * behaviour wrt the sorting order .. */ |
| ArrayList<String> fontNames = new ArrayList<String>(); |
| for (ArrayList<String> a : familyToFontListMap.values()) { |
| for (String s : a) { |
| fontNames.add(s); |
| } |
| } |
| return fontNames.toArray(STR_ARRAY); |
| } |
| |
| public boolean gotFontsFromPlatform() { |
| return getFullNameToFileMap().size() != 0; |
| } |
| |
| public String getFileNameForFontName(String fontName) { |
| String fontNameLC = fontName.toLowerCase(Locale.ENGLISH); |
| return fontToFileMap.get(fontNameLC); |
| } |
| |
| private PhysicalFont registerFontFile(String file) { |
| if (new File(file).isAbsolute() && |
| !registeredFonts.containsKey(file)) { |
| int fontFormat = FONTFORMAT_NONE; |
| int fontRank = Font2D.UNKNOWN_RANK; |
| if (ttFilter.accept(null, file)) { |
| fontFormat = FONTFORMAT_TRUETYPE; |
| fontRank = Font2D.TTF_RANK; |
| } else if |
| (t1Filter.accept(null, file)) { |
| fontFormat = FONTFORMAT_TYPE1; |
| fontRank = Font2D.TYPE1_RANK; |
| } |
| if (fontFormat == FONTFORMAT_NONE) { |
| return null; |
| } |
| return registerFontFile(file, null, fontFormat, false, fontRank); |
| } |
| return null; |
| } |
| |
| /* Used to register any font files that are found by platform APIs |
| * that weren't previously found in the standard font locations. |
| * the isAbsolute() check is needed since that's whats stored in the |
| * set, and on windows, the fonts in the system font directory that |
| * are in the fontToFileMap are just basenames. We don't want to try |
| * to register those again, but we do want to register other registry |
| * installed fonts. |
| */ |
| protected void registerOtherFontFiles(HashSet<String> registeredFontFiles) { |
| if (getFullNameToFileMap().size() == 0) { |
| return; |
| } |
| for (String file : fontToFileMap.values()) { |
| registerFontFile(file); |
| } |
| } |
| |
| public boolean |
| getFamilyNamesFromPlatform(TreeMap<String,String> familyNames, |
| Locale requestedLocale) { |
| if (getFullNameToFileMap().size() == 0) { |
| return false; |
| } |
| checkForUnreferencedFontFiles(); |
| for (String name : fontToFamilyNameMap.values()) { |
| familyNames.put(name.toLowerCase(requestedLocale), name); |
| } |
| return true; |
| } |
| |
| /* Path may be absolute or a base file name relative to one of |
| * the platform font directories |
| */ |
| private String getPathName(final String s) { |
| File f = new File(s); |
| if (f.isAbsolute()) { |
| return s; |
| } else if (pathDirs.length==1) { |
| return pathDirs[0] + File.separator + s; |
| } else { |
| String path = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<String>() { |
| public String run() { |
| for (int p=0; p<pathDirs.length; p++) { |
| File f = new File(pathDirs[p] +File.separator+ s); |
| if (f.exists()) { |
| return f.getAbsolutePath(); |
| } |
| } |
| return null; |
| } |
| }); |
| if (path != null) { |
| return path; |
| } |
| } |
| return s; // shouldn't happen, but harmless |
| } |
| |
| /* lcName is required to be lower case for use as a key. |
| * lcName may be a full name, or a family name, and style may |
| * be specified in addition to either of these. So be sure to |
| * get the right one. Since an app *could* ask for "Foo Regular" |
| * and later ask for "Foo Italic", if we don't register all the |
| * styles, then logic in findFont2D may try to style the original |
| * so we register the entire family if we get a match here. |
| * This is still a big win because this code is invoked where |
| * otherwise we would register all fonts. |
| * It's also useful for the case where "Foo Bold" was specified with |
| * style Font.ITALIC, as we would want in that case to try to return |
| * "Foo Bold Italic" if it exists, and it is only by locating "Foo Bold" |
| * and opening it that we really "know" it's Bold, and can look for |
| * a font that supports that and the italic style. |
| * The code in here is not overtly windows-specific but in fact it |
| * is unlikely to be useful as is on other platforms. It is maintained |
| * in this shared source file to be close to its sole client and |
| * because so much of the logic is intertwined with the logic in |
| * findFont2D. |
| */ |
| private Font2D findFontFromPlatform(String lcName, int style) { |
| if (getFullNameToFileMap().size() == 0) { |
| return null; |
| } |
| |
| ArrayList<String> family = null; |
| String fontFile = null; |
| String familyName = fontToFamilyNameMap.get(lcName); |
| if (familyName != null) { |
| fontFile = fontToFileMap.get(lcName); |
| family = familyToFontListMap.get |
| (familyName.toLowerCase(Locale.ENGLISH)); |
| } else { |
| family = familyToFontListMap.get(lcName); // is lcName is a family? |
| if (family != null && family.size() > 0) { |
| String lcFontName = family.get(0).toLowerCase(Locale.ENGLISH); |
| if (lcFontName != null) { |
| familyName = fontToFamilyNameMap.get(lcFontName); |
| } |
| } |
| } |
| if (family == null || familyName == null) { |
| return null; |
| } |
| String [] fontList = family.toArray(STR_ARRAY); |
| if (fontList.length == 0) { |
| return null; |
| } |
| |
| /* first check that for every font in this family we can find |
| * a font file. The specific reason for doing this is that |
| * in at least one case on Windows a font has the face name "David" |
| * but the registry entry is "David Regular". That is the "unique" |
| * name of the font but in other cases the registry contains the |
| * "full" name. See the specifications of name ids 3 and 4 in the |
| * TrueType 'name' table. |
| * In general this could cause a problem that we fail to register |
| * if we all members of a family that we may end up mapping to |
| * the wrong font member: eg return Bold when Plain is needed. |
| */ |
| for (int f=0;f<fontList.length;f++) { |
| String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH); |
| String fileName = fontToFileMap.get(fontNameLC); |
| if (fileName == null) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Platform lookup : No file for font " + |
| fontList[f] + " in family " +familyName); |
| } |
| return null; |
| } |
| } |
| |
| /* Currently this code only looks for TrueType fonts, so format |
| * and rank can be specified without looking at the filename. |
| */ |
| PhysicalFont physicalFont = null; |
| if (fontFile != null) { |
| physicalFont = registerFontFile(getPathName(fontFile), null, |
| FONTFORMAT_TRUETYPE, false, |
| Font2D.TTF_RANK); |
| } |
| /* Register all fonts in this family. */ |
| for (int f=0;f<fontList.length;f++) { |
| String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH); |
| String fileName = fontToFileMap.get(fontNameLC); |
| if (fontFile != null && fontFile.equals(fileName)) { |
| continue; |
| } |
| /* Currently this code only looks for TrueType fonts, so format |
| * and rank can be specified without looking at the filename. |
| */ |
| registerFontFile(getPathName(fileName), null, |
| FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK); |
| } |
| |
| Font2D font = null; |
| FontFamily fontFamily = FontFamily.getFamily(familyName); |
| /* Handle case where request "MyFont Bold", style=Font.ITALIC */ |
| if (physicalFont != null) { |
| style |= physicalFont.style; |
| } |
| if (fontFamily != null) { |
| font = fontFamily.getFont(style); |
| if (font == null) { |
| font = fontFamily.getClosestStyle(style); |
| } |
| } |
| return font; |
| } |
| |
| private ConcurrentHashMap<String, Font2D> fontNameCache = |
| new ConcurrentHashMap<String, Font2D>(); |
| |
| /* |
| * The client supplies a name and a style. |
| * The name could be a family name, or a full name. |
| * A font may exist with the specified style, or it may |
| * exist only in some other style. For non-native fonts the scaler |
| * may be able to emulate the required style. |
| */ |
| public Font2D findFont2D(String name, int style, int fallback) { |
| String lowerCaseName = name.toLowerCase(Locale.ENGLISH); |
| String mapName = lowerCaseName + dotStyleStr(style); |
| Font2D font; |
| |
| /* If preferLocaleFonts() or preferProportionalFonts() has been |
| * called we may be using an alternate set of composite fonts in this |
| * app context. The presence of a pre-built name map indicates whether |
| * this is so, and gives access to the alternate composite for the |
| * name. |
| */ |
| if (_usingPerAppContextComposites) { |
| @SuppressWarnings("unchecked") |
| ConcurrentHashMap<String, Font2D> altNameCache = |
| (ConcurrentHashMap<String, Font2D>) |
| AppContext.getAppContext().get(CompositeFont.class); |
| if (altNameCache != null) { |
| font = altNameCache.get(mapName); |
| } else { |
| font = null; |
| } |
| } else { |
| font = fontNameCache.get(mapName); |
| } |
| if (font != null) { |
| return font; |
| } |
| |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger().info("Search for font: " + name); |
| } |
| |
| // The check below is just so that the bitmap fonts being set by |
| // AWT and Swing thru the desktop properties do not trigger the |
| // the load fonts case. The two bitmap fonts are now mapped to |
| // appropriate equivalents for serif and sansserif. |
| // Note that the cost of this comparison is only for the first |
| // call until the map is filled. |
| if (FontUtilities.isWindows) { |
| if (lowerCaseName.equals("ms sans serif")) { |
| name = "sansserif"; |
| } else if (lowerCaseName.equals("ms serif")) { |
| name = "serif"; |
| } |
| } |
| |
| /* This isn't intended to support a client passing in the |
| * string default, but if a client passes in null for the name |
| * the java.awt.Font class internally substitutes this name. |
| * So we need to recognise it here to prevent a loadFonts |
| * on the unrecognised name. The only potential problem with |
| * this is it would hide any real font called "default"! |
| * But that seems like a potential problem we can ignore for now. |
| */ |
| if (lowerCaseName.equals("default")) { |
| name = "dialog"; |
| } |
| |
| /* First see if its a family name. */ |
| FontFamily family = FontFamily.getFamily(name); |
| if (family != null) { |
| font = family.getFontWithExactStyleMatch(style); |
| if (font == null) { |
| font = findDeferredFont(name, style); |
| } |
| if (font == null) { |
| font = family.getFont(style); |
| } |
| if (font == null) { |
| font = family.getClosestStyle(style); |
| } |
| if (font != null) { |
| fontNameCache.put(mapName, font); |
| return font; |
| } |
| } |
| |
| /* If it wasn't a family name, it should be a full name of |
| * either a composite, or a physical font |
| */ |
| font = fullNameToFont.get(lowerCaseName); |
| if (font != null) { |
| /* Check that the requested style matches the matched font's style. |
| * But also match style automatically if the requested style is |
| * "plain". This because the existing behaviour is that the fonts |
| * listed via getAllFonts etc always list their style as PLAIN. |
| * This does lead to non-commutative behaviours where you might |
| * start with "Lucida Sans Regular" and ask for a BOLD version |
| * and get "Lucida Sans DemiBold" but if you ask for the PLAIN |
| * style of "Lucida Sans DemiBold" you get "Lucida Sans DemiBold". |
| * This consistent however with what happens if you have a bold |
| * version of a font and no plain version exists - alg. styling |
| * doesn't "unbolden" the font. |
| */ |
| if (font.style == style || style == Font.PLAIN) { |
| fontNameCache.put(mapName, font); |
| return font; |
| } else { |
| /* If it was a full name like "Lucida Sans Regular", but |
| * the style requested is "bold", then we want to see if |
| * there's the appropriate match against another font in |
| * that family before trying to load all fonts, or applying a |
| * algorithmic styling |
| */ |
| family = FontFamily.getFamily(font.getFamilyName(null)); |
| if (family != null) { |
| Font2D familyFont = family.getFont(style|font.style); |
| /* We exactly matched the requested style, use it! */ |
| if (familyFont != null) { |
| fontNameCache.put(mapName, familyFont); |
| return familyFont; |
| } else { |
| /* This next call is designed to support the case |
| * where bold italic is requested, and if we must |
| * style, then base it on either bold or italic - |
| * not on plain! |
| */ |
| familyFont = family.getClosestStyle(style|font.style); |
| if (familyFont != null) { |
| /* The next check is perhaps one |
| * that shouldn't be done. ie if we get this |
| * far we have probably as close a match as we |
| * are going to get. We could load all fonts to |
| * see if somehow some parts of the family are |
| * loaded but not all of it. |
| */ |
| if (familyFont.canDoStyle(style|font.style)) { |
| fontNameCache.put(mapName, familyFont); |
| return familyFont; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (FontUtilities.isWindows) { |
| |
| font = findFontFromPlatformMap(lowerCaseName, style); |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("findFontFromPlatformMap returned " + font); |
| } |
| if (font != null) { |
| fontNameCache.put(mapName, font); |
| return font; |
| } |
| |
| /* Don't want Windows to return a Lucida Sans font from |
| * C:\Windows\Fonts |
| */ |
| if (deferredFontFiles.size() > 0) { |
| font = findJREDeferredFont(lowerCaseName, style); |
| if (font != null) { |
| fontNameCache.put(mapName, font); |
| return font; |
| } |
| } |
| font = findFontFromPlatform(lowerCaseName, style); |
| if (font != null) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Found font via platform API for request:\"" + |
| name + "\":, style="+style+ |
| " found font: " + font); |
| } |
| fontNameCache.put(mapName, font); |
| return font; |
| } |
| } |
| |
| /* If reach here and no match has been located, then if there are |
| * uninitialised deferred fonts, load as many of those as needed |
| * to find the deferred font. If none is found through that |
| * search continue on. |
| * There is possibly a minor issue when more than one |
| * deferred font implements the same font face. Since deferred |
| * fonts are only those in font configuration files, this is a |
| * controlled situation, the known case being Solaris euro_fonts |
| * versions of Arial, Times New Roman, Courier New. However |
| * the larger font will transparently replace the smaller one |
| * - see addToFontList() - when it is needed by the composite font. |
| */ |
| if (deferredFontFiles.size() > 0) { |
| font = findDeferredFont(name, style); |
| if (font != null) { |
| fontNameCache.put(mapName, font); |
| return font; |
| } |
| } |
| |
| /* Some apps use deprecated 1.0 names such as helvetica and courier. On |
| * Solaris these are Type1 fonts in /usr/openwin/lib/X11/fonts/Type1. |
| * If running on Solaris will register all the fonts in this |
| * directory. |
| * May as well register the whole directory without actually testing |
| * the font name is one of the deprecated names as the next step would |
| * load all fonts which are in this directory anyway. |
| * In the event that this lookup is successful it potentially "hides" |
| * TrueType versions of such fonts that are elsewhere but since they |
| * do not exist on Solaris this is not a problem. |
| * Set a flag to indicate we've done this registration to avoid |
| * repetition and more seriously, to avoid recursion. |
| */ |
| if (FontUtilities.isSolaris &&!loaded1dot0Fonts) { |
| /* "timesroman" is a special case since that's not the |
| * name of any known font on Solaris or elsewhere. |
| */ |
| if (lowerCaseName.equals("timesroman")) { |
| font = findFont2D("serif", style, fallback); |
| fontNameCache.put(mapName, font); |
| } |
| register1dot0Fonts(); |
| loaded1dot0Fonts = true; |
| Font2D ff = findFont2D(name, style, fallback); |
| return ff; |
| } |
| |
| /* We check for application registered fonts before |
| * explicitly loading all fonts as if necessary the registration |
| * code will have done so anyway. And we don't want to needlessly |
| * load the actual files for all fonts. |
| * Just as for installed fonts we check for family before fullname. |
| * We do not add these fonts to fontNameCache for the |
| * app context case which eliminates the overhead of a per context |
| * cache for these. |
| */ |
| |
| if (fontsAreRegistered || fontsAreRegisteredPerAppContext) { |
| Hashtable<String, FontFamily> familyTable = null; |
| Hashtable<String, Font2D> nameTable; |
| |
| if (fontsAreRegistered) { |
| familyTable = createdByFamilyName; |
| nameTable = createdByFullName; |
| } else { |
| AppContext appContext = AppContext.getAppContext(); |
| @SuppressWarnings("unchecked") |
| Hashtable<String,FontFamily> tmp1 = |
| (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); |
| familyTable = tmp1; |
| |
| @SuppressWarnings("unchecked") |
| Hashtable<String, Font2D> tmp2 = |
| (Hashtable<String,Font2D>)appContext.get(regFullNameKey); |
| nameTable = tmp2; |
| } |
| |
| family = familyTable.get(lowerCaseName); |
| if (family != null) { |
| font = family.getFontWithExactStyleMatch(style); |
| if (font == null) { |
| font = family.getFont(style); |
| } |
| if (font == null) { |
| font = family.getClosestStyle(style); |
| } |
| if (font != null) { |
| if (fontsAreRegistered) { |
| fontNameCache.put(mapName, font); |
| } |
| return font; |
| } |
| } |
| font = nameTable.get(lowerCaseName); |
| if (font != null) { |
| if (fontsAreRegistered) { |
| fontNameCache.put(mapName, font); |
| } |
| return font; |
| } |
| } |
| |
| /* If reach here and no match has been located, then if all fonts |
| * are not yet loaded, do so, and then recurse. |
| */ |
| if (!loadedAllFonts) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Load fonts looking for:" + name); |
| } |
| loadFonts(); |
| loadedAllFonts = true; |
| return findFont2D(name, style, fallback); |
| } |
| |
| if (!loadedAllFontFiles) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Load font files looking for:" + name); |
| } |
| loadFontFiles(); |
| loadedAllFontFiles = true; |
| return findFont2D(name, style, fallback); |
| } |
| |
| /* The primary name is the locale default - ie not US/English but |
| * whatever is the default in this locale. This is the way it always |
| * has been but may be surprising to some developers if "Arial Regular" |
| * were hard-coded in their app and yet "Arial Regular" was not the |
| * default name. Fortunately for them, as a consequence of the JDK |
| * supporting returning names and family names for arbitrary locales, |
| * we also need to support searching all localised names for a match. |
| * But because this case of the name used to reference a font is not |
| * the same as the default for this locale is rare, it makes sense to |
| * search a much shorter list of default locale names and only go to |
| * a longer list of names in the event that no match was found. |
| * So add here code which searches localised names too. |
| * As in 1.4.x this happens only after loading all fonts, which |
| * is probably the right order. |
| */ |
| if ((font = findFont2DAllLocales(name, style)) != null) { |
| fontNameCache.put(mapName, font); |
| return font; |
| } |
| |
| /* Perhaps its a "compatibility" name - timesroman, helvetica, |
| * or courier, which 1.0 apps used for logical fonts. |
| * We look for these "late" after a loadFonts as we must not |
| * hide real fonts of these names. |
| * Map these appropriately: |
| * On windows this means according to the rules specified by the |
| * FontConfiguration : do it only for encoding==Cp1252 |
| * |
| * REMIND: this is something we plan to remove. |
| */ |
| if (FontUtilities.isWindows) { |
| String compatName = |
| getFontConfiguration().getFallbackFamilyName(name, null); |
| if (compatName != null) { |
| font = findFont2D(compatName, style, fallback); |
| fontNameCache.put(mapName, font); |
| return font; |
| } |
| } else if (lowerCaseName.equals("timesroman")) { |
| font = findFont2D("serif", style, fallback); |
| fontNameCache.put(mapName, font); |
| return font; |
| } else if (lowerCaseName.equals("helvetica")) { |
| font = findFont2D("sansserif", style, fallback); |
| fontNameCache.put(mapName, font); |
| return font; |
| } else if (lowerCaseName.equals("courier")) { |
| font = findFont2D("monospaced", style, fallback); |
| fontNameCache.put(mapName, font); |
| return font; |
| } |
| |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger().info("No font found for:" + name); |
| } |
| |
| switch (fallback) { |
| case PHYSICAL_FALLBACK: return getDefaultPhysicalFont(); |
| case LOGICAL_FALLBACK: return getDefaultLogicalFont(style); |
| default: return null; |
| } |
| } |
| |
| /* |
| * Workaround for apps which are dependent on a font metrics bug |
| * in JDK 1.1. This is an unsupported win32 private setting. |
| * Left in for a customer - do not remove. |
| */ |
| public boolean usePlatformFontMetrics() { |
| return usePlatformFontMetrics; |
| } |
| |
| public int getNumFonts() { |
| return physicalFonts.size()+maxCompFont; |
| } |
| |
| private static boolean fontSupportsEncoding(Font font, String encoding) { |
| return FontUtilities.getFont2D(font).supportsEncoding(encoding); |
| } |
| |
| protected abstract String getFontPath(boolean noType1Fonts); |
| |
| Thread fileCloser = null; |
| Vector<File> tmpFontFiles = null; |
| |
| public Font2D[] createFont2D(File fontFile, int fontFormat, boolean all, |
| boolean isCopy, CreatedFontTracker tracker) |
| throws FontFormatException { |
| |
| List<Font2D> fList = new ArrayList<Font2D>(); |
| int cnt = 1; |
| String fontFilePath = fontFile.getPath(); |
| FileFont font2D = null; |
| final File fFile = fontFile; |
| final CreatedFontTracker _tracker = tracker; |
| try { |
| switch (fontFormat) { |
| case Font.TRUETYPE_FONT: |
| font2D = new TrueTypeFont(fontFilePath, null, 0, true); |
| fList.add(font2D); |
| if (!all) { |
| break; |
| } |
| cnt = ((TrueTypeFont)font2D).getFontCount(); |
| int index = 1; |
| while (index < cnt) { |
| fList.add(new TrueTypeFont(fontFilePath, null, index++, true)); |
| } |
| break; |
| case Font.TYPE1_FONT: |
| font2D = new Type1Font(fontFilePath, null, isCopy); |
| fList.add(font2D); |
| break; |
| default: |
| throw new FontFormatException("Unrecognised Font Format"); |
| } |
| } catch (FontFormatException e) { |
| if (isCopy) { |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| public Object run() { |
| if (_tracker != null) { |
| _tracker.subBytes((int)fFile.length()); |
| } |
| fFile.delete(); |
| return null; |
| } |
| }); |
| } |
| throw(e); |
| } |
| if (isCopy) { |
| FileFont.setFileToRemove(fList, fontFile, cnt, tracker); |
| synchronized (FontManager.class) { |
| |
| if (tmpFontFiles == null) { |
| tmpFontFiles = new Vector<File>(); |
| } |
| tmpFontFiles.add(fontFile); |
| |
| if (fileCloser == null) { |
| final Runnable fileCloserRunnable = new Runnable() { |
| public void run() { |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| public Object run() { |
| |
| for (int i=0;i<CHANNELPOOLSIZE;i++) { |
| if (fontFileCache[i] != null) { |
| try { |
| fontFileCache[i].close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| if (tmpFontFiles != null) { |
| File[] files = new File[tmpFontFiles.size()]; |
| files = tmpFontFiles.toArray(files); |
| for (int f=0; f<files.length;f++) { |
| try { |
| files[f].delete(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| }); |
| } |
| }; |
| AccessController.doPrivileged((PrivilegedAction<Void>) () -> { |
| ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup(); |
| fileCloser = new Thread(rootTG, fileCloserRunnable, |
| "FileCloser", 0, false); |
| fileCloser.setContextClassLoader(null); |
| Runtime.getRuntime().addShutdownHook(fileCloser); |
| return null; |
| }); |
| } |
| } |
| } |
| return fList.toArray(new Font2D[0]); |
| } |
| |
| /* remind: used in X11GraphicsEnvironment and called often enough |
| * that we ought to obsolete this code |
| */ |
| public synchronized String getFullNameByFileName(String fileName) { |
| PhysicalFont[] physFonts = getPhysicalFonts(); |
| for (int i=0;i<physFonts.length;i++) { |
| if (physFonts[i].platName.equals(fileName)) { |
| return (physFonts[i].getFontName(null)); |
| } |
| } |
| return null; |
| } |
| |
| /* |
| * This is called when font is determined to be invalid/bad. |
| * It designed to be called (for example) by the font scaler |
| * when in processing a font file it is discovered to be incorrect. |
| * This is different than the case where fonts are discovered to |
| * be incorrect during initial verification, as such fonts are |
| * never registered. |
| * Handles to this font held are re-directed to a default font. |
| * This default may not be an ideal substitute buts it better than |
| * crashing This code assumes a PhysicalFont parameter as it doesn't |
| * make sense for a Composite to be "bad". |
| */ |
| public synchronized void deRegisterBadFont(Font2D font2D) { |
| if (!(font2D instanceof PhysicalFont)) { |
| /* We should never reach here, but just in case */ |
| return; |
| } else { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .severe("Deregister bad font: " + font2D); |
| } |
| replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont()); |
| } |
| } |
| |
| /* |
| * This encapsulates all the work that needs to be done when a |
| * Font2D is replaced by a different Font2D. |
| */ |
| public synchronized void replaceFont(PhysicalFont oldFont, |
| PhysicalFont newFont) { |
| |
| if (oldFont.handle.font2D != oldFont) { |
| /* already done */ |
| return; |
| } |
| |
| /* If we try to replace the font with itself, that won't work, |
| * so pick any alternative physical font |
| */ |
| if (oldFont == newFont) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .severe("Can't replace bad font with itself " + oldFont); |
| } |
| PhysicalFont[] physFonts = getPhysicalFonts(); |
| for (int i=0; i<physFonts.length;i++) { |
| if (physFonts[i] != newFont) { |
| newFont = physFonts[i]; |
| break; |
| } |
| } |
| if (oldFont == newFont) { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .severe("This is bad. No good physicalFonts found."); |
| } |
| return; |
| } |
| } |
| |
| /* eliminate references to this font, so it won't be located |
| * by future callers, and will be eligible for GC when all |
| * references are removed |
| */ |
| oldFont.handle.font2D = newFont; |
| physicalFonts.remove(oldFont.fullName); |
| fullNameToFont.remove(oldFont.fullName.toLowerCase(Locale.ENGLISH)); |
| FontFamily.remove(oldFont); |
| if (localeFullNamesToFont != null) { |
| Map.Entry<?, ?>[] mapEntries = localeFullNamesToFont.entrySet(). |
| toArray(new Map.Entry<?, ?>[0]); |
| /* Should I be replacing these, or just I just remove |
| * the names from the map? |
| */ |
| for (int i=0; i<mapEntries.length;i++) { |
| if (mapEntries[i].getValue() == oldFont) { |
| try { |
| @SuppressWarnings("unchecked") |
| Map.Entry<String, PhysicalFont> tmp = (Map.Entry<String, PhysicalFont>)mapEntries[i]; |
| tmp.setValue(newFont); |
| } catch (Exception e) { |
| /* some maps don't support this operation. |
| * In this case just give up and remove the entry. |
| */ |
| localeFullNamesToFont.remove(mapEntries[i].getKey()); |
| } |
| } |
| } |
| } |
| |
| for (int i=0; i<maxCompFont; i++) { |
| /* Deferred initialization of composites shouldn't be |
| * a problem for this case, since a font must have been |
| * initialised to be discovered to be bad. |
| * Some JRE composites on Solaris use two versions of the same |
| * font. The replaced font isn't bad, just "smaller" so there's |
| * no need to make the slot point to the new font. |
| * Since composites have a direct reference to the Font2D (not |
| * via a handle) making this substitution is not safe and could |
| * cause an additional problem and so this substitution is |
| * warranted only when a font is truly "bad" and could cause |
| * a crash. So we now replace it only if its being substituted |
| * with some font other than a fontconfig rank font |
| * Since in practice a substitution will have the same rank |
| * this may never happen, but the code is safer even if its |
| * also now a no-op. |
| * The only obvious "glitch" from this stems from the current |
| * implementation that when asked for the number of glyphs in a |
| * composite it lies and returns the number in slot 0 because |
| * composite glyphs aren't contiguous. Since we live with that |
| * we can live with the glitch that depending on how it was |
| * initialised a composite may return different values for this. |
| * Fixing the issues with composite glyph ids is tricky as |
| * there are exclusion ranges and unlike other fonts even the |
| * true "numGlyphs" isn't a contiguous range. Likely the only |
| * solution is an API that returns an array of glyph ranges |
| * which takes precedence over the existing API. That might |
| * also need to address excluding ranges which represent a |
| * code point supported by an earlier component. |
| */ |
| if (newFont.getRank() > Font2D.FONT_CONFIG_RANK) { |
| compFonts[i].replaceComponentFont(oldFont, newFont); |
| } |
| } |
| } |
| |
| private synchronized void loadLocaleNames() { |
| if (localeFullNamesToFont != null) { |
| return; |
| } |
| localeFullNamesToFont = new HashMap<String, TrueTypeFont>(); |
| Font2D[] fonts = getRegisteredFonts(); |
| for (int i=0; i<fonts.length; i++) { |
| if (fonts[i] instanceof TrueTypeFont) { |
| TrueTypeFont ttf = (TrueTypeFont)fonts[i]; |
| String[] fullNames = ttf.getAllFullNames(); |
| for (int n=0; n<fullNames.length; n++) { |
| localeFullNamesToFont.put(fullNames[n], ttf); |
| } |
| FontFamily family = FontFamily.getFamily(ttf.familyName); |
| if (family != null) { |
| FontFamily.addLocaleNames(family, ttf.getAllFamilyNames()); |
| } |
| } |
| } |
| } |
| |
| /* This replicate the core logic of findFont2D but operates on |
| * all the locale names. This hasn't been merged into findFont2D to |
| * keep the logic simpler and reduce overhead, since this case is |
| * almost never used. The main case in which it is called is when |
| * a bogus font name is used and we need to check all possible names |
| * before returning the default case. |
| */ |
| private Font2D findFont2DAllLocales(String name, int style) { |
| |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Searching localised font names for:" + name); |
| } |
| |
| /* If reach here and no match has been located, then if we have |
| * not yet built the map of localeFullNamesToFont for TT fonts, do so |
| * now. This method must be called after all fonts have been loaded. |
| */ |
| if (localeFullNamesToFont == null) { |
| loadLocaleNames(); |
| } |
| String lowerCaseName = name.toLowerCase(); |
| Font2D font = null; |
| |
| /* First see if its a family name. */ |
| FontFamily family = FontFamily.getLocaleFamily(lowerCaseName); |
| if (family != null) { |
| font = family.getFont(style); |
| if (font == null) { |
| font = family.getClosestStyle(style); |
| } |
| if (font != null) { |
| return font; |
| } |
| } |
| |
| /* If it wasn't a family name, it should be a full name. */ |
| synchronized (this) { |
| font = localeFullNamesToFont.get(name); |
| } |
| if (font != null) { |
| if (font.style == style || style == Font.PLAIN) { |
| return font; |
| } else { |
| family = FontFamily.getFamily(font.getFamilyName(null)); |
| if (family != null) { |
| Font2D familyFont = family.getFont(style); |
| /* We exactly matched the requested style, use it! */ |
| if (familyFont != null) { |
| return familyFont; |
| } else { |
| familyFont = family.getClosestStyle(style); |
| if (familyFont != null) { |
| /* The next check is perhaps one |
| * that shouldn't be done. ie if we get this |
| * far we have probably as close a match as we |
| * are going to get. We could load all fonts to |
| * see if somehow some parts of the family are |
| * loaded but not all of it. |
| * This check is commented out for now. |
| */ |
| if (!familyFont.canDoStyle(style)) { |
| familyFont = null; |
| } |
| return familyFont; |
| } |
| } |
| } |
| } |
| } |
| return font; |
| } |
| |
| /* Supporting "alternate" composite fonts on 2D graphics objects |
| * is accessed by the application by calling methods on the local |
| * GraphicsEnvironment. The overall implementation is described |
| * in one place, here, since otherwise the implementation is spread |
| * around it may be difficult to track. |
| * The methods below call into SunGraphicsEnvironment which creates a |
| * new FontConfiguration instance. The FontConfiguration class, |
| * and its platform sub-classes are updated to take parameters requesting |
| * these behaviours. This is then used to create new composite font |
| * instances. Since this calls the initCompositeFont method in |
| * SunGraphicsEnvironment it performs the same initialization as is |
| * performed normally. There may be some duplication of effort, but |
| * that code is already written to be able to perform properly if called |
| * to duplicate work. The main difference is that if we detect we are |
| * running in an applet/browser/Java plugin environment these new fonts |
| * are not placed in the "default" maps but into an AppContext instance. |
| * The font lookup mechanism in java.awt.Font.getFont2D() is also updated |
| * so that look-up for composite fonts will in that case always |
| * do a lookup rather than returning a cached result. |
| * This is inefficient but necessary else singleton java.awt.Font |
| * instances would not retrieve the correct Font2D for the appcontext. |
| * sun.font.FontManager.findFont2D is also updated to that it uses |
| * a name map cache specific to that appcontext. |
| * |
| * Getting an AppContext is expensive, so there is a global variable |
| * that records whether these methods have ever been called and can |
| * avoid the expense for almost all applications. Once the correct |
| * CompositeFont is associated with the Font, everything should work |
| * through existing mechanisms. |
| * A special case is that GraphicsEnvironment.getAllFonts() must |
| * return an AppContext specific list. |
| * |
| * Calling the methods below is "heavyweight" but it is expected that |
| * these methods will be called very rarely. |
| * |
| * If _usingPerAppContextComposites is true, we are in "applet" |
| * (eg browser) environment and at least one context has selected |
| * an alternate composite font behaviour. |
| * If _usingAlternateComposites is true, we are not in an "applet" |
| * environment and the (single) application has selected |
| * an alternate composite font behaviour. |
| * |
| * - Printing: The implementation delegates logical fonts to an AWT |
| * mechanism which cannot use these alternate configurations. |
| * We can detect that alternate fonts are in use and back-off to 2D, but |
| * that uses outlines. Much of this can be fixed with additional work |
| * but that may have to wait. The results should be correct, just not |
| * optimal. |
| */ |
| private static final Object altJAFontKey = new Object(); |
| private static final Object localeFontKey = new Object(); |
| private static final Object proportionalFontKey = new Object(); |
| private boolean _usingPerAppContextComposites = false; |
| private boolean _usingAlternateComposites = false; |
| |
| /* These values are used only if we are running as a standalone |
| * application, as determined by maybeMultiAppContext(); |
| */ |
| private static boolean gAltJAFont = false; |
| private boolean gLocalePref = false; |
| private boolean gPropPref = false; |
| |
| /* This method doesn't check if alternates are selected in this app |
| * context. Its used by the FontMetrics caching code which in such |
| * a case cannot retrieve a cached metrics solely on the basis of |
| * the Font.equals() method since it needs to also check if the Font2D |
| * is the same. |
| * We also use non-standard composites for Swing native L&F fonts on |
| * Windows. In that case the policy is that the metrics reported are |
| * based solely on the physical font in the first slot which is the |
| * visible java.awt.Font. So in that case the metrics cache which tests |
| * the Font does what we want. In the near future when we expand the GTK |
| * logical font definitions we may need to revisit this if GTK reports |
| * combined metrics instead. For now though this test can be simple. |
| */ |
| public boolean maybeUsingAlternateCompositeFonts() { |
| return _usingAlternateComposites || _usingPerAppContextComposites; |
| } |
| |
| public boolean usingAlternateCompositeFonts() { |
| return (_usingAlternateComposites || |
| (_usingPerAppContextComposites && |
| AppContext.getAppContext().get(CompositeFont.class) != null)); |
| } |
| |
| private static boolean maybeMultiAppContext() { |
| Boolean appletSM = (Boolean) |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| public Object run() { |
| SecurityManager sm = System.getSecurityManager(); |
| return sm instanceof sun.applet.AppletSecurity; |
| } |
| }); |
| return appletSM.booleanValue(); |
| } |
| |
| /* Modifies the behaviour of a subsequent call to preferLocaleFonts() |
| * to use Mincho instead of Gothic for dialoginput in JA locales |
| * on windows. Not needed on other platforms. |
| */ |
| public synchronized void useAlternateFontforJALocales() { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Entered useAlternateFontforJALocales()."); |
| } |
| if (!FontUtilities.isWindows) { |
| return; |
| } |
| |
| if (!maybeMultiAppContext()) { |
| gAltJAFont = true; |
| } else { |
| AppContext appContext = AppContext.getAppContext(); |
| appContext.put(altJAFontKey, altJAFontKey); |
| } |
| } |
| |
| public boolean usingAlternateFontforJALocales() { |
| if (!maybeMultiAppContext()) { |
| return gAltJAFont; |
| } else { |
| AppContext appContext = AppContext.getAppContext(); |
| return appContext.get(altJAFontKey) == altJAFontKey; |
| } |
| } |
| |
| public synchronized void preferLocaleFonts() { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger().info("Entered preferLocaleFonts()."); |
| } |
| /* Test if re-ordering will have any effect */ |
| if (!FontConfiguration.willReorderForStartupLocale()) { |
| return; |
| } |
| |
| if (!maybeMultiAppContext()) { |
| if (gLocalePref == true) { |
| return; |
| } |
| gLocalePref = true; |
| createCompositeFonts(fontNameCache, gLocalePref, gPropPref); |
| _usingAlternateComposites = true; |
| } else { |
| AppContext appContext = AppContext.getAppContext(); |
| if (appContext.get(localeFontKey) == localeFontKey) { |
| return; |
| } |
| appContext.put(localeFontKey, localeFontKey); |
| boolean acPropPref = |
| appContext.get(proportionalFontKey) == proportionalFontKey; |
| ConcurrentHashMap<String, Font2D> |
| altNameCache = new ConcurrentHashMap<String, Font2D> (); |
| /* If there is an existing hashtable, we can drop it. */ |
| appContext.put(CompositeFont.class, altNameCache); |
| _usingPerAppContextComposites = true; |
| createCompositeFonts(altNameCache, true, acPropPref); |
| } |
| } |
| |
| public synchronized void preferProportionalFonts() { |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Entered preferProportionalFonts()."); |
| } |
| /* If no proportional fonts are configured, there's no need |
| * to take any action. |
| */ |
| if (!FontConfiguration.hasMonoToPropMap()) { |
| return; |
| } |
| |
| if (!maybeMultiAppContext()) { |
| if (gPropPref == true) { |
| return; |
| } |
| gPropPref = true; |
| createCompositeFonts(fontNameCache, gLocalePref, gPropPref); |
| _usingAlternateComposites = true; |
| } else { |
| AppContext appContext = AppContext.getAppContext(); |
| if (appContext.get(proportionalFontKey) == proportionalFontKey) { |
| return; |
| } |
| appContext.put(proportionalFontKey, proportionalFontKey); |
| boolean acLocalePref = |
| appContext.get(localeFontKey) == localeFontKey; |
| ConcurrentHashMap<String, Font2D> |
| altNameCache = new ConcurrentHashMap<String, Font2D> (); |
| /* If there is an existing hashtable, we can drop it. */ |
| appContext.put(CompositeFont.class, altNameCache); |
| _usingPerAppContextComposites = true; |
| createCompositeFonts(altNameCache, acLocalePref, true); |
| } |
| } |
| |
| private static HashSet<String> installedNames = null; |
| private static HashSet<String> getInstalledNames() { |
| if (installedNames == null) { |
| Locale l = getSystemStartupLocale(); |
| SunFontManager fontManager = SunFontManager.getInstance(); |
| String[] installedFamilies = |
| fontManager.getInstalledFontFamilyNames(l); |
| Font[] installedFonts = fontManager.getAllInstalledFonts(); |
| HashSet<String> names = new HashSet<String>(); |
| for (int i=0; i<installedFamilies.length; i++) { |
| names.add(installedFamilies[i].toLowerCase(l)); |
| } |
| for (int i=0; i<installedFonts.length; i++) { |
| names.add(installedFonts[i].getFontName(l).toLowerCase(l)); |
| } |
| installedNames = names; |
| } |
| return installedNames; |
| } |
| |
| /* Keys are used to lookup per-AppContext Hashtables */ |
| private static final Object regFamilyKey = new Object(); |
| private static final Object regFullNameKey = new Object(); |
| private Hashtable<String,FontFamily> createdByFamilyName; |
| private Hashtable<String,Font2D> createdByFullName; |
| private boolean fontsAreRegistered = false; |
| private boolean fontsAreRegisteredPerAppContext = false; |
| |
| public boolean registerFont(Font font) { |
| /* This method should not be called with "null". |
| * It is the caller's responsibility to ensure that. |
| */ |
| if (font == null) { |
| return false; |
| } |
| |
| /* Initialise these objects only once we start to use this API */ |
| synchronized (regFamilyKey) { |
| if (createdByFamilyName == null) { |
| createdByFamilyName = new Hashtable<String,FontFamily>(); |
| createdByFullName = new Hashtable<String,Font2D>(); |
| } |
| } |
| |
| if (! FontAccess.getFontAccess().isCreatedFont(font)) { |
| return false; |
| } |
| /* We want to ensure that this font cannot override existing |
| * installed fonts. Check these conditions : |
| * - family name is not that of an installed font |
| * - full name is not that of an installed font |
| * - family name is not the same as the full name of an installed font |
| * - full name is not the same as the family name of an installed font |
| * The last two of these may initially look odd but the reason is |
| * that (unfortunately) Font constructors do not distinuguish these. |
| * An extreme example of such a problem would be a font which has |
| * family name "Dialog.Plain" and full name of "Dialog". |
| * The one arguably overly stringent restriction here is that if an |
| * application wants to supply a new member of an existing family |
| * It will get rejected. But since the JRE can perform synthetic |
| * styling in many cases its not necessary. |
| * We don't apply the same logic to registered fonts. If apps want |
| * to do this lets assume they have a reason. It won't cause problems |
| * except for themselves. |
| */ |
| HashSet<String> names = getInstalledNames(); |
| Locale l = getSystemStartupLocale(); |
| String familyName = font.getFamily(l).toLowerCase(); |
| String fullName = font.getFontName(l).toLowerCase(); |
| if (names.contains(familyName) || names.contains(fullName)) { |
| return false; |
| } |
| |
| /* Checks passed, now register the font */ |
| Hashtable<String,FontFamily> familyTable; |
| Hashtable<String,Font2D> fullNameTable; |
| if (!maybeMultiAppContext()) { |
| familyTable = createdByFamilyName; |
| fullNameTable = createdByFullName; |
| fontsAreRegistered = true; |
| } else { |
| AppContext appContext = AppContext.getAppContext(); |
| @SuppressWarnings("unchecked") |
| Hashtable<String,FontFamily> tmp1 = |
| (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); |
| familyTable = tmp1; |
| @SuppressWarnings("unchecked") |
| Hashtable<String,Font2D> tmp2 = |
| (Hashtable<String,Font2D>)appContext.get(regFullNameKey); |
| fullNameTable = tmp2; |
| |
| if (familyTable == null) { |
| familyTable = new Hashtable<String,FontFamily>(); |
| fullNameTable = new Hashtable<String,Font2D>(); |
| appContext.put(regFamilyKey, familyTable); |
| appContext.put(regFullNameKey, fullNameTable); |
| } |
| fontsAreRegisteredPerAppContext = true; |
| } |
| /* Create the FontFamily and add font to the tables */ |
| Font2D font2D = FontUtilities.getFont2D(font); |
| int style = font2D.getStyle(); |
| FontFamily family = familyTable.get(familyName); |
| if (family == null) { |
| family = new FontFamily(font.getFamily(l)); |
| familyTable.put(familyName, family); |
| } |
| /* Remove name cache entries if not using app contexts. |
| * To accommodate a case where code may have registered first a plain |
| * family member and then used it and is now registering a bold family |
| * member, we need to remove all members of the family, so that the |
| * new style can get picked up rather than continuing to synthesise. |
| */ |
| if (fontsAreRegistered) { |
| removeFromCache(family.getFont(Font.PLAIN)); |
| removeFromCache(family.getFont(Font.BOLD)); |
| removeFromCache(family.getFont(Font.ITALIC)); |
| removeFromCache(family.getFont(Font.BOLD|Font.ITALIC)); |
| removeFromCache(fullNameTable.get(fullName)); |
| } |
| family.setFont(font2D, style); |
| fullNameTable.put(fullName, font2D); |
| return true; |
| } |
| |
| /* Remove from the name cache all references to the Font2D */ |
| private void removeFromCache(Font2D font) { |
| if (font == null) { |
| return; |
| } |
| String[] keys = fontNameCache.keySet().toArray(STR_ARRAY); |
| for (int k=0; k<keys.length;k++) { |
| if (fontNameCache.get(keys[k]) == font) { |
| fontNameCache.remove(keys[k]); |
| } |
| } |
| } |
| |
| // It may look odd to use TreeMap but its more convenient to the caller. |
| public TreeMap<String, String> getCreatedFontFamilyNames() { |
| |
| Hashtable<String,FontFamily> familyTable; |
| if (fontsAreRegistered) { |
| familyTable = createdByFamilyName; |
| } else if (fontsAreRegisteredPerAppContext) { |
| AppContext appContext = AppContext.getAppContext(); |
| @SuppressWarnings("unchecked") |
| Hashtable<String,FontFamily> tmp = |
| (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); |
| familyTable = tmp; |
| } else { |
| return null; |
| } |
| |
| Locale l = getSystemStartupLocale(); |
| synchronized (familyTable) { |
| TreeMap<String, String> map = new TreeMap<String, String>(); |
| for (FontFamily f : familyTable.values()) { |
| Font2D font2D = f.getFont(Font.PLAIN); |
| if (font2D == null) { |
| font2D = f.getClosestStyle(Font.PLAIN); |
| } |
| String name = font2D.getFamilyName(l); |
| map.put(name.toLowerCase(l), name); |
| } |
| return map; |
| } |
| } |
| |
| public Font[] getCreatedFonts() { |
| |
| Hashtable<String,Font2D> nameTable; |
| if (fontsAreRegistered) { |
| nameTable = createdByFullName; |
| } else if (fontsAreRegisteredPerAppContext) { |
| AppContext appContext = AppContext.getAppContext(); |
| @SuppressWarnings("unchecked") |
| Hashtable<String,Font2D> tmp = |
| (Hashtable<String,Font2D>)appContext.get(regFullNameKey); |
| nameTable = tmp; |
| } else { |
| return null; |
| } |
| |
| Locale l = getSystemStartupLocale(); |
| synchronized (nameTable) { |
| Font[] fonts = new Font[nameTable.size()]; |
| int i=0; |
| for (Font2D font2D : nameTable.values()) { |
| fonts[i++] = new Font(font2D.getFontName(l), Font.PLAIN, 1); |
| } |
| return fonts; |
| } |
| } |
| |
| |
| protected String[] getPlatformFontDirs(boolean noType1Fonts) { |
| |
| /* First check if we already initialised path dirs */ |
| if (pathDirs != null) { |
| return pathDirs; |
| } |
| |
| String path = getPlatformFontPath(noType1Fonts); |
| StringTokenizer parser = |
| new StringTokenizer(path, File.pathSeparator); |
| ArrayList<String> pathList = new ArrayList<String>(); |
| try { |
| while (parser.hasMoreTokens()) { |
| pathList.add(parser.nextToken()); |
| } |
| } catch (NoSuchElementException e) { |
| } |
| pathDirs = pathList.toArray(new String[0]); |
| return pathDirs; |
| } |
| |
| /** |
| * Returns an array of two strings. The first element is the |
| * name of the font. The second element is the file name. |
| */ |
| protected abstract String[] getDefaultPlatformFont(); |
| |
| // Begin: Refactored from SunGraphicsEnviroment. |
| |
| /* |
| * helper function for registerFonts |
| */ |
| private void addDirFonts(String dirName, File dirFile, |
| FilenameFilter filter, |
| int fontFormat, boolean useJavaRasterizer, |
| int fontRank, |
| boolean defer, boolean resolveSymLinks) { |
| String[] ls = dirFile.list(filter); |
| if (ls == null || ls.length == 0) { |
| return; |
| } |
| String[] fontNames = new String[ls.length]; |
| String[][] nativeNames = new String[ls.length][]; |
| int fontCount = 0; |
| |
| for (int i=0; i < ls.length; i++ ) { |
| File theFile = new File(dirFile, ls[i]); |
| String fullName = null; |
| if (resolveSymLinks) { |
| try { |
| fullName = theFile.getCanonicalPath(); |
| } catch (IOException e) { |
| } |
| } |
| if (fullName == null) { |
| fullName = dirName + File.separator + ls[i]; |
| } |
| |
| // REMIND: case compare depends on platform |
| if (registeredFontFiles.contains(fullName)) { |
| continue; |
| } |
| |
| if (badFonts != null && badFonts.contains(fullName)) { |
| if (FontUtilities.debugFonts()) { |
| FontUtilities.getLogger() |
| .warning("skip bad font " + fullName); |
| } |
| continue; // skip this font file. |
| } |
| |
| registeredFontFiles.add(fullName); |
| |
| if (FontUtilities.debugFonts() |
| && FontUtilities.getLogger().isLoggable(PlatformLogger.Level.INFO)) { |
| String message = "Registering font " + fullName; |
| String[] natNames = getNativeNames(fullName, null); |
| if (natNames == null) { |
| message += " with no native name"; |
| } else { |
| message += " with native name(s) " + natNames[0]; |
| for (int nn = 1; nn < natNames.length; nn++) { |
| message += ", " + natNames[nn]; |
| } |
| } |
| FontUtilities.getLogger().info(message); |
| } |
| fontNames[fontCount] = fullName; |
| nativeNames[fontCount++] = getNativeNames(fullName, null); |
| } |
| registerFonts(fontNames, nativeNames, fontCount, fontFormat, |
| useJavaRasterizer, fontRank, defer); |
| return; |
| } |
| |
| protected String[] getNativeNames(String fontFileName, |
| String platformName) { |
| return null; |
| } |
| |
| /** |
| * Returns a file name for the physical font represented by this platform |
| * font name. The default implementation tries to obtain the file name |
| * from the font configuration. |
| * Subclasses may override to provide information from other sources. |
| */ |
| protected String getFileNameFromPlatformName(String platformFontName) { |
| return fontConfig.getFileNameFromPlatformName(platformFontName); |
| } |
| |
| /** |
| * Return the default font configuration. |
| */ |
| public FontConfiguration getFontConfiguration() { |
| return fontConfig; |
| } |
| |
| /* A call to this method should be followed by a call to |
| * registerFontDirs(..) |
| */ |
| public String getPlatformFontPath(boolean noType1Font) { |
| if (fontPath == null) { |
| fontPath = getFontPath(noType1Font); |
| } |
| return fontPath; |
| } |
| |
| public static boolean isOpenJDK() { |
| return FontUtilities.isOpenJDK; |
| } |
| |
| protected void loadFonts() { |
| if (discoveredAllFonts) { |
| return; |
| } |
| /* Use lock specific to the font system */ |
| synchronized (this) { |
| if (FontUtilities.debugFonts()) { |
| Thread.dumpStack(); |
| FontUtilities.getLogger() |
| .info("SunGraphicsEnvironment.loadFonts() called"); |
| } |
| initialiseDeferredFonts(); |
| |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| public Object run() { |
| if (fontPath == null) { |
| fontPath = getPlatformFontPath(noType1Font); |
| registerFontDirs(fontPath); |
| } |
| if (fontPath != null) { |
| // this will find all fonts including those already |
| // registered. But we have checks in place to prevent |
| // double registration. |
| if (! gotFontsFromPlatform()) { |
| registerFontsOnPath(fontPath, false, |
| Font2D.UNKNOWN_RANK, |
| false, true); |
| loadedAllFontFiles = true; |
| } |
| } |
| registerOtherFontFiles(registeredFontFiles); |
| discoveredAllFonts = true; |
| return null; |
| } |
| }); |
| } |
| } |
| |
| protected void registerFontDirs(String pathName) { |
| return; |
| } |
| |
| private void registerFontsOnPath(String pathName, |
| boolean useJavaRasterizer, int fontRank, |
| boolean defer, boolean resolveSymLinks) { |
| |
| StringTokenizer parser = new StringTokenizer(pathName, |
| File.pathSeparator); |
| try { |
| while (parser.hasMoreTokens()) { |
| registerFontsInDir(parser.nextToken(), |
| useJavaRasterizer, fontRank, |
| defer, resolveSymLinks); |
| } |
| } catch (NoSuchElementException e) { |
| } |
| } |
| |
| /* Called to register fall back fonts */ |
| public void registerFontsInDir(String dirName) { |
| registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false); |
| } |
| |
| // MACOSX begin -- need to access this in subclass |
| protected void registerFontsInDir(String dirName, boolean useJavaRasterizer, |
| // MACOSX end |
| int fontRank, |
| boolean defer, boolean resolveSymLinks) { |
| File pathFile = new File(dirName); |
| addDirFonts(dirName, pathFile, ttFilter, |
| FONTFORMAT_TRUETYPE, useJavaRasterizer, |
| fontRank==Font2D.UNKNOWN_RANK ? |
| Font2D.TTF_RANK : fontRank, |
| defer, resolveSymLinks); |
| addDirFonts(dirName, pathFile, t1Filter, |
| FONTFORMAT_TYPE1, useJavaRasterizer, |
| fontRank==Font2D.UNKNOWN_RANK ? |
| Font2D.TYPE1_RANK : fontRank, |
| defer, resolveSymLinks); |
| } |
| |
| protected void registerFontDir(String path) { |
| } |
| |
| /** |
| * Returns file name for default font, either absolute |
| * or relative as needed by registerFontFile. |
| */ |
| public synchronized String getDefaultFontFile() { |
| if (defaultFontFileName == null) { |
| initDefaultFonts(); |
| } |
| return defaultFontFileName; |
| } |
| |
| private void initDefaultFonts() { |
| if (!isOpenJDK()) { |
| defaultFontName = lucidaFontName; |
| if (useAbsoluteFontFileNames()) { |
| defaultFontFileName = |
| jreFontDirName + File.separator + FontUtilities.LUCIDA_FILE_NAME; |
| } else { |
| defaultFontFileName = FontUtilities.LUCIDA_FILE_NAME; |
| } |
| } |
| } |
| |
| /** |
| * Whether registerFontFile expects absolute or relative |
| * font file names. |
| */ |
| protected boolean useAbsoluteFontFileNames() { |
| return true; |
| } |
| |
| /** |
| * Creates this environment's FontConfiguration. |
| */ |
| protected abstract FontConfiguration createFontConfiguration(); |
| |
| public abstract FontConfiguration |
| createFontConfiguration(boolean preferLocaleFonts, |
| boolean preferPropFonts); |
| |
| /** |
| * Returns face name for default font, or null if |
| * no face names are used for CompositeFontDescriptors |
| * for this platform. |
| */ |
| public synchronized String getDefaultFontFaceName() { |
| if (defaultFontName == null) { |
| initDefaultFonts(); |
| } |
| return defaultFontName; |
| } |
| |
| public void loadFontFiles() { |
| loadFonts(); |
| if (loadedAllFontFiles) { |
| return; |
| } |
| /* Use lock specific to the font system */ |
| synchronized (this) { |
| if (FontUtilities.debugFonts()) { |
| Thread.dumpStack(); |
| FontUtilities.getLogger().info("loadAllFontFiles() called"); |
| } |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| public Object run() { |
| if (fontPath == null) { |
| fontPath = getPlatformFontPath(noType1Font); |
| } |
| if (fontPath != null) { |
| // this will find all fonts including those already |
| // registered. But we have checks in place to prevent |
| // double registration. |
| registerFontsOnPath(fontPath, false, |
| Font2D.UNKNOWN_RANK, |
| false, true); |
| } |
| loadedAllFontFiles = true; |
| return null; |
| } |
| }); |
| } |
| } |
| |
| /* |
| * This method asks the font configuration API for all platform names |
| * used as components of composite/logical fonts and iterates over these |
| * looking up their corresponding file name and registers these fonts. |
| * It also ensures that the fonts are accessible via platform APIs. |
| * The composites themselves are then registered. |
| */ |
| private void |
| initCompositeFonts(FontConfiguration fontConfig, |
| ConcurrentHashMap<String, Font2D> altNameCache) { |
| |
| if (FontUtilities.isLogging()) { |
| FontUtilities.getLogger() |
| .info("Initialising composite fonts"); |
| } |
| |
| int numCoreFonts = fontConfig.getNumberCoreFonts(); |
| String[] fcFonts = fontConfig.getPlatformFontNames(); |
| for (int f=0; f<fcFonts.length; f++) { |
| String platformFontName = fcFonts[f]; |
| String fontFileName = |
| getFileNameFromPlatformName(platformFontName); |
| String[] nativeNames = null; |
| if (fontFileName == null |
| || fontFileName.equals(platformFontName)) { |
| /* No file located, so register using the platform name, |
| * i.e. as a native font. |
| */ |
| fontFileName = platformFontName; |
| } else { |
| if (f < numCoreFonts) { |
| /* If platform APIs also need to access the font, add it |
| * to a set to be registered with the platform too. |
| * This may be used to add the parent directory to the X11 |
| * font path if its not already there. See the docs for the |
| * subclass implementation. |
| * This is now mainly for the benefit of X11-based AWT |
| * But for historical reasons, 2D initialisation code |
| * makes these calls. |
| * If the fontconfiguration file is properly set up |
| * so that all fonts are mapped to files and all their |
| * appropriate directories are specified, then this |
| * method will be low cost as it will return after |
| * a test that finds a null lookup map. |
| */ |
| addFontToPlatformFontPath(platformFontName); |
| } |
| nativeNames = getNativeNames(fontFileName, platformFontName); |
| } |
| /* Uncomment these two lines to "generate" the XLFD->filename |
| * mappings needed to speed start-up on Solaris. |
| * Augment this with the appendedpathname and the mappings |
| * for native (F3) fonts |
| */ |
| //String platName = platformFontName.replaceAll(" ", "_"); |
| //System.out.println("filename."+platName+"="+fontFileName); |
| registerFontFile(fontFileName, nativeNames, |
| Font2D.FONT_CONFIG_RANK, true); |
| |
| |
| } |
| /* This registers accumulated paths from the calls to |
| * addFontToPlatformFontPath(..) and any specified by |
| * the font configuration. Rather than registering |
| * the fonts it puts them in a place and form suitable for |
| * the Toolkit to pick up and use if a toolkit is initialised, |
| * and if it uses X11 fonts. |
| */ |
| registerPlatformFontsUsedByFontConfiguration(); |
| |
| CompositeFontDescriptor[] compositeFontInfo |
| = fontConfig.get2DCompositeFontInfo(); |
| for (int i = 0; i < compositeFontInfo.length; i++) { |
| CompositeFontDescriptor descriptor = compositeFontInfo[i]; |
| String[] componentFileNames = descriptor.getComponentFileNames(); |
| String[] componentFaceNames = descriptor.getComponentFaceNames(); |
| |
| /* It would be better eventually to handle this in the |
| * FontConfiguration code which should also remove duplicate slots |
| */ |
| if (missingFontFiles != null) { |
| for (int ii=0; ii<componentFileNames.length; ii++) { |
| if (missingFontFiles.contains(componentFileNames[ii])) { |
| componentFileNames[ii] = getDefaultFontFile(); |
| componentFaceNames[ii] = getDefaultFontFaceName(); |
| } |
| } |
| } |
| |
| /* FontConfiguration needs to convey how many fonts it has added |
| * as fallback component fonts which should not affect metrics. |
| * The core component count will be the number of metrics slots. |
| * This does not preclude other mechanisms for adding |
| * fall back component fonts to the composite. |
| */ |
| if (altNameCache != null) { |
| SunFontManager.registerCompositeFont( |
| descriptor.getFaceName(), |
| componentFileNames, componentFaceNames, |
| descriptor.getCoreComponentCount(), |
| descriptor.getExclusionRanges(), |
| descriptor.getExclusionRangeLimits(), |
| true, |
| altNameCache); |
| } else { |
| registerCompositeFont(descriptor.getFaceName(), |
| componentFileNames, componentFaceNames, |
| descriptor.getCoreComponentCount(), |
| descriptor.getExclusionRanges(), |
| descriptor.getExclusionRangeLimits(), |
| true); |
| } |
| if (FontUtilities.debugFonts()) { |
| FontUtilities.getLogger() |
| .info("registered " + descriptor.getFaceName()); |
| } |
| } |
| } |
| |
| /** |
| * Notifies graphics environment that the logical font configuration |
| * uses the given platform font name. The graphics environment may |
| * use this for platform specific initialization. |
| */ |
| protected void addFontToPlatformFontPath(String platformFontName) { |
| } |
| |
| protected void registerFontFile(String fontFileName, String[] nativeNames, |
| int fontRank, boolean defer) { |
| // REMIND: case compare depends on platform |
| if (registeredFontFiles.contains(fontFileName)) { |
| return; |
| } |
| int fontFormat; |
| if (ttFilter.accept(null, fontFileName)) { |
| fontFormat = FONTFORMAT_TRUETYPE; |
| } else if (t1Filter.accept(null, fontFileName)) { |
| fontFormat = FONTFORMAT_TYPE1; |
| } else { |
| fontFormat = FONTFORMAT_NATIVE; |
| } |
| registeredFontFiles.add(fontFileName); |
| if (defer) { |
| registerDeferredFont(fontFileName, fontFileName, nativeNames, |
| fontFormat, false, fontRank); |
| } else { |
| registerFontFile(fontFileName, nativeNames, fontFormat, false, |
| fontRank); |
| } |
| } |
| |
| protected void registerPlatformFontsUsedByFontConfiguration() { |
| } |
| |
| /* |
| * A GE may verify whether a font file used in a fontconfiguration |
| * exists. If it doesn't then either we may substitute the default |
| * font, or perhaps elide it altogether from the composite font. |
| * This makes some sense on windows where the font file is only |
| * likely to be in one place. But on other OSes, eg Linux, the file |
| * can move around depending. So there we probably don't want to assume |
| * its missing and so won't add it to this list. |
| * If this list - missingFontFiles - is non-null then the composite |
| * font initialisation logic tests to see if a font file is in that |
| * set. |
| * Only one thread should be able to add to this set so we don't |
| * synchronize. |
| */ |
| protected void addToMissingFontFileList(String fileName) { |
| if (missingFontFiles == null) { |
| missingFontFiles = new HashSet<String>(); |
| } |
| missingFontFiles.add(fileName); |
| } |
| |
| /* |
| * This is for use only within getAllFonts(). |
| * Fonts listed in the fontconfig files for windows were all |
| * on the "deferred" initialisation list. They were registered |
| * either in the course of the application, or in the call to |
| * loadFonts() within getAllFonts(). The fontconfig file specifies |
| * the names of the fonts using the English names. If there's a |
| * different name in the execution locale, then the platform will |
| * report that, and we will construct the font with both names, and |
| * thereby enumerate it twice. This happens for Japanese fonts listed |
| * in the windows fontconfig, when run in the JA locale. The solution |
| * is to rely (in this case) on the platform's font->file mapping to |
| * determine that this name corresponds to a file we already registered. |
| * This works because |
| * - we know when we get here all deferred fonts are already initialised |
| * - when we register a font file, we register all fonts in it. |
| * - we know the fontconfig fonts are all in the windows registry |
| */ |
| private boolean isNameForRegisteredFile(String fontName) { |
| String fileName = getFileNameForFontName(fontName); |
| if (fileName == null) { |
| return false; |
| } |
| return registeredFontFiles.contains(fileName); |
| } |
| |
| /* |
| * This invocation is not in a privileged block because |
| * all privileged operations (reading files and properties) |
| * was conducted on the creation of the GE |
| */ |
| public void |
| createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache, |
| boolean preferLocale, |
| boolean preferProportional) { |
| |
| FontConfiguration fontConfig = |
| createFontConfiguration(preferLocale, preferProportional); |
| initCompositeFonts(fontConfig, altNameCache); |
| } |
| |
| /** |
| * Returns all fonts installed in this environment. |
| */ |
| public Font[] getAllInstalledFonts() { |
| if (allFonts == null) { |
| loadFonts(); |
| TreeMap<String, Font2D> fontMapNames = new TreeMap<>(); |
| /* warning: the number of composite fonts could change dynamically |
| * if applications are allowed to create them. "allfonts" could |
| * then be stale. |
| */ |
| Font2D[] allfonts = getRegisteredFonts(); |
| for (int i=0; i < allfonts.length; i++) { |
| if (!(allfonts[i] instanceof NativeFont)) { |
| fontMapNames.put(allfonts[i].getFontName(null), |
| allfonts[i]); |
| } |
| } |
| |
| String[] platformNames = getFontNamesFromPlatform(); |
| if (platformNames != null) { |
| for (int i=0; i<platformNames.length; i++) { |
| if (!isNameForRegisteredFile(platformNames[i])) { |
| fontMapNames.put(platformNames[i], null); |
| } |
| } |
| } |
| |
| String[] fontNames = null; |
| if (fontMapNames.size() > 0) { |
| fontNames = new String[fontMapNames.size()]; |
| Object [] keyNames = fontMapNames.keySet().toArray(); |
| for (int i=0; i < keyNames.length; i++) { |
| fontNames[i] = (String)keyNames[i]; |
| } |
| } |
| Font[] fonts = new Font[fontNames.length]; |
| for (int i=0; i < fontNames.length; i++) { |
| fonts[i] = new Font(fontNames[i], Font.PLAIN, 1); |
| Font2D f2d = fontMapNames.get(fontNames[i]); |
| if (f2d != null) { |
| FontAccess.getFontAccess().setFont2D(fonts[i], f2d.handle); |
| } |
| } |
| allFonts = fonts; |
| } |
| |
| Font []copyFonts = new Font[allFonts.length]; |
| System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length); |
| return copyFonts; |
| } |
| |
| /** |
| * Get a list of installed fonts in the requested {@link Locale}. |
| * The list contains the fonts Family Names. |
| * If Locale is null, the default locale is used. |
| * |
| * @param requestedLocale, if null the default locale is used. |
| * @return list of installed fonts in the system. |
| */ |
| public String[] getInstalledFontFamilyNames(Locale requestedLocale) { |
| if (requestedLocale == null) { |
| requestedLocale = Locale.getDefault(); |
| } |
| if (allFamilies != null && lastDefaultLocale != null && |
| requestedLocale.equals(lastDefaultLocale)) { |
| String[] copyFamilies = new String[allFamilies.length]; |
| System.arraycopy(allFamilies, 0, copyFamilies, |
| 0, allFamilies.length); |
| return copyFamilies; |
| } |
| |
| TreeMap<String,String> familyNames = new TreeMap<String,String>(); |
| // these names are always there and aren't localised |
| String str; |
| str = Font.SERIF; familyNames.put(str.toLowerCase(), str); |
| str = Font.SANS_SERIF; familyNames.put(str.toLowerCase(), str); |
| str = Font.MONOSPACED; familyNames.put(str.toLowerCase(), str); |
| str = Font.DIALOG; familyNames.put(str.toLowerCase(), str); |
| str = Font.DIALOG_INPUT; familyNames.put(str.toLowerCase(), str); |
| |
| /* Platform APIs may be used to get the set of available family |
| * names for the current default locale so long as it is the same |
| * as the start-up system locale, rather than loading all fonts. |
| */ |
| if (requestedLocale.equals(getSystemStartupLocale()) && |
| getFamilyNamesFromPlatform(familyNames, requestedLocale)) { |
| /* Augment platform names with JRE font family names */ |
| getJREFontFamilyNames(familyNames, requestedLocale); |
| } else { |
| loadFontFiles(); |
| Font2D[] physicalfonts = getPhysicalFonts(); |
| for (int i=0; i < physicalfonts.length; i++) { |
| if (!(physicalfonts[i] instanceof NativeFont)) { |
| String name = |
| physicalfonts[i].getFamilyName(requestedLocale); |
| familyNames.put(name.toLowerCase(requestedLocale), name); |
| } |
| } |
| } |
| |
| // Add any native font family names here |
| addNativeFontFamilyNames(familyNames, requestedLocale); |
| |
| String[] retval = new String[familyNames.size()]; |
| Object [] keyNames = familyNames.keySet().toArray(); |
| for (int i=0; i < keyNames.length; i++) { |
| retval[i] = familyNames.get(keyNames[i]); |
| } |
| if (requestedLocale.equals(Locale.getDefault())) { |
| lastDefaultLocale = requestedLocale; |
| allFamilies = new String[retval.length]; |
| System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length); |
| } |
| return retval; |
| } |
| |
| // Provides an aperture to add native font family names to the map |
| protected void addNativeFontFamilyNames(TreeMap<String, String> familyNames, Locale requestedLocale) { } |
| |
| public void register1dot0Fonts() { |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| public Object run() { |
| String type1Dir = "/usr/openwin/lib/X11/fonts/Type1"; |
| registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK, |
| false, false); |
| return null; |
| } |
| }); |
| } |
| |
| /* Really we need only the JRE fonts family names, but there's little |
| * overhead in doing this the easy way by adding all the currently |
| * known fonts. |
| */ |
| protected void getJREFontFamilyNames(TreeMap<String,String> familyNames, |
| Locale requestedLocale) { |
| registerDeferredJREFonts(jreFontDirName); |
| Font2D[] physicalfonts = getPhysicalFonts(); |
| for (int i=0; i < physicalfonts.length; i++) { |
| if (!(physicalfonts[i] instanceof NativeFont)) { |
| String name = |
| physicalfonts[i].getFamilyName(requestedLocale); |
| familyNames.put(name.toLowerCase(requestedLocale), name); |
| } |
| } |
| } |
| |
| /** |
| * Default locale can be changed but we need to know the initial locale |
| * as that is what is used by native code. Changing Java default locale |
| * doesn't affect that. |
| * Returns the locale in use when using native code to communicate |
| * with platform APIs. On windows this is known as the "system" locale, |
| * and it is usually the same as the platform locale, but not always, |
| * so this method also checks an implementation property used only |
| * on windows and uses that if set. |
| */ |
| private static Locale systemLocale = null; |
| private static Locale getSystemStartupLocale() { |
| if (systemLocale == null) { |
| systemLocale = (Locale) |
| java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Object>() { |
| public Object run() { |
| /* On windows the system locale may be different than the |
| * user locale. This is an unsupported configuration, but |
| * in that case we want to return a dummy locale that will |
| * never cause a match in the usage of this API. This is |
| * important because Windows documents that the family |
| * names of fonts are enumerated using the language of |
| * the system locale. BY returning a dummy locale in that |
| * case we do not use the platform API which would not |
| * return us the names we want. |
| */ |
| String fileEncoding = System.getProperty("file.encoding", ""); |
| String sysEncoding = System.getProperty("sun.jnu.encoding"); |
| if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) { |
| return Locale.ROOT; |
| } |
| |
| String language = System.getProperty("user.language", "en"); |
| String country = System.getProperty("user.country",""); |
| String variant = System.getProperty("user.variant",""); |
| return new Locale(language, country, variant); |
| } |
| }); |
| } |
| return systemLocale; |
| } |
| |
| void addToPool(FileFont font) { |
| |
| FileFont fontFileToClose = null; |
| int freeSlot = -1; |
| |
| synchronized (fontFileCache) { |
| /* Avoid duplicate entries in the pool, and don't close() it, |
| * since this method is called only from within open(). |
| * Seeing a duplicate is most likely to happen if the thread |
| * was interrupted during a read, forcing perhaps repeated |
| * close and open calls and it eventually it ends up pointing |
| * at the same slot. |
| */ |
| for (int i=0;i<CHANNELPOOLSIZE;i++) { |
| if (fontFileCache[i] == font) { |
| return; |
| } |
| if (fontFileCache[i] == null && freeSlot < 0) { |
| freeSlot = i; |
| } |
| } |
| if (freeSlot >= 0) { |
| fontFileCache[freeSlot] = font; |
| return; |
| } else { |
| /* replace with new font. */ |
| fontFileToClose = fontFileCache[lastPoolIndex]; |
| fontFileCache[lastPoolIndex] = font; |
| /* lastPoolIndex is updated so that the least recently opened |
| * file will be closed next. |
| */ |
| lastPoolIndex = (lastPoolIndex+1) % CHANNELPOOLSIZE; |
| } |
| } |
| /* Need to close the font file outside of the synchronized block, |
| * since its possible some other thread is in an open() call on |
| * this font file, and could be holding its lock and the pool lock. |
| * Releasing the pool lock allows that thread to continue, so it can |
| * then release the lock on this font, allowing the close() call |
| * below to proceed. |
| * Also, calling close() is safe because any other thread using |
| * the font we are closing() synchronizes all reading, so we |
| * will not close the file while its in use. |
| */ |
| if (fontFileToClose != null) { |
| fontFileToClose.close(); |
| } |
| } |
| |
| protected FontUIResource getFontConfigFUIR(String family, int style, |
| int size) |
| { |
| return new FontUIResource(family, style, size); |
| } |
| } |