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