1 /* 2 * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.font; 27 28 import java.awt.Font; 29 import java.lang.ref.SoftReference; 30 import java.util.concurrent.ConcurrentHashMap; 31 import java.security.AccessController; 32 33 import java.security.PrivilegedAction; 34 import javax.swing.plaf.FontUIResource; 35 36 import sun.util.logging.PlatformLogger; 37 38 /** 39 * A collection of utility methods. 40 */ 41 @SuppressWarnings("removal") 42 public final class FontUtilities { 43 44 public static boolean isLinux; 45 46 public static boolean isMacOSX; 47 public static boolean isMacOSX14; 48 49 public static boolean isBSD; 50 51 public static boolean useJDKScaler; 52 53 public static boolean isWindows; 54 55 private static boolean debugFonts = false; 56 private static PlatformLogger logger = null; 57 private static boolean logging; 58 59 // This static initializer block figures out the OS constants. 60 static { 61 AccessController.doPrivileged(new PrivilegedAction<Object>() { @SuppressWarnings(R) @Override public Object run() { String osName = System.getProperty(R, R); isLinux = osName.startsWith(R); isBSD = osName.endsWith(R); isMacOSX = osName.contains(R); if (isMacOSX) { isMacOSX14 = true; String version = System.getProperty(R, R); if (version.startsWith(R)) { version = version.substring(3); int periodIndex = version.indexOf(B); if (periodIndex != -1) { version = version.substring(0, periodIndex); } try { int v = Integer.parseInt(version); isMacOSX14 = (v >= 14); } catch (NumberFormatException e) { } } } String scalerStr = System.getProperty(R); if (scalerStr != null) { useJDKScaler = R.equals(scalerStr); } else { useJDKScaler = false; } isWindows = osName.startsWith(R); String debugLevel = System.getProperty(R); if (debugLevel != null && !debugLevel.equals(R)) { debugFonts = true; logger = PlatformLogger.getLogger(R); if (debugLevel.equals(R)) { logger.setLevel(PlatformLogger.Level.WARNING); } else if (debugLevel.equals(R)) { logger.setLevel(PlatformLogger.Level.SEVERE); } logging = logger.isEnabled(); } return null; } })62 AccessController.doPrivileged(new PrivilegedAction<Object>() { 63 @SuppressWarnings("deprecation") // PlatformLogger.setLevel is deprecated. 64 @Override 65 public Object run() { 66 String osName = System.getProperty("os.name", "unknownOS"); 67 68 isLinux = osName.startsWith("Linux"); 69 70 isBSD = osName.endsWith("BSD"); 71 72 isMacOSX = osName.contains("OS X"); // TODO: MacOSX 73 if (isMacOSX) { 74 // os.version has values like 10.13.6, 10.14.6 75 // If it is not positively recognised as 10.13 or less, 76 // assume it means 10.14 or some later version. 77 isMacOSX14 = true; 78 String version = System.getProperty("os.version", ""); 79 if (version.startsWith("10.")) { 80 version = version.substring(3); 81 int periodIndex = version.indexOf('.'); 82 if (periodIndex != -1) { 83 version = version.substring(0, periodIndex); 84 } 85 try { 86 int v = Integer.parseInt(version); 87 isMacOSX14 = (v >= 14); 88 } catch (NumberFormatException e) { 89 } 90 } 91 } 92 /* If set to "jdk", use the JDK's scaler rather than 93 * the platform one. This may be a no-op on platforms where 94 * JDK has been configured so that it always relies on the 95 * platform scaler. The principal case where it has an 96 * effect is that on Windows, 2D will never use GDI. 97 */ 98 String scalerStr = System.getProperty("sun.java2d.font.scaler"); 99 if (scalerStr != null) { 100 useJDKScaler = "jdk".equals(scalerStr); 101 } else { 102 useJDKScaler = false; 103 } 104 isWindows = osName.startsWith("Windows"); 105 String debugLevel = 106 System.getProperty("sun.java2d.debugfonts"); 107 108 if (debugLevel != null && !debugLevel.equals("false")) { 109 debugFonts = true; 110 logger = PlatformLogger.getLogger("sun.java2d"); 111 if (debugLevel.equals("warning")) { 112 logger.setLevel(PlatformLogger.Level.WARNING); 113 } else if (debugLevel.equals("severe")) { 114 logger.setLevel(PlatformLogger.Level.SEVERE); 115 } 116 logging = logger.isEnabled(); 117 } 118 119 return null; 120 } 121 }); 122 } 123 124 /** 125 * Referenced by code in the JDK which wants to test for the 126 * minimum char code for which layout may be required. 127 * Note that even basic latin text can benefit from ligatures, 128 * eg "ffi" but we presently apply those only if explicitly 129 * requested with TextAttribute.LIGATURES_ON. 130 * The value here indicates the lowest char code for which failing 131 * to invoke layout would prevent acceptable rendering. 132 */ 133 public static final int MIN_LAYOUT_CHARCODE = 0x0300; 134 135 /** 136 * Referenced by code in the JDK which wants to test for the 137 * maximum char code for which layout may be required. 138 * Note this does not account for supplementary characters 139 * where the caller interprets 'layout' to mean any case where 140 * one 'char' (ie the java type char) does not map to one glyph 141 */ 142 public static final int MAX_LAYOUT_CHARCODE = 0x206F; 143 144 /** 145 * Calls the private getFont2D() method in java.awt.Font objects. 146 * 147 * @param font the font object to call 148 * 149 * @return the Font2D object returned by Font.getFont2D() 150 */ getFont2D(Font font)151 public static Font2D getFont2D(Font font) { 152 return FontAccess.getFontAccess().getFont2D(font); 153 } 154 155 /** 156 * Return true if there any characters which would trigger layout. 157 * This method considers supplementary characters to be simple, 158 * since we do not presently invoke layout on any code points in 159 * outside the BMP. 160 */ isComplexScript(char [] chs, int start, int limit)161 public static boolean isComplexScript(char [] chs, int start, int limit) { 162 163 for (int i = start; i < limit; i++) { 164 if (chs[i] < MIN_LAYOUT_CHARCODE) { 165 continue; 166 } 167 else if (isComplexCharCode(chs[i])) { 168 return true; 169 } 170 } 171 return false; 172 } 173 174 /** 175 * If there is anything in the text which triggers a case 176 * where char->glyph does not map 1:1 in straightforward 177 * left->right ordering, then this method returns true. 178 * Scripts which might require it but are not treated as such 179 * due to JDK implementations will not return true. 180 * ie a 'true' return is an indication of the treatment by 181 * the implementation. 182 * Whether supplementary characters should be considered is dependent 183 * on the needs of the caller. Since this method accepts the 'char' type 184 * then such chars are always represented by a pair. From a rendering 185 * perspective these will all (in the cases I know of) still be one 186 * unicode character -> one glyph. But if a caller is using this to 187 * discover any case where it cannot make naive assumptions about 188 * the number of chars, and how to index through them, then it may 189 * need the option to have a 'true' return in such a case. 190 */ isComplexText(char [] chs, int start, int limit)191 public static boolean isComplexText(char [] chs, int start, int limit) { 192 193 for (int i = start; i < limit; i++) { 194 if (chs[i] < MIN_LAYOUT_CHARCODE) { 195 continue; 196 } 197 else if (isNonSimpleChar(chs[i])) { 198 return true; 199 } 200 } 201 return false; 202 } 203 204 /* This is almost the same as the method above, except it takes a 205 * char which means it may include undecoded surrogate pairs. 206 * The distinction is made so that code which needs to identify all 207 * cases in which we do not have a simple mapping from 208 * char->unicode character->glyph can be identified. 209 * For example measurement cannot simply sum advances of 'chars', 210 * the caret in editable text cannot advance one 'char' at a time, etc. 211 * These callers really are asking for more than whether 'layout' 212 * needs to be run, they need to know if they can assume 1->1 213 * char->glyph mapping. 214 */ isNonSimpleChar(char ch)215 public static boolean isNonSimpleChar(char ch) { 216 return 217 isComplexCharCode(ch) || 218 (ch >= CharToGlyphMapper.HI_SURROGATE_START && 219 ch <= CharToGlyphMapper.LO_SURROGATE_END); 220 } 221 222 /* If the character code falls into any of a number of unicode ranges 223 * where we know that simple left->right layout mapping chars to glyphs 224 * 1:1 and accumulating advances is going to produce incorrect results, 225 * we want to know this so the caller can use a more intelligent layout 226 * approach. A caller who cares about optimum performance may want to 227 * check the first case and skip the method call if its in that range. 228 * Although there's a lot of tests in here, knowing you can skip 229 * CTL saves a great deal more. The rest of the checks are ordered 230 * so that rather than checking explicitly if (>= start & <= end) 231 * which would mean all ranges would need to be checked so be sure 232 * CTL is not needed, the method returns as soon as it recognises 233 * the code point is outside of a CTL ranges. 234 * NOTE: Since this method accepts an 'int' it is asssumed to properly 235 * represent a CHARACTER. ie it assumes the caller has already 236 * converted surrogate pairs into supplementary characters, and so 237 * can handle this case and doesn't need to be told such a case is 238 * 'complex'. 239 */ isComplexCharCode(int code)240 public static boolean isComplexCharCode(int code) { 241 242 if (code < MIN_LAYOUT_CHARCODE || code > MAX_LAYOUT_CHARCODE) { 243 return false; 244 } 245 else if (code <= 0x036f) { 246 // Trigger layout for combining diacriticals 0x0300->0x036f 247 return true; 248 } 249 else if (code < 0x0590) { 250 // No automatic layout for Greek, Cyrillic, Armenian. 251 return false; 252 } 253 else if (code <= 0x06ff) { 254 // Hebrew 0590 - 05ff 255 // Arabic 0600 - 06ff 256 return true; 257 } 258 else if (code < 0x0900) { 259 return false; // Syriac and Thaana 260 } 261 else if (code <= 0x0e7f) { 262 // if Indic, assume shaping for conjuncts, reordering: 263 // 0900 - 097F Devanagari 264 // 0980 - 09FF Bengali 265 // 0A00 - 0A7F Gurmukhi 266 // 0A80 - 0AFF Gujarati 267 // 0B00 - 0B7F Oriya 268 // 0B80 - 0BFF Tamil 269 // 0C00 - 0C7F Telugu 270 // 0C80 - 0CFF Kannada 271 // 0D00 - 0D7F Malayalam 272 // 0D80 - 0DFF Sinhala 273 // 0E00 - 0E7F if Thai, assume shaping for vowel, tone marks 274 return true; 275 } 276 else if (code < 0x0f00) { 277 return false; 278 } 279 else if (code <= 0x0fff) { // U+0F00 - U+0FFF Tibetan 280 return true; 281 } 282 else if (code < 0x10A0) { // U+1000 - U+109F Myanmar 283 return true; 284 } 285 else if (code < 0x1100) { 286 return false; 287 } 288 else if (code < 0x11ff) { // U+1100 - U+11FF Old Hangul 289 return true; 290 } 291 else if (code < 0x1780) { 292 return false; 293 } 294 else if (code <= 0x17ff) { // 1780 - 17FF Khmer 295 return true; 296 } 297 else if (code < 0x200c) { 298 return false; 299 } 300 else if (code <= 0x200d) { // zwj or zwnj 301 return true; 302 } 303 else if (code >= 0x202a && code <= 0x202e) { // directional control 304 return true; 305 } 306 else if (code >= 0x206a && code <= 0x206f) { // directional control 307 return true; 308 } 309 return false; 310 } 311 getLogger()312 public static PlatformLogger getLogger() { 313 return logger; 314 } 315 isLogging()316 public static boolean isLogging() { 317 return logging; 318 } 319 debugFonts()320 public static boolean debugFonts() { 321 return debugFonts; 322 } 323 logWarning(String s)324 public static void logWarning(String s) { 325 getLogger().warning(s); 326 } 327 logInfo(String s)328 public static void logInfo(String s) { 329 getLogger().info(s); 330 } 331 logSevere(String s)332 public static void logSevere(String s) { 333 getLogger().severe(s); 334 } 335 336 // The following methods are used by Swing. 337 338 /* Revise the implementation to in fact mean "font is a composite font. 339 * This ensures that Swing components will always benefit from the 340 * fall back fonts 341 */ fontSupportsDefaultEncoding(Font font)342 public static boolean fontSupportsDefaultEncoding(Font font) { 343 return getFont2D(font) instanceof CompositeFont; 344 } 345 346 /** 347 * This method is provided for internal and exclusive use by Swing. 348 * 349 * It may be used in conjunction with fontSupportsDefaultEncoding(Font) 350 * In the event that a desktop properties font doesn't directly 351 * support the default encoding, (ie because the host OS supports 352 * adding support for the current locale automatically for native apps), 353 * then Swing calls this method to get a font which uses the specified 354 * font for the code points it covers, but also supports this locale 355 * just as the standard composite fonts do. 356 * Note: this will over-ride any setting where an application 357 * specifies it prefers locale specific composite fonts. 358 * The logic for this, is that this method is used only where the user or 359 * application has specified that the native L&F be used, and that 360 * we should honour that request to use the same font as native apps use. 361 * 362 * The behaviour of this method is to construct a new composite 363 * Font object that uses the specified physical font as its first 364 * component, and adds all the components of "dialog" as fall back 365 * components. 366 * The method currently assumes that only the size and style attributes 367 * are set on the specified font. It doesn't copy the font transform or 368 * other attributes because they aren't set on a font created from 369 * the desktop. This will need to be fixed if use is broadened. 370 * 371 * Operations such as Font.deriveFont will work properly on the 372 * font returned by this method for deriving a different point size. 373 * Additionally it tries to support a different style by calling 374 * getNewComposite() below. That also supports replacing slot zero 375 * with a different physical font but that is expected to be "rare". 376 * Deriving with a different style is needed because its been shown 377 * that some applications try to do this for Swing FontUIResources. 378 * Also operations such as new Font(font.getFontName(..), Font.PLAIN, 14); 379 * will NOT yield the same result, as the new underlying CompositeFont 380 * cannot be "looked up" in the font registry. 381 * This returns a FontUIResource as that is the Font sub-class needed 382 * by Swing. 383 * Suggested usage is something like : 384 * FontUIResource fuir; 385 * Font desktopFont = getDesktopFont(..); 386 * if (FontManager.fontSupportsDefaultEncoding(desktopFont)) { 387 * fuir = new FontUIResource(desktopFont); 388 * } else { 389 * fuir = FontManager.getCompositeFontUIResource(desktopFont); 390 * } 391 * return fuir; 392 */ 393 private static volatile 394 SoftReference<ConcurrentHashMap<PhysicalFont, CompositeFont>> 395 compMapRef = new SoftReference<>(null); 396 getCompositeFontUIResource(Font font)397 public static FontUIResource getCompositeFontUIResource(Font font) { 398 399 FontUIResource fuir = new FontUIResource(font); 400 Font2D font2D = FontUtilities.getFont2D(font); 401 402 if (!(font2D instanceof PhysicalFont)) { 403 /* Swing should only be calling this when a font is obtained 404 * from desktop properties, so should generally be a physical font, 405 * an exception might be for names like "MS Serif" which are 406 * automatically mapped to "Serif", so there's no need to do 407 * anything special in that case. But note that suggested usage 408 * is first to call fontSupportsDefaultEncoding(Font) and this 409 * method should not be called if that were to return true. 410 */ 411 return fuir; 412 } 413 414 FontManager fm = FontManagerFactory.getInstance(); 415 Font2D dialog = fm.findFont2D("dialog", font.getStyle(), FontManager.NO_FALLBACK); 416 // Should never be null, but MACOSX fonts are not CompositeFonts 417 if (dialog == null || !(dialog instanceof CompositeFont)) { 418 return fuir; 419 } 420 CompositeFont dialog2D = (CompositeFont)dialog; 421 PhysicalFont physicalFont = (PhysicalFont)font2D; 422 ConcurrentHashMap<PhysicalFont, CompositeFont> compMap = compMapRef.get(); 423 if (compMap == null) { // Its been collected. 424 compMap = new ConcurrentHashMap<PhysicalFont, CompositeFont>(); 425 compMapRef = new SoftReference<>(compMap); 426 } 427 CompositeFont compFont = compMap.get(physicalFont); 428 if (compFont == null) { 429 compFont = new CompositeFont(physicalFont, dialog2D); 430 compMap.put(physicalFont, compFont); 431 } 432 FontAccess.getFontAccess().setFont2D(fuir, compFont.handle); 433 /* marking this as a created font is needed as only created fonts 434 * copy their creator's handles. 435 */ 436 FontAccess.getFontAccess().setCreatedFont(fuir); 437 return fuir; 438 } 439 440 /* A small "map" from GTK/fontconfig names to the equivalent JDK 441 * logical font name. 442 */ 443 private static final String[][] nameMap = { 444 {"sans", "sansserif"}, 445 {"sans-serif", "sansserif"}, 446 {"serif", "serif"}, 447 {"monospace", "monospaced"} 448 }; 449 mapFcName(String name)450 public static String mapFcName(String name) { 451 for (int i = 0; i < nameMap.length; i++) { 452 if (name.equals(nameMap[i][0])) { 453 return nameMap[i][1]; 454 } 455 } 456 return null; 457 } 458 459 460 /* This is called by Swing passing in a fontconfig family name 461 * such as "sans". In return Swing gets a FontUIResource instance 462 * that has queried fontconfig to resolve the font(s) used for this. 463 * Fontconfig will if asked return a list of fonts to give the largest 464 * possible code point coverage. 465 * For now we use only the first font returned by fontconfig, and 466 * back it up with the most closely matching JDK logical font. 467 * Essentially this means pre-pending what we return now with fontconfig's 468 * preferred physical font. This could lead to some duplication in cases, 469 * if we already included that font later. We probably should remove such 470 * duplicates, but it is not a significant problem. It can be addressed 471 * later as part of creating a Composite which uses more of the 472 * same fonts as fontconfig. At that time we also should pay more 473 * attention to the special rendering instructions fontconfig returns, 474 * such as whether we should prefer embedded bitmaps over antialiasing. 475 * There's no way to express that via a Font at present. 476 */ getFontConfigFUIR(String fcFamily, int style, int size)477 public static FontUIResource getFontConfigFUIR(String fcFamily, 478 int style, int size) { 479 480 String mapped = mapFcName(fcFamily); 481 if (mapped == null) { 482 mapped = "sansserif"; 483 } 484 485 FontUIResource fuir; 486 FontManager fm = FontManagerFactory.getInstance(); 487 if (fm instanceof SunFontManager) { 488 SunFontManager sfm = (SunFontManager) fm; 489 fuir = sfm.getFontConfigFUIR(mapped, style, size); 490 } else { 491 fuir = new FontUIResource(mapped, style, size); 492 } 493 return fuir; 494 } 495 496 497 /** 498 * Used by windows printing to assess if a font is likely to 499 * be layout compatible with JDK 500 * TrueType fonts should be, but if they have no GPOS table, 501 * but do have a GSUB table, then they are probably older 502 * fonts GDI handles differently. 503 */ textLayoutIsCompatible(Font font)504 public static boolean textLayoutIsCompatible(Font font) { 505 506 Font2D font2D = getFont2D(font); 507 if (font2D instanceof TrueTypeFont) { 508 TrueTypeFont ttf = (TrueTypeFont) font2D; 509 return 510 ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null || 511 ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null; 512 } else { 513 return false; 514 } 515 } 516 517 } 518