/* * Copyright (c) 2008, 2018, 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.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.lang.ref.SoftReference; import java.util.concurrent.ConcurrentHashMap; import java.security.AccessController; import java.security.PrivilegedAction; import javax.swing.plaf.FontUIResource; import sun.util.logging.PlatformLogger; /** * A collection of utility methods. */ public final class FontUtilities { public static boolean isSolaris; public static boolean isLinux; public static boolean isMacOSX; public static boolean isAIX; public static boolean useJDKScaler; public static boolean isWindows; private static boolean debugFonts = false; private static PlatformLogger logger = null; private static boolean logging; // This static initializer block figures out the OS constants. static { AccessController.doPrivileged(new PrivilegedAction() { @SuppressWarnings("deprecation") // PlatformLogger.setLevel is deprecated. @Override public Object run() { String osName = System.getProperty("os.name", "unknownOS"); isSolaris = osName.startsWith("SunOS"); isLinux = osName.startsWith("Linux"); isMacOSX = osName.contains("OS X"); // TODO: MacOSX isAIX = osName.startsWith("AIX"); /* If set to "jdk", use the JDK's scaler rather than * the platform one. This may be a no-op on platforms where * JDK has been configured so that it always relies on the * platform scaler. The principal case where it has an * effect is that on Windows, 2D will never use GDI. */ String scalerStr = System.getProperty("sun.java2d.font.scaler"); if (scalerStr != null) { useJDKScaler = "jdk".equals(scalerStr); } else { useJDKScaler = false; } isWindows = osName.startsWith("Windows"); String debugLevel = System.getProperty("sun.java2d.debugfonts"); if (debugLevel != null && !debugLevel.equals("false")) { debugFonts = true; logger = PlatformLogger.getLogger("sun.java2d"); if (debugLevel.equals("warning")) { logger.setLevel(PlatformLogger.Level.WARNING); } else if (debugLevel.equals("severe")) { logger.setLevel(PlatformLogger.Level.SEVERE); } } if (debugFonts) { logger = PlatformLogger.getLogger("sun.java2d"); logging = logger.isEnabled(); } return null; } }); } /** * Referenced by code in the JDK which wants to test for the * minimum char code for which layout may be required. * Note that even basic latin text can benefit from ligatures, * eg "ffi" but we presently apply those only if explicitly * requested with TextAttribute.LIGATURES_ON. * The value here indicates the lowest char code for which failing * to invoke layout would prevent acceptable rendering. */ public static final int MIN_LAYOUT_CHARCODE = 0x0300; /** * Referenced by code in the JDK which wants to test for the * maximum char code for which layout may be required. * Note this does not account for supplementary characters * where the caller interprets 'layout' to mean any case where * one 'char' (ie the java type char) does not map to one glyph */ public static final int MAX_LAYOUT_CHARCODE = 0x206F; /** * Calls the private getFont2D() method in java.awt.Font objects. * * @param font the font object to call * * @return the Font2D object returned by Font.getFont2D() */ public static Font2D getFont2D(Font font) { return FontAccess.getFontAccess().getFont2D(font); } /** * Return true if there any characters which would trigger layout. * This method considers supplementary characters to be simple, * since we do not presently invoke layout on any code points in * outside the BMP. */ public static boolean isComplexScript(char [] chs, int start, int limit) { for (int i = start; i < limit; i++) { if (chs[i] < MIN_LAYOUT_CHARCODE) { continue; } else if (isComplexCharCode(chs[i])) { return true; } } return false; } /** * If there is anything in the text which triggers a case * where char->glyph does not map 1:1 in straightforward * left->right ordering, then this method returns true. * Scripts which might require it but are not treated as such * due to JDK implementations will not return true. * ie a 'true' return is an indication of the treatment by * the implementation. * Whether supplementary characters should be considered is dependent * on the needs of the caller. Since this method accepts the 'char' type * then such chars are always represented by a pair. From a rendering * perspective these will all (in the cases I know of) still be one * unicode character -> one glyph. But if a caller is using this to * discover any case where it cannot make naive assumptions about * the number of chars, and how to index through them, then it may * need the option to have a 'true' return in such a case. */ public static boolean isComplexText(char [] chs, int start, int limit) { for (int i = start; i < limit; i++) { if (chs[i] < MIN_LAYOUT_CHARCODE) { continue; } else if (isNonSimpleChar(chs[i])) { return true; } } return false; } /* This is almost the same as the method above, except it takes a * char which means it may include undecoded surrogate pairs. * The distinction is made so that code which needs to identify all * cases in which we do not have a simple mapping from * char->unicode character->glyph can be identified. * For example measurement cannot simply sum advances of 'chars', * the caret in editable text cannot advance one 'char' at a time, etc. * These callers really are asking for more than whether 'layout' * needs to be run, they need to know if they can assume 1->1 * char->glyph mapping. */ public static boolean isNonSimpleChar(char ch) { return isComplexCharCode(ch) || (ch >= CharToGlyphMapper.HI_SURROGATE_START && ch <= CharToGlyphMapper.LO_SURROGATE_END); } /* If the character code falls into any of a number of unicode ranges * where we know that simple left->right layout mapping chars to glyphs * 1:1 and accumulating advances is going to produce incorrect results, * we want to know this so the caller can use a more intelligent layout * approach. A caller who cares about optimum performance may want to * check the first case and skip the method call if its in that range. * Although there's a lot of tests in here, knowing you can skip * CTL saves a great deal more. The rest of the checks are ordered * so that rather than checking explicitly if (>= start & <= end) * which would mean all ranges would need to be checked so be sure * CTL is not needed, the method returns as soon as it recognises * the code point is outside of a CTL ranges. * NOTE: Since this method accepts an 'int' it is asssumed to properly * represent a CHARACTER. ie it assumes the caller has already * converted surrogate pairs into supplementary characters, and so * can handle this case and doesn't need to be told such a case is * 'complex'. */ public static boolean isComplexCharCode(int code) { if (code < MIN_LAYOUT_CHARCODE || code > MAX_LAYOUT_CHARCODE) { return false; } else if (code <= 0x036f) { // Trigger layout for combining diacriticals 0x0300->0x036f return true; } else if (code < 0x0590) { // No automatic layout for Greek, Cyrillic, Armenian. return false; } else if (code <= 0x06ff) { // Hebrew 0590 - 05ff // Arabic 0600 - 06ff return true; } else if (code < 0x0900) { return false; // Syriac and Thaana } else if (code <= 0x0e7f) { // if Indic, assume shaping for conjuncts, reordering: // 0900 - 097F Devanagari // 0980 - 09FF Bengali // 0A00 - 0A7F Gurmukhi // 0A80 - 0AFF Gujarati // 0B00 - 0B7F Oriya // 0B80 - 0BFF Tamil // 0C00 - 0C7F Telugu // 0C80 - 0CFF Kannada // 0D00 - 0D7F Malayalam // 0D80 - 0DFF Sinhala // 0E00 - 0E7F if Thai, assume shaping for vowel, tone marks return true; } else if (code < 0x0f00) { return false; } else if (code <= 0x0fff) { // U+0F00 - U+0FFF Tibetan return true; } else if (code < 0x1100) { return false; } else if (code < 0x11ff) { // U+1100 - U+11FF Old Hangul return true; } else if (code < 0x1780) { return false; } else if (code <= 0x17ff) { // 1780 - 17FF Khmer return true; } else if (code < 0x200c) { return false; } else if (code <= 0x200d) { // zwj or zwnj return true; } else if (code >= 0x202a && code <= 0x202e) { // directional control return true; } else if (code >= 0x206a && code <= 0x206f) { // directional control return true; } return false; } public static PlatformLogger getLogger() { return logger; } public static boolean isLogging() { return logging; } public static boolean debugFonts() { return debugFonts; } // The following methods are used by Swing. /* Revise the implementation to in fact mean "font is a composite font. * This ensures that Swing components will always benefit from the * fall back fonts */ public static boolean fontSupportsDefaultEncoding(Font font) { return getFont2D(font) instanceof CompositeFont; } /** * This method is provided for internal and exclusive use by Swing. * * It may be used in conjunction with fontSupportsDefaultEncoding(Font) * In the event that a desktop properties font doesn't directly * support the default encoding, (ie because the host OS supports * adding support for the current locale automatically for native apps), * then Swing calls this method to get a font which uses the specified * font for the code points it covers, but also supports this locale * just as the standard composite fonts do. * Note: this will over-ride any setting where an application * specifies it prefers locale specific composite fonts. * The logic for this, is that this method is used only where the user or * application has specified that the native L&F be used, and that * we should honour that request to use the same font as native apps use. * * The behaviour of this method is to construct a new composite * Font object that uses the specified physical font as its first * component, and adds all the components of "dialog" as fall back * components. * The method currently assumes that only the size and style attributes * are set on the specified font. It doesn't copy the font transform or * other attributes because they aren't set on a font created from * the desktop. This will need to be fixed if use is broadened. * * Operations such as Font.deriveFont will work properly on the * font returned by this method for deriving a different point size. * Additionally it tries to support a different style by calling * getNewComposite() below. That also supports replacing slot zero * with a different physical font but that is expected to be "rare". * Deriving with a different style is needed because its been shown * that some applications try to do this for Swing FontUIResources. * Also operations such as new Font(font.getFontName(..), Font.PLAIN, 14); * will NOT yield the same result, as the new underlying CompositeFont * cannot be "looked up" in the font registry. * This returns a FontUIResource as that is the Font sub-class needed * by Swing. * Suggested usage is something like : * FontUIResource fuir; * Font desktopFont = getDesktopFont(..); * if (FontManager.fontSupportsDefaultEncoding(desktopFont)) { * fuir = new FontUIResource(desktopFont); * } else { * fuir = FontManager.getCompositeFontUIResource(desktopFont); * } * return fuir; */ private static volatile SoftReference> compMapRef = new SoftReference<>(null); public static FontUIResource getCompositeFontUIResource(Font font) { FontUIResource fuir = new FontUIResource(font); Font2D font2D = FontUtilities.getFont2D(font); if (!(font2D instanceof PhysicalFont)) { /* Swing should only be calling this when a font is obtained * from desktop properties, so should generally be a physical font, * an exception might be for names like "MS Serif" which are * automatically mapped to "Serif", so there's no need to do * anything special in that case. But note that suggested usage * is first to call fontSupportsDefaultEncoding(Font) and this * method should not be called if that were to return true. */ return fuir; } FontManager fm = FontManagerFactory.getInstance(); Font2D dialog = fm.findFont2D("dialog", font.getStyle(), FontManager.NO_FALLBACK); // Should never be null, but MACOSX fonts are not CompositeFonts if (dialog == null || !(dialog instanceof CompositeFont)) { return fuir; } CompositeFont dialog2D = (CompositeFont)dialog; PhysicalFont physicalFont = (PhysicalFont)font2D; ConcurrentHashMap compMap = compMapRef.get(); if (compMap == null) { // Its been collected. compMap = new ConcurrentHashMap(); compMapRef = new SoftReference<>(compMap); } CompositeFont compFont = compMap.get(physicalFont); if (compFont == null) { compFont = new CompositeFont(physicalFont, dialog2D); compMap.put(physicalFont, compFont); } FontAccess.getFontAccess().setFont2D(fuir, compFont.handle); /* marking this as a created font is needed as only created fonts * copy their creator's handles. */ FontAccess.getFontAccess().setCreatedFont(fuir); return fuir; } /* A small "map" from GTK/fontconfig names to the equivalent JDK * logical font name. */ private static final String[][] nameMap = { {"sans", "sansserif"}, {"sans-serif", "sansserif"}, {"serif", "serif"}, {"monospace", "monospaced"} }; public static String mapFcName(String name) { for (int i = 0; i < nameMap.length; i++) { if (name.equals(nameMap[i][0])) { return nameMap[i][1]; } } return null; } /* This is called by Swing passing in a fontconfig family name * such as "sans". In return Swing gets a FontUIResource instance * that has queried fontconfig to resolve the font(s) used for this. * Fontconfig will if asked return a list of fonts to give the largest * possible code point coverage. * For now we use only the first font returned by fontconfig, and * back it up with the most closely matching JDK logical font. * Essentially this means pre-pending what we return now with fontconfig's * preferred physical font. This could lead to some duplication in cases, * if we already included that font later. We probably should remove such * duplicates, but it is not a significant problem. It can be addressed * later as part of creating a Composite which uses more of the * same fonts as fontconfig. At that time we also should pay more * attention to the special rendering instructions fontconfig returns, * such as whether we should prefer embedded bitmaps over antialiasing. * There's no way to express that via a Font at present. */ public static FontUIResource getFontConfigFUIR(String fcFamily, int style, int size) { String mapped = mapFcName(fcFamily); if (mapped == null) { mapped = "sansserif"; } FontUIResource fuir; FontManager fm = FontManagerFactory.getInstance(); if (fm instanceof SunFontManager) { SunFontManager sfm = (SunFontManager) fm; fuir = sfm.getFontConfigFUIR(mapped, style, size); } else { fuir = new FontUIResource(mapped, style, size); } return fuir; } /** * Used by windows printing to assess if a font is likely to * be layout compatible with JDK * TrueType fonts should be, but if they have no GPOS table, * but do have a GSUB table, then they are probably older * fonts GDI handles differently. */ public static boolean textLayoutIsCompatible(Font font) { Font2D font2D = getFont2D(font); if (font2D instanceof TrueTypeFont) { TrueTypeFont ttf = (TrueTypeFont) font2D; return ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null || ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null; } else { return false; } } }