| /* | |
| * Copyright (c) 2008, 2025, 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.lang.ref.SoftReference; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import javax.swing.plaf.FontUIResource; | |
| import sun.awt.OSInfo; | |
| import sun.util.logging.PlatformLogger; | |
| /** | |
| * A collection of utility methods. | |
| */ | |
| public final class FontUtilities { | |
| public static boolean isLinux; | |
| public static boolean isMacOSX; | |
| public static boolean isMacOSX14; | |
| 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 { | |
| initStatic(); | |
| } | |
| @SuppressWarnings("deprecation") // PlatformLogger.setLevel is deprecated. | |
| private static void initStatic() { | |
| isLinux = OSInfo.getOSType() == OSInfo.OSType.LINUX; | |
| isMacOSX = OSInfo.getOSType() == OSInfo.OSType.MACOSX; | |
| if (isMacOSX) { | |
| // os.version has values like 10.13.6, 10.14.6 | |
| // If it is not positively recognised as 10.13 or less, | |
| // assume it means 10.14 or some later version. | |
| isMacOSX14 = true; | |
| String version = System.getProperty("os.version", ""); | |
| if (version.startsWith("10.")) { | |
| version = version.substring(3); | |
| int periodIndex = version.indexOf('.'); | |
| if (periodIndex != -1) { | |
| version = version.substring(0, periodIndex); | |
| } | |
| try { | |
| int v = Integer.parseInt(version); | |
| isMacOSX14 = (v >= 14); | |
| } catch (NumberFormatException e) { | |
| } | |
| } | |
| } | |
| /* 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 = OSInfo.getOSType() == OSInfo.OSType.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); | |
| } | |
| logging = logger.isEnabled(); | |
| } | |
| } | |
| /** | |
| * 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 assumed 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 < 0x10A0) { // U+1000 - U+109F Myanmar | |
| 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; | |
| } | |
| /** | |
| * <p>Checks whether or not the specified codepoint is ignorable by default, per the | |
| * Unicode standard (with exceptions for HarfBuzz and Uniscribe compatibility). | |
| * The codepoints categorized as ignorable should remain in sync with HarfBuzz's | |
| * is_default_ignorable() in hb-unicode.hh. | |
| * | |
| * <p>See the original Unicode list of default-ignorable codepoints | |
| * <a href="https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt">here</a>: | |
| * | |
| * <pre> | |
| * 00AD # Cf SOFT HYPHEN | |
| * 034F # Mn COMBINING GRAPHEME JOINER | |
| * 061C # Cf ARABIC LETTER MARK | |
| * 115F..1160 # Lo [2] HANGUL CHOSEONG FILLER..HANGUL JUNGSEONG FILLER | |
| * 17B4..17B5 # Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA | |
| * 180B..180D # Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE | |
| * 180E # Cf MONGOLIAN VOWEL SEPARATOR | |
| * 180F # Mn MONGOLIAN FREE VARIATION SELECTOR FOUR | |
| * 200B..200F # Cf [5] ZERO WIDTH SPACE..RIGHT-TO-LEFT MARK | |
| * 202A..202E # Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE | |
| * 2060..2064 # Cf [5] WORD JOINER..INVISIBLE PLUS | |
| * 2065 # Cn <reserved-2065> | |
| * 2066..206F # Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES | |
| * 3164 # Lo HANGUL FILLER | |
| * FE00..FE0F # Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16 | |
| * FEFF # Cf ZERO WIDTH NO-BREAK SPACE | |
| * FFA0 # Lo HALFWIDTH HANGUL FILLER | |
| * FFF0..FFF8 # Cn [9] <reserved-FFF0>..<reserved-FFF8> | |
| * 1BCA0..1BCA3 # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP | |
| * 1D173..1D17A # Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE | |
| * E0000 # Cn <reserved-E0000> | |
| * E0001 # Cf LANGUAGE TAG | |
| * E0002..E001F # Cn [30] <reserved-E0002>..<reserved-E001F> | |
| * E0020..E007F # Cf [96] TAG SPACE..CANCEL TAG | |
| * E0080..E00FF # Cn [128] <reserved-E0080>..<reserved-E00FF> | |
| * E0100..E01EF # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 | |
| * E01F0..E0FFF # Cn [3600] <reserved-E01F0>..<reserved-E0FFF> | |
| * </pre> | |
| */ | |
| public static boolean isDefaultIgnorable(int charCode) { | |
| if (charCode < 0x00AD) { | |
| return false; | |
| } | |
| int plane = charCode >> 16; | |
| if (plane == 0) { | |
| // basic multilingual plane (BMP) | |
| int page = charCode >> 8; | |
| switch (page) { | |
| case 0x00: return (charCode == 0x00AD); | |
| case 0x03: return (charCode == 0x034F); | |
| case 0x06: return (charCode == 0x061C); | |
| case 0x17: return (charCode >= 0x17B4 && charCode <= 0x17B5); | |
| case 0x18: return (charCode >= 0x180B && charCode <= 0x180E); | |
| case 0x20: return (charCode >= 0x200B && charCode <= 0x200F) || | |
| (charCode >= 0x202A && charCode <= 0x202E) || | |
| (charCode >= 0x2060 && charCode <= 0x206F); | |
| case 0xFE: return (charCode >= 0xFE00 && charCode <= 0xFE0F) || | |
| (charCode == 0xFEFF); | |
| case 0xFF: return (charCode >= 0xFFF0 && charCode <= 0xFFF8); | |
| default: return false; | |
| } | |
| } else { | |
| // other planes | |
| switch (plane) { | |
| case 0x01: return (charCode >= 0x1D173 && charCode <= 0x1D17A); | |
| case 0x0E: return (charCode >= 0xE0000 && charCode <= 0xE0FFF); | |
| default: return false; | |
| } | |
| } | |
| } | |
| /** | |
| * <p>Checks whether or not the specified codepoint is whitespace which is | |
| * ignorable at the shaping stage of text rendering. These ignorable | |
| * whitespace characters should be used prior to text shaping and | |
| * rendering to determine the position of the text, but are not themselves | |
| * rendered. | |
| * | |
| * <p>Includes 0x0009 (horizontal tab / TAB), 0x000A (line feed / LF), | |
| * 0x000B (vertical tab / VT), 0x000C (form feed / FF), | |
| * 0x000D (carriage return / CR), 0x0085 (next line / NEL), | |
| * 0x2028 (line separator / LS), 0x2029 (paragraph separator / PS). | |
| * | |
| * @param ch the codepoint to check | |
| * @return whether the specified codepoint is ignorable whitespace | |
| */ | |
| public static boolean isIgnorableWhitespace(int ch) { | |
| return (ch >= 0x0009 && ch <= 0x000d) | |
| || ch == 0x0085 | |
| || ch == 0x2028 | |
| || ch == 0x2029; | |
| } | |
| public static PlatformLogger getLogger() { | |
| return logger; | |
| } | |
| public static boolean isLogging() { | |
| return logging; | |
| } | |
| public static boolean debugFonts() { | |
| return debugFonts; | |
| } | |
| public static void logWarning(String s) { | |
| getLogger().warning(s); | |
| } | |
| public static void logInfo(String s) { | |
| getLogger().info(s); | |
| } | |
| public static void logSevere(String s) { | |
| getLogger().severe(s); | |
| } | |
| // 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<ConcurrentHashMap<PhysicalFont, CompositeFont>> | |
| 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 instanceof CompositeFont dialog2D)) { | |
| return fuir; | |
| } | |
| PhysicalFont physicalFont = (PhysicalFont)font2D; | |
| ConcurrentHashMap<PhysicalFont, CompositeFont> compMap = compMapRef.get(); | |
| if (compMap == null) { // Its been collected. | |
| compMap = new ConcurrentHashMap<PhysicalFont, CompositeFont>(); | |
| 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; | |
| } | |
| } | |
| } |