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