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