/* * Copyright (c) 2008, 2020, 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.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.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)); } } } } private static Font2DHandle FONT_HANDLE_NULL = new Font2DHandle(null); 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_TTC = 2; public static final int FONTFORMAT_COMPOSITE = 3; public static final int FONTFORMAT_NATIVE = 4; /* 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 compositeFonts = new ConcurrentHashMap<>(); private ConcurrentHashMap physicalFonts = new ConcurrentHashMap<>(); private ConcurrentHashMap registeredFonts = new ConcurrentHashMap<>(); /* given a full name find the Font. Remind: there's duplication * here in that this contains the content of compositeFonts + * physicalFonts. */ // MACOSX begin -- need to access this in subclass protected ConcurrentHashMap fullNameToFont = new ConcurrentHashMap<>(); // MACOSX end /* TrueType fonts have localised names. Support searching all * of these before giving up on a name. */ private HashMap localeFullNamesToFont; private PhysicalFont defaultPhysicalFont; static boolean longAddresses; private boolean loaded1dot0Fonts = false; boolean loadedAllFonts = false; boolean loadedAllFontFiles = false; String[] jreOtherFontFiles; boolean noOtherJREFontFiles = false; // initial assumption. public static String jreLibDirName; public static String jreFontDirName; private static HashSet missingFontFiles = null; private String defaultFontName; private String defaultFontFileName; protected HashSet registeredFontFiles = new HashSet<>(); private ArrayList badFonts; /* fontPath is the location of all fonts on the system, excluding the * JRE's own font directory but including any path specified using the * sun.java2d.fontpath property. Together with that property, it is * initialised by the getPlatformFontPath() method * This call must be followed by a call to registerFontDirs(fontPath) * once any extra debugging path has been appended. */ protected String fontPath; private FontConfiguration fontConfig; /* discoveredAllFonts is set to true when all fonts on the font path are * discovered. This usually also implies opening, validating and * registering, but an implementation may be optimized to avold this. * So see also "loadedAllFontFiles" */ private boolean discoveredAllFonts = false; /* No need to keep consing up new instances - reuse a singleton. * The trade-off is that these objects don't get GC'd. */ private static final FilenameFilter ttFilter = new TTFilter(); private static final FilenameFilter t1Filter = new T1Filter(); private Font[] allFonts; private String[] allFamilies; // cache for default locale only private Locale lastDefaultLocale; public static boolean noType1Font; /* Used to indicate required return type from toArray(..); */ private static String[] STR_ARRAY = new String[0]; /** * Deprecated, unsupported hack - actually invokes a bug! * Left in for a customer, don't remove. */ private boolean usePlatformFontMetrics = false; /** * Returns the global SunFontManager instance. This is similar to * {@link FontManagerFactory#getInstance()} but it returns a * SunFontManager instance instead. This is only used in internal classes * where we can safely assume that a SunFontManager is to be used. * * @return the global SunFontManager instance */ public static SunFontManager getInstance() { FontManager fm = FontManagerFactory.getInstance(); return (SunFontManager) fm; } public FilenameFilter getTrueTypeFilter() { return ttFilter; } public FilenameFilter getType1Filter() { return t1Filter; } /* After we reach MAXSOFTREFCNT, use weak refs for created fonts. * This means that a small number of created fonts as used in a UI app * will not be eagerly collected, but an app that create many will * have them collected more frequently to reclaim storage. */ private static int maxSoftRefCnt = 10; static { AccessController.doPrivileged(new PrivilegedAction() { public Void 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"; maxSoftRefCnt = Integer.getInteger("sun.java2d.font.maxSoftRefs", 10); 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(); protected SunFontManager() { AccessController.doPrivileged(new PrivilegedAction() { public Void run() { File badFontFile = new File(jreFontDirName + File.separator + "badfonts.txt"); if (badFontFile.exists()) { badFonts = new ArrayList<>(); try (FileInputStream fis = new FileInputStream(badFontFile); BufferedReader br = new BufferedReader(new InputStreamReader(fis))) { 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) { } } /* 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 JDK 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(); 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 += 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); } initCompositeFonts(fontConfig, null); return null; } }); boolean platformFont = AccessController.doPrivileged( new PrivilegedAction() { public Boolean run() { String prop = System.getProperty("java2d.font.usePlatformFont"); String env = System.getenv("JAVA2D_USEPLATFORMFONT"); return "true".equals(prop) || env != null; } }); if (platformFont) { usePlatformFontMetrics = true; System.out.println("Enabling platform font metrics for win32. This is an unsupported option."); System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases."); System.out.println("It is appropriate only for use by applications which do not use any Java 2"); System.out.println("functionality. This property will be removed in a later release."); } } public Font2DHandle getNewComposite(String family, int style, Font2DHandle handle) { if (!(handle.font2D instanceof CompositeFont)) { return handle; } CompositeFont oldComp = (CompositeFont)handle.font2D; PhysicalFont oldFont = oldComp.getSlotFont(0); if (family == null) { family = oldFont.getFamilyName(null); } if (style == -1) { style = oldComp.getStyle(); } Font2D newFont = findFont2D(family, style, NO_FALLBACK); if (!(newFont instanceof PhysicalFont)) { newFont = oldFont; } PhysicalFont physicalFont = (PhysicalFont)newFont; CompositeFont dialog2D = (CompositeFont)findFont2D("dialog", style, NO_FALLBACK); if (dialog2D == null) { /* shouldn't happen */ return handle; } CompositeFont compFont = new CompositeFont(physicalFont, dialog2D); Font2DHandle newHandle = new Font2DHandle(compFont); return newHandle; } protected void registerCompositeFont(String compositeName, String[] componentFileNames, String[] componentNames, int numMetricsSlots, int[] exclusionRanges, int[] exclusionMaxIndex, boolean defer) { CompositeFont cf = new CompositeFont(compositeName, componentFileNames, componentNames, numMetricsSlots, exclusionRanges, exclusionMaxIndex, defer, this); addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK); synchronized (compFonts) { compFonts[maxCompFont++] = cf; } } /* This variant is used only when the application specifies * a variant of composite fonts which prefers locale specific or * proportional fonts. */ protected static void registerCompositeFont(String compositeName, String[] componentFileNames, String[] componentNames, int numMetricsSlots, int[] exclusionRanges, int[] exclusionMaxIndex, boolean defer, ConcurrentHashMap altNameCache) { CompositeFont cf = new CompositeFont(compositeName, componentFileNames, componentNames, numMetricsSlots, exclusionRanges, exclusionMaxIndex, defer, SunFontManager.getInstance()); /* if the cache has an existing composite for this case, make * its handle point to this new font. * This ensures that when the altNameCache that is passed in * is the global mapNameCache - ie we are running as an application - * that any statically created java.awt.Font instances which already * have a Font2D instance will have that re-directed to the new Font * on subsequent uses. This is particularly important for "the" * default font instance, or similar cases where a UI toolkit (eg * Swing) has cached a java.awt.Font. Note that if Swing is using * a custom composite APIs which update the standard composites have * no effect - this is typically the case only when using the Windows * L&F where these APIs would conflict with that L&F anyway. */ Font2D oldFont =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 || fontName.isEmpty()) { 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 deferredFontFiles = new ConcurrentHashMap<>(); private final ConcurrentHashMap initialisedFonts = new ConcurrentHashMap<>(); /* Remind: possibly enhance initialiseDeferredFonts() to be * optionally given a name and a style and it could stop when it * finds that font - but this would be a problem if two of the * fonts reference the same font face name (cf the Solaris * euro fonts). */ protected synchronized void initialiseDeferredFonts() { for (String fileName : deferredFontFiles.keySet()) { initialiseDeferredFont(fileName); } } protected synchronized void registerDeferredJREFonts(String jreDir) { for (FontRegistrationInfo info : deferredFontFiles.values()) { if (info.fontFilePath != null && info.fontFilePath.startsWith(jreDir)) { initialiseDeferredFont(info.fontFilePath); } } } public boolean isDeferredFont(String fileName) { return deferredFontFiles.containsKey(fileName); } PhysicalFont findJREDeferredFont(String name, int style) { /* 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 jreOtherFontFiles will be empty. * noOtherJREFontFiles is used so we can skip this block as soon * as its determined that it's not needed - almost always after the * very first time through. */ if (noOtherJREFontFiles) { return null; } synchronized (jreFontDirName) { if (jreOtherFontFiles == null) { HashSet otherFontFiles = new HashSet<>(); for (String deferredFile : deferredFontFiles.keySet()) { File file = new File(deferredFile); String dir = file.getParent(); /* skip names which aren't absolute, aren't in the JRE * directory, or are known Lucida fonts. */ if (dir == null || !dir.equals(jreFontDirName)) { continue; } otherFontFiles.add(deferredFile); } jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY); if (jreOtherFontFiles.length == 0) { noOtherJREFontFiles = true; } } for (int i=0; inew Error("Probable fatal error: No physical 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 fontToFileMap, HashMap fontToFamilyNameMap, HashMap> familyToFontListMap, Locale locale) { } /* Obtained from Platform APIs (windows only) * Map from lower-case font full name to basename of font file. * Eg "arial bold" -> ARIALBD.TTF. * For TTC files, there is a mapping for each font in the file. */ private HashMap fontToFileMap = null; /* Obtained from Platform APIs (windows only) * Map from lower-case font full name to the name of its font family * Eg "arial bold" -> "Arial" */ private HashMap fontToFamilyNameMap = null; /* Obtained from Platform APIs (windows only) * Map from a lower-case family name to a list of full names of * the member fonts, eg: * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"] */ private HashMap> familyToFontListMap= null; /* The directories which contain platform fonts */ private String[] pathDirs = null; private boolean haveCheckedUnreferencedFontFiles; private String[] getFontFilesFromPath(boolean noType1) { final FilenameFilter filter; if (noType1) { filter = ttFilter; } else { filter = new TTorT1Filter(); } return AccessController.doPrivileged(new PrivilegedAction() { public String[] run() { if (pathDirs.length == 1) { File dir = new File(pathDirs[0]); String[] files = dir.list(filter); if (files == null) { return new String[0]; } for (int f=0; f fileList = new ArrayList<>(); for (int i = 0; i< pathDirs.length; i++) { File dir = new File(pathDirs[i]); String[] files = dir.list(filter); if (files == null) { continue; } for (int f = 0; f < 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 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<>(); } unmappedFontNames.add(font); } } } if (unmappedFontNames != null) { HashSet unmappedFontFiles = new HashSet<>(); /* 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 ffmapCopy = (HashMap)(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 registryFiles = new ArrayList<>(); 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 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 registryFiles = new ArrayList<>(); 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 fontToFileMap2 = null; HashMap fontToFamilyNameMap2 = null; HashMap> 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<>(fontToFileMap); fontToFamilyNameMap2 = new HashMap<>(fontToFamilyNameMap); familyToFontListMap2 = new HashMap<>(familyToFontListMap); } String fontName = f.getFontName(null); String family = f.getFamilyName(null); String familyLC = family.toLowerCase(); fontToFamilyNameMap2.put(fontName, family); fontToFileMap2.put(fontName, pathFile); ArrayList fonts = familyToFontListMap2.get(familyLC); if (fonts == null) { fonts = new ArrayList<>(); } else { fonts = new ArrayList<>(fonts); } fonts.add(fontName); familyToFontListMap2.put(familyLC, fonts); } } if (fontToFileMap2 != null) { fontToFileMap = fontToFileMap2; familyToFontListMap = familyToFontListMap2; fontToFamilyNameMap = fontToFamilyNameMap2; } } private void resolveFontFiles(HashSet unmappedFiles, ArrayList 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 platformFontMap; /** * default implementation does nothing. */ public HashMap populateHardcodedFileNameMap() { return new HashMap<>(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 = AccessController.doPrivileged(new PrivilegedAction() { public Boolean run() { for (int i=0; i 0 && style != font.style) { style |= font.style; font = fontFamily.getFont(style); if (font == null) { font = fontFamily.getClosestStyle(style); } } } return font; } private synchronized HashMap getFullNameToFileMap() { if (fontToFileMap == null) { pathDirs = getPlatformFontDirs(noType1Font); fontToFileMap = new HashMap<>(100); fontToFamilyNameMap = new HashMap<>(100); familyToFontListMap = new HashMap<>(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 fontNames = new ArrayList<>(); for (ArrayList 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 registeredFontFiles) { if (getFullNameToFileMap().size() == 0) { return; } for (String file : fontToFileMap.values()) { registerFontFile(file); } } public boolean getFamilyNamesFromPlatform(TreeMap 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 = AccessController.doPrivileged( new PrivilegedAction() { 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 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 fontNameCache = new ConcurrentHashMap<>(); /* * 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); /* 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. */ Font2D 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 = findFontFromPlatform(lowerCaseName, 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 font from C:\Windows\Fonts * if someone has installed a font with the same name * in the JRE. */ 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; } } /* 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) { Hashtable familyTable = createdByFamilyName; Hashtable nameTable = createdByFullName; 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 tmpFontFiles = null; private int createdFontCount = 0; public Font2D[] createFont2D(File fontFile, int fontFormat, boolean all, boolean isCopy, CreatedFontTracker tracker) throws FontFormatException { List fList = new ArrayList<>(); int cnt = 1; String fontFilePath = fontFile.getPath(); FileFont font2D = null; final File fFile = fontFile; final CreatedFontTracker _tracker = tracker; boolean weakRefs = false; int maxStrikes = 0; synchronized (this) { if (createdFontCount < maxSoftRefCnt) { createdFontCount++; } else { weakRefs = true; maxStrikes = 10; } } try { switch (fontFormat) { case Font.TRUETYPE_FONT: font2D = new TrueTypeFont(fontFilePath, null, 0, true); font2D.setUseWeakRefs(weakRefs, maxStrikes); fList.add(font2D); if (!all) { break; } cnt = ((TrueTypeFont)font2D).getFontCount(); int index = 1; while (index < cnt) { font2D = new TrueTypeFont(fontFilePath, null, index++, true); font2D.setUseWeakRefs(weakRefs, maxStrikes); fList.add(font2D); } break; case Font.TYPE1_FONT: font2D = new Type1Font(fontFilePath, null, isCopy); font2D.setUseWeakRefs(weakRefs, maxStrikes); fList.add(font2D); break; default: throw new FontFormatException("Unrecognised Font Format"); } } catch (FontFormatException e) { if (isCopy) { AccessController.doPrivileged(new PrivilegedAction() { public Void 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(); } tmpFontFiles.add(fontFile); if (fileCloser == null) { final Runnable fileCloserRunnable = new Runnable() { public void run() { AccessController.doPrivileged(new PrivilegedAction() { public Void 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) () -> { 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[] 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 tmp = (Map.Entry)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 Font2D.FONT_CONFIG_RANK) { compFonts[i].replaceComponentFont(oldFont, newFont); } } } private synchronized void loadLocaleNames() { if (localeFullNamesToFont != null) { return; } localeFullNamesToFont = new HashMap<>(); Font2D[] fonts = getRegisteredFonts(); for (int i=0; i installedNames = null; private static HashSet getInstalledNames() { if (installedNames == null) { Locale l = getSystemStartupLocale(); SunFontManager fontManager = SunFontManager.getInstance(); String[] installedFamilies = fontManager.getInstalledFontFamilyNames(l); Font[] installedFonts = fontManager.getAllInstalledFonts(); HashSet names = new HashSet<>(); for (int i=0; i createdByFamilyName; private Hashtable createdByFullName; private boolean fontsAreRegistered = 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 (regFamilyLock) { if (createdByFamilyName == null) { createdByFamilyName = new Hashtable(); createdByFullName = new Hashtable(); } } 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 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 familyTable = createdByFamilyName; Hashtable fullNameTable = createdByFullName; fontsAreRegistered = 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 getCreatedFontFamilyNames() { Hashtable familyTable; if (fontsAreRegistered) { familyTable = createdByFamilyName; } else { return null; } Locale l = getSystemStartupLocale(); synchronized (familyTable) { TreeMap map = new TreeMap(); 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 nameTable; if (fontsAreRegistered) { nameTable = createdByFullName; } 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 pathList = new ArrayList<>(); 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; } 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(); AccessController.doPrivileged(new PrivilegedAction() { public Void 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() { return defaultFontFileName; } /** * 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() { 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"); } AccessController.doPrivileged(new PrivilegedAction() { public Void 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 altNameCache) { if (FontUtilities.isLogging()) { FontUtilities.getLogger() .info("Initialising composite fonts"); } int numCoreFonts = fontConfig.getNumberCoreFonts(); String[] fcFonts = fontConfig.getPlatformFontNames(); for (int f=0; ffilename * 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(); } 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 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 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 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 familyNames = new TreeMap(); // 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 familyNames, Locale requestedLocale) { } public void register1dot0Fonts() { AccessController.doPrivileged(new PrivilegedAction() { public Void 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 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 = AccessController.doPrivileged(new PrivilegedAction() { public Locale 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= 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); } }