1 /* 2 * Copyright (c) 2003, 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.awt.GraphicsEnvironment; 30 import java.awt.FontFormatException; 31 import java.io.File; 32 import java.io.FilenameFilter; 33 import java.security.AccessController; 34 import java.security.PrivilegedAction; 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.Hashtable; 39 import java.util.Iterator; 40 import java.util.Locale; 41 import java.util.Map; 42 import java.util.NoSuchElementException; 43 import java.util.StringTokenizer; 44 import java.util.TreeMap; 45 import java.util.Vector; 46 import java.util.concurrent.ConcurrentHashMap; 47 import java.util.logging.Level; 48 import java.util.logging.Logger; 49 import javax.swing.plaf.FontUIResource; 50 51 import sun.awt.AppContext; 52 import sun.awt.FontConfiguration; 53 import sun.awt.SunHints; 54 import sun.awt.SunToolkit; 55 import sun.java2d.HeadlessGraphicsEnvironment; 56 import sun.java2d.SunGraphicsEnvironment; 57 58 import java.awt.geom.GeneralPath; 59 import java.awt.geom.Point2D; 60 import java.awt.geom.Rectangle2D; 61 62 import java.lang.reflect.Constructor; 63 64 import sun.java2d.Disposer; 65 66 /* 67 * Interface between Java Fonts (java.awt.Font) and the underlying 68 * font files/native font resources and the Java and native font scalers. 69 */ 70 public final class FontManager { 71 72 public static final int FONTFORMAT_NONE = -1; 73 public static final int FONTFORMAT_TRUETYPE = 0; 74 public static final int FONTFORMAT_TYPE1 = 1; 75 public static final int FONTFORMAT_T2K = 2; 76 public static final int FONTFORMAT_TTC = 3; 77 public static final int FONTFORMAT_COMPOSITE = 4; 78 public static final int FONTFORMAT_NATIVE = 5; 79 80 public static final int NO_FALLBACK = 0; 81 public static final int PHYSICAL_FALLBACK = 1; 82 public static final int LOGICAL_FALLBACK = 2; 83 84 public static final int QUADPATHTYPE = 1; 85 public static final int CUBICPATHTYPE = 2; 86 87 /* Pool of 20 font file channels chosen because some UTF-8 locale 88 * composite fonts can use up to 16 platform fonts (including the 89 * Lucida fall back). This should prevent channel thrashing when 90 * dealing with one of these fonts. 91 * The pool array stores the fonts, rather than directly referencing 92 * the channels, as the font needs to do the open/close work. 93 */ 94 private static final int CHANNELPOOLSIZE = 20; 95 private static int lastPoolIndex = 0; 96 private static int poolSize = 0; 97 private static FileFont fontFileCache[] = new FileFont[CHANNELPOOLSIZE]; 98 99 /* Need to implement a simple linked list scheme for fast 100 * traversal and lookup. 101 * Also want to "fast path" dialog so there's minimal overhead. 102 */ 103 /* There are at exactly 20 composite fonts: 5 faces (but some are not 104 * usually different), in 4 styles. The array may be auto-expanded 105 * later if more are needed, eg for user-defined composites or locale 106 * variants. 107 */ 108 private static int maxCompFont = 0; 109 private static CompositeFont [] compFonts = new CompositeFont[20]; 110 private static ConcurrentHashMap<String, CompositeFont> 111 compositeFonts = new ConcurrentHashMap<String, CompositeFont>(); 112 private static ConcurrentHashMap<String, PhysicalFont> 113 physicalFonts = new ConcurrentHashMap<String, PhysicalFont>(); 114 private static ConcurrentHashMap<String, PhysicalFont> 115 registeredFontFiles = new ConcurrentHashMap<String, PhysicalFont>(); 116 117 /* given a full name find the Font. Remind: there's duplication 118 * here in that this contains the content of compositeFonts + 119 * physicalFonts. 120 */ 121 private static ConcurrentHashMap<String, Font2D> 122 fullNameToFont = new ConcurrentHashMap<String, Font2D>(); 123 124 /* TrueType fonts have localised names. Support searching all 125 * of these before giving up on a name. 126 */ 127 private static HashMap<String, TrueTypeFont> localeFullNamesToFont; 128 129 private static PhysicalFont defaultPhysicalFont; 130 131 /* deprecated, unsupported hack - actually invokes a bug! */ 132 private static boolean usePlatformFontMetrics = false; 133 134 public static Logger logger = null; 135 public static boolean logging; 136 static boolean longAddresses; 137 static String osName; 138 static boolean useT2K; 139 static boolean isWindows; 140 static boolean isSolaris; 141 public static boolean isSolaris8; // needed to check for JA wavedash fix. 142 public static boolean isSolaris9; // needed to check for songti font usage. 143 private static boolean loaded1dot0Fonts = false; 144 static SunGraphicsEnvironment sgEnv; 145 static boolean loadedAllFonts = false; 146 static boolean loadedAllFontFiles = false; 147 static TrueTypeFont eudcFont; 148 static HashMap<String,String> jreFontMap; 149 static HashSet<String> jreLucidaFontFiles; 150 static String[] jreOtherFontFiles; 151 static boolean noOtherJREFontFiles = false; // initial assumption. 152 153 /* Used to indicate required return type from toArray(..); */ 154 private static String[] STR_ARRAY = new String[0]; 155 156 private static void initJREFontMap() { 157 158 /* Key is familyname+style value as an int. 159 * Value is filename containing the font. 160 * If no mapping exists, it means there is no font file for the style 161 * If the mapping exists but the file doesn't exist in the deferred 162 * list then it means its not installed. 163 * This looks like a lot of code and strings but if it saves even 164 * a single file being opened at JRE start-up there's a big payoff. 165 * Lucida Sans is probably the only important case as the others 166 * are rarely used. Consider removing the other mappings if there's 167 * no evidence they are useful in practice. 168 */ 169 jreFontMap = new HashMap<String,String>(); 170 jreLucidaFontFiles = new HashSet<String>(); 171 if (SunGraphicsEnvironment.isOpenJDK()) { 172 return; 173 } 174 /* Lucida Sans Family */ 175 jreFontMap.put("lucida sans0", "LucidaSansRegular.ttf"); 176 jreFontMap.put("lucida sans1", "LucidaSansDemiBold.ttf"); 177 /* Lucida Sans full names (map Bold and DemiBold to same file) */ 178 jreFontMap.put("lucida sans regular0", "LucidaSansRegular.ttf"); 179 jreFontMap.put("lucida sans regular1", "LucidaSansDemiBold.ttf"); 180 jreFontMap.put("lucida sans bold1", "LucidaSansDemiBold.ttf"); 181 jreFontMap.put("lucida sans demibold1", "LucidaSansDemiBold.ttf"); 182 183 /* Lucida Sans Typewriter Family */ 184 jreFontMap.put("lucida sans typewriter0", 185 "LucidaTypewriterRegular.ttf"); 186 jreFontMap.put("lucida sans typewriter1", "LucidaTypewriterBold.ttf"); 187 /* Typewriter full names (map Bold and DemiBold to same file) */ 188 jreFontMap.put("lucida sans typewriter regular0", 189 "LucidaTypewriter.ttf"); 190 jreFontMap.put("lucida sans typewriter regular1", 191 "LucidaTypewriterBold.ttf"); 192 jreFontMap.put("lucida sans typewriter bold1", 193 "LucidaTypewriterBold.ttf"); 194 jreFontMap.put("lucida sans typewriter demibold1", 195 "LucidaTypewriterBold.ttf"); 196 197 /* Lucida Bright Family */ 198 jreFontMap.put("lucida bright0", "LucidaBrightRegular.ttf"); 199 jreFontMap.put("lucida bright1", "LucidaBrightDemiBold.ttf"); 200 jreFontMap.put("lucida bright2", "LucidaBrightItalic.ttf"); 201 jreFontMap.put("lucida bright3", "LucidaBrightDemiItalic.ttf"); 202 /* Lucida Bright full names (map Bold and DemiBold to same file) */ 203 jreFontMap.put("lucida bright regular0", "LucidaBrightRegular.ttf"); 204 jreFontMap.put("lucida bright regular1", "LucidaBrightDemiBold.ttf"); 205 jreFontMap.put("lucida bright regular2", "LucidaBrightItalic.ttf"); 206 jreFontMap.put("lucida bright regular3", "LucidaBrightDemiItalic.ttf"); 207 jreFontMap.put("lucida bright bold1", "LucidaBrightDemiBold.ttf"); 208 jreFontMap.put("lucida bright bold3", "LucidaBrightDemiItalic.ttf"); 209 jreFontMap.put("lucida bright demibold1", "LucidaBrightDemiBold.ttf"); 210 jreFontMap.put("lucida bright demibold3","LucidaBrightDemiItalic.ttf"); 211 jreFontMap.put("lucida bright italic2", "LucidaBrightItalic.ttf"); 212 jreFontMap.put("lucida bright italic3", "LucidaBrightDemiItalic.ttf"); 213 jreFontMap.put("lucida bright bold italic3", 214 "LucidaBrightDemiItalic.ttf"); 215 jreFontMap.put("lucida bright demibold italic3", 216 "LucidaBrightDemiItalic.ttf"); 217 for (String ffile : jreFontMap.values()) { 218 jreLucidaFontFiles.add(ffile); 219 } 220 } 221 222 static { 223 224 if (SunGraphicsEnvironment.debugFonts) { 225 logger = Logger.getLogger("sun.java2d", null); 226 logging = logger.getLevel() != Level.OFF; 227 } 228 initJREFontMap(); 229 230 java.security.AccessController.doPrivileged( 231 new java.security.PrivilegedAction() { 232 public Object run() { 233 FontManagerNativeLibrary.load(); 234 235 // JNI throws an exception if a class/method/field is not found, 236 // so there's no need to do anything explicit here. 237 initIDs(); 238 239 switch (StrikeCache.nativeAddressSize) { 240 case 8: longAddresses = true; break; 241 case 4: longAddresses = false; break; 242 default: throw new RuntimeException("Unexpected address size"); 243 } 244 245 osName = System.getProperty("os.name", "unknownOS"); 246 isSolaris = osName.startsWith("SunOS"); 247 248 if (isSolaris) { 249 String t2kStr= System.getProperty("sun.java2d.font.scaler"); 250 useT2K = "t2k".equals(t2kStr); 251 String version = System.getProperty("os.version", "unk"); 252 isSolaris8 = version.equals("5.8"); 253 isSolaris9 = version.equals("5.9"); 254 } else { 255 isWindows = osName.startsWith("Windows"); 256 if (isWindows) { 257 String eudcFile = 258 SunGraphicsEnvironment.eudcFontFileName; 259 if (eudcFile != null) { 260 try { 261 eudcFont = new TrueTypeFont(eudcFile, null, 0, 262 true); 263 } catch (FontFormatException e) { 264 } 265 } 266 String prop = 267 System.getProperty("java2d.font.usePlatformFont"); 268 if (("true".equals(prop) || getPlatformFontVar())) { 269 usePlatformFontMetrics = true; 270 System.out.println("Enabling platform font metrics for win32. This is an unsupported option."); 271 System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases."); 272 System.out.println("It is appropriate only for use by applications which do not use any Java 2"); 273 System.out.println("functionality. This property will be removed in a later release."); 274 } 275 } 276 } 277 return null; 278 } 279 }); 280 } 281 282 /* Initialise ptrs used by JNI methods */ 283 private static native void initIDs(); 284 285 public static void addToPool(FileFont font) { 286 boolean added = false; 287 synchronized (fontFileCache) { 288 /* use poolSize to quickly detect if there's any free slots. 289 * This is a performance tweak based on the assumption that 290 * if this is executed at all often, its because there are many 291 * fonts being used and the pool will be full, and we will save 292 * a fruitless iteration 293 */ 294 if (poolSize < CHANNELPOOLSIZE) { 295 for (int i=0; i<CHANNELPOOLSIZE; i++) { 296 if (fontFileCache[i] == null) { 297 fontFileCache[i] = font; 298 poolSize++; 299 added = true; 300 break; 301 } 302 } 303 assert added; 304 } else { 305 // is it possible for this to be the same font? 306 assert fontFileCache[lastPoolIndex] != font; 307 /* replace with new font, poolSize is unchanged. */ 308 fontFileCache[lastPoolIndex].close(); 309 fontFileCache[lastPoolIndex] = font; 310 /* lastPoolIndex is updated so that the least recently opened 311 * file will be closed next. 312 */ 313 lastPoolIndex = (lastPoolIndex+1) % CHANNELPOOLSIZE; 314 } 315 } 316 } 317 318 /* 319 * In the normal course of events, the pool of fonts can remain open 320 * ready for quick access to their contents. The pool is sized so 321 * that it is not an excessive consumer of system resources whilst 322 * facilitating performance by providing ready access to the most 323 * recently used set of font files. 324 * The only reason to call removeFromPool(..) is for a Font that 325 * you want to to have GC'd. Currently this would apply only to fonts 326 * created with java.awt.Font.createFont(..). 327 * In this case, the caller is expected to have arranged for the file 328 * to be closed. 329 * REMIND: consider how to know when a createFont created font should 330 * be closed. 331 */ 332 public static void removeFromPool(FileFont font) { 333 synchronized (fontFileCache) { 334 for (int i=0; i<CHANNELPOOLSIZE; i++) { 335 if (fontFileCache[i] == font) { 336 fontFileCache[i] = null; 337 poolSize--; 338 } 339 } 340 } 341 } 342 343 /** 344 * This method is provided for internal and exclusive use by Swing. 345 * 346 * @param font representing a physical font. 347 * @return true if the underlying font is a TrueType or OpenType font 348 * that claims to support the Microsoft Windows encoding corresponding to 349 * the default file.encoding property of this JRE instance. 350 * This narrow value is useful for Swing to decide if the font is useful 351 * for the the Windows Look and Feel, or, if a composite font should be 352 * used instead. 353 * The information used to make the decision is obtained from 354 * the ulCodePageRange fields in the font. 355 * A caller can use isLogicalFont(Font) in this class before calling 356 * this method and would not need to call this method if that 357 * returns true. 358 */ 359 // static boolean fontSupportsDefaultEncoding(Font font) { 360 // String encoding = 361 // (String) java.security.AccessController.doPrivileged( 362 // new sun.security.action.GetPropertyAction("file.encoding")); 363 364 // if (encoding == null || font == null) { 365 // return false; 366 // } 367 368 // encoding = encoding.toLowerCase(Locale.ENGLISH); 369 370 // return FontManager.fontSupportsEncoding(font, encoding); 371 // } 372 373 /* Revise the implementation to in fact mean "font is a composite font. 374 * This ensures that Swing components will always benefit from the 375 * fall back fonts 376 */ 377 public static boolean fontSupportsDefaultEncoding(Font font) { 378 return getFont2D(font) instanceof CompositeFont; 379 } 380 381 /** 382 * This method is provided for internal and exclusive use by Swing. 383 * 384 * It may be used in conjunction with fontSupportsDefaultEncoding(Font) 385 * In the event that a desktop properties font doesn't directly 386 * support the default encoding, (ie because the host OS supports 387 * adding support for the current locale automatically for native apps), 388 * then Swing calls this method to get a font which uses the specified 389 * font for the code points it covers, but also supports this locale 390 * just as the standard composite fonts do. 391 * Note: this will over-ride any setting where an application 392 * specifies it prefers locale specific composite fonts. 393 * The logic for this, is that this method is used only where the user or 394 * application has specified that the native L&F be used, and that 395 * we should honour that request to use the same font as native apps use. 396 * 397 * The behaviour of this method is to construct a new composite 398 * Font object that uses the specified physical font as its first 399 * component, and adds all the components of "dialog" as fall back 400 * components. 401 * The method currently assumes that only the size and style attributes 402 * are set on the specified font. It doesn't copy the font transform or 403 * other attributes because they aren't set on a font created from 404 * the desktop. This will need to be fixed if use is broadened. 405 * 406 * Operations such as Font.deriveFont will work properly on the 407 * font returned by this method for deriving a different point size. 408 * Additionally it tries to support a different style by calling 409 * getNewComposite() below. That also supports replacing slot zero 410 * with a different physical font but that is expected to be "rare". 411 * Deriving with a different style is needed because its been shown 412 * that some applications try to do this for Swing FontUIResources. 413 * Also operations such as new Font(font.getFontName(..), Font.PLAIN, 14); 414 * will NOT yield the same result, as the new underlying CompositeFont 415 * cannot be "looked up" in the font registry. 416 * This returns a FontUIResource as that is the Font sub-class needed 417 * by Swing. 418 * Suggested usage is something like : 419 * FontUIResource fuir; 420 * Font desktopFont = getDesktopFont(..); 421 * // NOTE even if fontSupportsDefaultEncoding returns true because 422 * // you get Tahoma and are running in an English locale, you may 423 * // still want to just call getCompositeFontUIResource() anyway 424 * // as only then will you get fallback fonts - eg for CJK. 425 * if (FontManager.fontSupportsDefaultEncoding(desktopFont)) { 426 * fuir = new FontUIResource(..); 427 * } else { 428 * fuir = FontManager.getCompositeFontUIResource(desktopFont); 429 * } 430 * return fuir; 431 */ 432 public static FontUIResource getCompositeFontUIResource(Font font) { 433 434 FontUIResource fuir = new FontUIResource(font); 435 Font2D font2D = getFont2D(font); 436 437 if (!(font2D instanceof PhysicalFont)) { 438 /* Swing should only be calling this when a font is obtained 439 * from desktop properties, so should generally be a physical font, 440 * an exception might be for names like "MS Serif" which are 441 * automatically mapped to "Serif", so there's no need to do 442 * anything special in that case. But note that suggested usage 443 * is first to call fontSupportsDefaultEncoding(Font) and this 444 * method should not be called if that were to return true. 445 */ 446 return fuir; 447 } 448 449 CompositeFont dialog2D = 450 (CompositeFont)findFont2D("dialog", font.getStyle(), NO_FALLBACK); 451 if (dialog2D == null) { /* shouldn't happen */ 452 return fuir; 453 } 454 PhysicalFont physicalFont = (PhysicalFont)font2D; 455 CompositeFont compFont = new CompositeFont(physicalFont, dialog2D); 456 setFont2D(fuir, compFont.handle); 457 /* marking this as a created font is needed as only created fonts 458 * copy their creator's handles. 459 */ 460 setCreatedFont(fuir); 461 return fuir; 462 } 463 464 public static Font2DHandle getNewComposite(String family, int style, 465 Font2DHandle handle) { 466 467 if (!(handle.font2D instanceof CompositeFont)) { 468 return handle; 469 } 470 471 CompositeFont oldComp = (CompositeFont)handle.font2D; 472 PhysicalFont oldFont = oldComp.getSlotFont(0); 473 474 if (family == null) { 475 family = oldFont.getFamilyName(null); 476 } 477 if (style == -1) { 478 style = oldComp.getStyle(); 479 } 480 481 Font2D newFont = findFont2D(family, style, NO_FALLBACK); 482 if (!(newFont instanceof PhysicalFont)) { 483 newFont = oldFont; 484 } 485 PhysicalFont physicalFont = (PhysicalFont)newFont; 486 CompositeFont dialog2D = 487 (CompositeFont)findFont2D("dialog", style, NO_FALLBACK); 488 if (dialog2D == null) { /* shouldn't happen */ 489 return handle; 490 } 491 CompositeFont compFont = new CompositeFont(physicalFont, dialog2D); 492 Font2DHandle newHandle = new Font2DHandle(compFont); 493 return newHandle; 494 } 495 496 public static native void setFont2D(Font font, Font2DHandle font2DHandle); 497 498 private static native boolean isCreatedFont(Font font); 499 private static native void setCreatedFont(Font font); 500 501 public static void registerCompositeFont(String compositeName, 502 String[] componentFileNames, 503 String[] componentNames, 504 int numMetricsSlots, 505 int[] exclusionRanges, 506 int[] exclusionMaxIndex, 507 boolean defer) { 508 509 CompositeFont cf = new CompositeFont(compositeName, 510 componentFileNames, 511 componentNames, 512 numMetricsSlots, 513 exclusionRanges, 514 exclusionMaxIndex, defer); 515 addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK); 516 synchronized (compFonts) { 517 compFonts[maxCompFont++] = cf; 518 } 519 } 520 521 /* This variant is used only when the application specifies 522 * a variant of composite fonts which prefers locale specific or 523 * proportional fonts. 524 */ 525 public static void registerCompositeFont(String compositeName, 526 String[] componentFileNames, 527 String[] componentNames, 528 int numMetricsSlots, 529 int[] exclusionRanges, 530 int[] exclusionMaxIndex, 531 boolean defer, 532 ConcurrentHashMap<String, Font2D> 533 altNameCache) { 534 535 CompositeFont cf = new CompositeFont(compositeName, 536 componentFileNames, 537 componentNames, 538 numMetricsSlots, 539 exclusionRanges, 540 exclusionMaxIndex, defer); 541 /* if the cache has an existing composite for this case, make 542 * its handle point to this new font. 543 * This ensures that when the altNameCache that is passed in 544 * is the global mapNameCache - ie we are running as an application - 545 * that any statically created java.awt.Font instances which already 546 * have a Font2D instance will have that re-directed to the new Font 547 * on subsequent uses. This is particularly important for "the" 548 * default font instance, or similar cases where a UI toolkit (eg 549 * Swing) has cached a java.awt.Font. Note that if Swing is using 550 * a custom composite APIs which update the standard composites have 551 * no effect - this is typically the case only when using the Windows 552 * L&F where these APIs would conflict with that L&F anyway. 553 */ 554 Font2D oldFont = (Font2D) 555 altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH)); 556 if (oldFont instanceof CompositeFont) { 557 oldFont.handle.font2D = cf; 558 } 559 altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf); 560 } 561 562 private static void addCompositeToFontList(CompositeFont f, int rank) { 563 564 if (logging) { 565 logger.info("Add to Family "+ f.familyName + 566 ", Font " + f.fullName + " rank="+rank); 567 } 568 f.setRank(rank); 569 compositeFonts.put(f.fullName, f); 570 fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f); 571 572 FontFamily family = FontFamily.getFamily(f.familyName); 573 if (family == null) { 574 family = new FontFamily(f.familyName, true, rank); 575 } 576 family.setFont(f, f.style); 577 } 578 579 /* 580 * Systems may have fonts with the same name. 581 * We want to register only one of such fonts (at least until 582 * such time as there might be APIs which can accommodate > 1). 583 * Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts, 584 * 4) Type1 fonts, 5) native fonts. 585 * 586 * If the new font has the same name as the old font, the higher 587 * ranked font gets added, replacing the lower ranked one. 588 * If the fonts are of equal rank, then make a special case of 589 * font configuration rank fonts, which are on closer inspection, 590 * OT/TT fonts such that the larger font is registered. This is 591 * a heuristic since a font may be "larger" in the sense of more 592 * code points, or be a larger "file" because it has more bitmaps. 593 * So it is possible that using filesize may lead to less glyphs, and 594 * using glyphs may lead to lower quality display. Probably number 595 * of glyphs is the ideal, but filesize is information we already 596 * have and is good enough for the known cases. 597 * Also don't want to register fonts that match JRE font families 598 * but are coming from a source other than the JRE. 599 * This will ensure that we will algorithmically style the JRE 600 * plain font and get the same set of glyphs for all styles. 601 * 602 * Note that this method returns a value 603 * if it returns the same object as its argument that means this 604 * font was newly registered. 605 * If it returns a different object it means this font already exists, 606 * and you should use that one. 607 * If it returns null means this font was not registered and none 608 * in that name is registered. The caller must find a substitute 609 */ 610 private static PhysicalFont addToFontList(PhysicalFont f, int rank) { 611 612 String fontName = f.fullName; 613 String familyName = f.familyName; 614 if (fontName == null || "".equals(fontName)) { 615 return null; 616 } 617 if (compositeFonts.containsKey(fontName)) { 618 /* Don't register any font that has the same name as a composite */ 619 return null; 620 } 621 f.setRank(rank); 622 if (!physicalFonts.containsKey(fontName)) { 623 if (logging) { 624 logger.info("Add to Family "+familyName + 625 ", Font " + fontName + " rank="+rank); 626 } 627 physicalFonts.put(fontName, f); 628 FontFamily family = FontFamily.getFamily(familyName); 629 if (family == null) { 630 family = new FontFamily(familyName, false, rank); 631 family.setFont(f, f.style); 632 } else if (family.getRank() >= rank) { 633 family.setFont(f, f.style); 634 } 635 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f); 636 return f; 637 } else { 638 PhysicalFont newFont = f; 639 PhysicalFont oldFont = physicalFonts.get(fontName); 640 if (oldFont == null) { 641 return null; 642 } 643 /* If the new font is of an equal or higher rank, it is a 644 * candidate to replace the current one, subject to further tests. 645 */ 646 if (oldFont.getRank() >= rank) { 647 648 /* All fonts initialise their mapper when first 649 * used. If the mapper is non-null then this font 650 * has been accessed at least once. In that case 651 * do not replace it. This may be overly stringent, 652 * but its probably better not to replace a font that 653 * someone is already using without a compelling reason. 654 * Additionally the primary case where it is known 655 * this behaviour is important is in certain composite 656 * fonts, and since all the components of a given 657 * composite are usually initialised together this 658 * is unlikely. For this to be a problem, there would 659 * have to be a case where two different composites used 660 * different versions of the same-named font, and they 661 * were initialised and used at separate times. 662 * In that case we continue on and allow the new font to 663 * be installed, but replaceFont will continue to allow 664 * the original font to be used in Composite fonts. 665 */ 666 if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) { 667 return oldFont; 668 } 669 670 /* Normally we require a higher rank to replace a font, 671 * but as a special case, if the two fonts are the same rank, 672 * and are instances of TrueTypeFont we want the 673 * more complete (larger) one. 674 */ 675 if (oldFont.getRank() == rank) { 676 if (oldFont instanceof TrueTypeFont && 677 newFont instanceof TrueTypeFont) { 678 TrueTypeFont oldTTFont = (TrueTypeFont)oldFont; 679 TrueTypeFont newTTFont = (TrueTypeFont)newFont; 680 if (oldTTFont.fileSize >= newTTFont.fileSize) { 681 return oldFont; 682 } 683 } else { 684 return oldFont; 685 } 686 } 687 /* Don't replace ever JRE fonts. 688 * This test is in case a font configuration references 689 * a Lucida font, which has been mapped to a Lucida 690 * from the host O/S. The assumption here is that any 691 * such font configuration file is probably incorrect, or 692 * the host O/S version is for the use of AWT. 693 * In other words if we reach here, there's a possible 694 * problem with our choice of font configuration fonts. 695 */ 696 if (oldFont.platName.startsWith( 697 SunGraphicsEnvironment.jreFontDirName)) { 698 if (logging) { 699 logger.warning("Unexpected attempt to replace a JRE " + 700 " font " + fontName + " from " + 701 oldFont.platName + 702 " with " + newFont.platName); 703 } 704 return oldFont; 705 } 706 707 if (logging) { 708 logger.info("Replace in Family " + familyName + 709 ",Font " + fontName + " new rank="+rank + 710 " from " + oldFont.platName + 711 " with " + newFont.platName); 712 } 713 replaceFont(oldFont, newFont); 714 physicalFonts.put(fontName, newFont); 715 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), 716 newFont); 717 718 FontFamily family = FontFamily.getFamily(familyName); 719 if (family == null) { 720 family = new FontFamily(familyName, false, rank); 721 family.setFont(newFont, newFont.style); 722 } else if (family.getRank() >= rank) { 723 family.setFont(newFont, newFont.style); 724 } 725 return newFont; 726 } else { 727 return oldFont; 728 } 729 } 730 } 731 732 public static Font2D[] getRegisteredFonts() { 733 PhysicalFont[] physFonts = getPhysicalFonts(); 734 int mcf = maxCompFont; /* for MT-safety */ 735 Font2D[] regFonts = new Font2D[physFonts.length+mcf]; 736 System.arraycopy(compFonts, 0, regFonts, 0, mcf); 737 System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length); 738 return regFonts; 739 } 740 741 public static PhysicalFont[] getPhysicalFonts() { 742 return physicalFonts.values().toArray(new PhysicalFont[0]); 743 } 744 745 746 /* The class FontRegistrationInfo is used when a client says not 747 * to register a font immediately. This mechanism is used to defer 748 * initialisation of all the components of composite fonts at JRE 749 * start-up. The CompositeFont class is "aware" of this and when it 750 * is first used it asks for the registration of its components. 751 * Also in the event that any physical font is requested the 752 * deferred fonts are initialised before triggering a search of the 753 * system. 754 * Two maps are used. One to track the deferred fonts. The 755 * other to track the fonts that have been initialised through this 756 * mechanism. 757 */ 758 759 private static final class FontRegistrationInfo { 760 761 String fontFilePath; 762 String[] nativeNames; 763 int fontFormat; 764 boolean javaRasterizer; 765 int fontRank; 766 767 FontRegistrationInfo(String fontPath, String[] names, int format, 768 boolean useJavaRasterizer, int rank) { 769 this.fontFilePath = fontPath; 770 this.nativeNames = names; 771 this.fontFormat = format; 772 this.javaRasterizer = useJavaRasterizer; 773 this.fontRank = rank; 774 } 775 } 776 777 private static final ConcurrentHashMap<String, FontRegistrationInfo> 778 deferredFontFiles = 779 new ConcurrentHashMap<String, FontRegistrationInfo>(); 780 private static final ConcurrentHashMap<String, Font2DHandle> 781 initialisedFonts = new ConcurrentHashMap<String, Font2DHandle>(); 782 783 /* Remind: possibly enhance initialiseDeferredFonts() to be 784 * optionally given a name and a style and it could stop when it 785 * finds that font - but this would be a problem if two of the 786 * fonts reference the same font face name (cf the Solaris 787 * euro fonts). 788 */ 789 public static synchronized void initialiseDeferredFonts() { 790 for (String fileName : deferredFontFiles.keySet()) { 791 initialiseDeferredFont(fileName); 792 } 793 } 794 795 public static synchronized void registerDeferredJREFonts(String jreDir) { 796 for (FontRegistrationInfo info : deferredFontFiles.values()) { 797 if (info.fontFilePath != null && 798 info.fontFilePath.startsWith(jreDir)) { 799 initialiseDeferredFont(info.fontFilePath); 800 } 801 } 802 } 803 804 /* We keep a map of the files which contain the Lucida fonts so we 805 * don't need to search for them. 806 * But since we know what fonts these files contain, we can also avoid 807 * opening them to look for a font name we don't recognise - see 808 * findDeferredFont(). 809 * For typical cases where the font isn't a JRE one the overhead is 810 * this method call, HashMap.get() and null reference test, then 811 * a boolean test of noOtherJREFontFiles. 812 */ 813 private static PhysicalFont findJREDeferredFont(String name, int style) { 814 815 PhysicalFont physicalFont; 816 String nameAndStyle = name.toLowerCase(Locale.ENGLISH) + style; 817 String fileName = jreFontMap.get(nameAndStyle); 818 if (fileName != null) { 819 initSGEnv(); /* ensure jreFontDirName is initialised */ 820 fileName = SunGraphicsEnvironment.jreFontDirName + 821 File.separator + fileName; 822 if (deferredFontFiles.get(fileName) != null) { 823 physicalFont = initialiseDeferredFont(fileName); 824 if (physicalFont != null && 825 (physicalFont.getFontName(null).equalsIgnoreCase(name) || 826 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) 827 && physicalFont.style == style) { 828 return physicalFont; 829 } 830 } 831 } 832 833 /* Iterate over the deferred font files looking for any in the 834 * jre directory that we didn't recognise, open each of these. 835 * In almost all installations this will quickly fall through 836 * because only the Lucidas will be present and jreOtherFontFiles 837 * will be empty. 838 * noOtherJREFontFiles is used so we can skip this block as soon 839 * as its determined that its not needed - almost always after the 840 * very first time through. 841 */ 842 if (noOtherJREFontFiles) { 843 return null; 844 } 845 synchronized (jreLucidaFontFiles) { 846 if (jreOtherFontFiles == null) { 847 HashSet<String> otherFontFiles = new HashSet<String>(); 848 for (String deferredFile : deferredFontFiles.keySet()) { 849 File file = new File(deferredFile); 850 String dir = file.getParent(); 851 String fname = file.getName(); 852 /* skip names which aren't absolute, aren't in the JRE 853 * directory, or are known Lucida fonts. 854 */ 855 if (dir == null || 856 !dir.equals(SunGraphicsEnvironment.jreFontDirName) || 857 jreLucidaFontFiles.contains(fname)) { 858 continue; 859 } 860 otherFontFiles.add(deferredFile); 861 } 862 jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY); 863 if (jreOtherFontFiles.length == 0) { 864 noOtherJREFontFiles = true; 865 } 866 } 867 868 for (int i=0; i<jreOtherFontFiles.length;i++) { 869 fileName = jreOtherFontFiles[i]; 870 if (fileName == null) { 871 continue; 872 } 873 jreOtherFontFiles[i] = null; 874 physicalFont = initialiseDeferredFont(fileName); 875 if (physicalFont != null && 876 (physicalFont.getFontName(null).equalsIgnoreCase(name) || 877 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) 878 && physicalFont.style == style) { 879 return physicalFont; 880 } 881 } 882 } 883 884 return null; 885 } 886 887 /* This skips JRE installed fonts. */ 888 private static PhysicalFont findOtherDeferredFont(String name, int style) { 889 for (String fileName : deferredFontFiles.keySet()) { 890 File file = new File(fileName); 891 String dir = file.getParent(); 892 String fname = file.getName(); 893 if (dir != null && 894 dir.equals(SunGraphicsEnvironment.jreFontDirName) && 895 jreLucidaFontFiles.contains(fname)) { 896 continue; 897 } 898 PhysicalFont physicalFont = initialiseDeferredFont(fileName); 899 if (physicalFont != null && 900 (physicalFont.getFontName(null).equalsIgnoreCase(name) || 901 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) && 902 physicalFont.style == style) { 903 return physicalFont; 904 } 905 } 906 return null; 907 } 908 909 private static PhysicalFont findDeferredFont(String name, int style) { 910 911 PhysicalFont physicalFont = findJREDeferredFont(name, style); 912 if (physicalFont != null) { 913 return physicalFont; 914 } else { 915 return findOtherDeferredFont(name, style); 916 } 917 } 918 919 public static void registerDeferredFont(String fileNameKey, 920 String fullPathName, 921 String[] nativeNames, 922 int fontFormat, 923 boolean useJavaRasterizer, 924 int fontRank) { 925 FontRegistrationInfo regInfo = 926 new FontRegistrationInfo(fullPathName, nativeNames, fontFormat, 927 useJavaRasterizer, fontRank); 928 deferredFontFiles.put(fileNameKey, regInfo); 929 } 930 931 932 public static synchronized 933 PhysicalFont initialiseDeferredFont(String fileNameKey) { 934 935 if (fileNameKey == null) { 936 return null; 937 } 938 if (logging) { 939 logger.info("Opening deferred font file " + fileNameKey); 940 } 941 942 PhysicalFont physicalFont; 943 FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey); 944 if (regInfo != null) { 945 deferredFontFiles.remove(fileNameKey); 946 physicalFont = registerFontFile(regInfo.fontFilePath, 947 regInfo.nativeNames, 948 regInfo.fontFormat, 949 regInfo.javaRasterizer, 950 regInfo.fontRank); 951 952 953 if (physicalFont != null) { 954 /* Store the handle, so that if a font is bad, we 955 * retrieve the substituted font. 956 */ 957 initialisedFonts.put(fileNameKey, physicalFont.handle); 958 } else { 959 initialisedFonts.put(fileNameKey, 960 getDefaultPhysicalFont().handle); 961 } 962 } else { 963 Font2DHandle handle = initialisedFonts.get(fileNameKey); 964 if (handle == null) { 965 /* Probably shouldn't happen, but just in case */ 966 physicalFont = getDefaultPhysicalFont(); 967 } else { 968 physicalFont = (PhysicalFont)(handle.font2D); 969 } 970 } 971 return physicalFont; 972 } 973 974 /* Note that the return value from this method is not always 975 * derived from this file, and may be null. See addToFontList for 976 * some explanation of this. 977 */ 978 public static PhysicalFont registerFontFile(String fileName, 979 String[] nativeNames, 980 int fontFormat, 981 boolean useJavaRasterizer, 982 int fontRank) { 983 984 PhysicalFont regFont = registeredFontFiles.get(fileName); 985 if (regFont != null) { 986 return regFont; 987 } 988 989 PhysicalFont physicalFont = null; 990 try { 991 String name; 992 993 switch (fontFormat) { 994 995 case FontManager.FONTFORMAT_TRUETYPE: 996 int fn = 0; 997 TrueTypeFont ttf; 998 do { 999 ttf = new TrueTypeFont(fileName, nativeNames, fn++, 1000 useJavaRasterizer); 1001 PhysicalFont pf = addToFontList(ttf, fontRank); 1002 if (physicalFont == null) { 1003 physicalFont = pf; 1004 } 1005 } 1006 while (fn < ttf.getFontCount()); 1007 break; 1008 1009 case FontManager.FONTFORMAT_TYPE1: 1010 Type1Font t1f = new Type1Font(fileName, nativeNames); 1011 physicalFont = addToFontList(t1f, fontRank); 1012 break; 1013 1014 case FontManager.FONTFORMAT_NATIVE: 1015 NativeFont nf = new NativeFont(fileName, false); 1016 physicalFont = addToFontList(nf, fontRank); 1017 default: 1018 1019 } 1020 if (logging) { 1021 logger.info("Registered file " + fileName + " as font " + 1022 physicalFont + " rank=" + fontRank); 1023 } 1024 } catch (FontFormatException ffe) { 1025 if (logging) { 1026 logger.warning("Unusable font: " + 1027 fileName + " " + ffe.toString()); 1028 } 1029 } 1030 if (physicalFont != null && 1031 fontFormat != FontManager.FONTFORMAT_NATIVE) { 1032 registeredFontFiles.put(fileName, physicalFont); 1033 } 1034 return physicalFont; 1035 } 1036 1037 public static void registerFonts(String[] fileNames, 1038 String[][] nativeNames, 1039 int fontCount, 1040 int fontFormat, 1041 boolean useJavaRasterizer, 1042 int fontRank, boolean defer) { 1043 1044 for (int i=0; i < fontCount; i++) { 1045 if (defer) { 1046 registerDeferredFont(fileNames[i],fileNames[i], nativeNames[i], 1047 fontFormat, useJavaRasterizer, fontRank); 1048 } else { 1049 registerFontFile(fileNames[i], nativeNames[i], 1050 fontFormat, useJavaRasterizer, fontRank); 1051 } 1052 } 1053 } 1054 1055 /* 1056 * This is the Physical font used when some other font on the system 1057 * can't be located. There has to be at least one font or the font 1058 * system is not useful and the graphics environment cannot sustain 1059 * the Java platform. 1060 */ 1061 public static PhysicalFont getDefaultPhysicalFont() { 1062 if (defaultPhysicalFont == null) { 1063 /* findFont2D will load all fonts before giving up the search. 1064 * If the JRE Lucida isn't found (eg because the JRE fonts 1065 * directory is missing), it could find another version of Lucida 1066 * from the host system. This is OK because at that point we are 1067 * trying to gracefully handle/recover from a system 1068 * misconfiguration and this is probably a reasonable substitution. 1069 */ 1070 defaultPhysicalFont = (PhysicalFont) 1071 findFont2D("Lucida Sans Regular", Font.PLAIN, NO_FALLBACK); 1072 if (defaultPhysicalFont == null) { 1073 defaultPhysicalFont = (PhysicalFont) 1074 findFont2D("Arial", Font.PLAIN, NO_FALLBACK); 1075 } 1076 if (defaultPhysicalFont == null) { 1077 /* Because of the findFont2D call above, if we reach here, we 1078 * know all fonts have already been loaded, just accept any 1079 * match at this point. If this fails we are in real trouble 1080 * and I don't know how to recover from there being absolutely 1081 * no fonts anywhere on the system. 1082 */ 1083 Iterator i = physicalFonts.values().iterator(); 1084 if (i.hasNext()) { 1085 defaultPhysicalFont = (PhysicalFont)i.next(); 1086 } else { 1087 throw new Error("Probable fatal error:No fonts found."); 1088 } 1089 } 1090 } 1091 return defaultPhysicalFont; 1092 } 1093 1094 public static CompositeFont getDefaultLogicalFont(int style) { 1095 return (CompositeFont)findFont2D("dialog", style, NO_FALLBACK); 1096 } 1097 1098 /* 1099 * return String representation of style prepended with "." 1100 * This is useful for performance to avoid unnecessary string operations. 1101 */ 1102 private static String dotStyleStr(int num) { 1103 switch(num){ 1104 case Font.BOLD: 1105 return ".bold"; 1106 case Font.ITALIC: 1107 return ".italic"; 1108 case Font.ITALIC | Font.BOLD: 1109 return ".bolditalic"; 1110 default: 1111 return ".plain"; 1112 } 1113 } 1114 1115 static void initSGEnv() { 1116 if (sgEnv == null) { 1117 GraphicsEnvironment ge = 1118 GraphicsEnvironment.getLocalGraphicsEnvironment(); 1119 if (ge instanceof HeadlessGraphicsEnvironment) { 1120 HeadlessGraphicsEnvironment hgEnv = 1121 (HeadlessGraphicsEnvironment)ge; 1122 sgEnv = (SunGraphicsEnvironment) 1123 hgEnv.getSunGraphicsEnvironment(); 1124 } else { 1125 sgEnv = (SunGraphicsEnvironment)ge; 1126 } 1127 } 1128 } 1129 1130 /* This is implemented only on windows and is called from code that 1131 * executes only on windows. This isn't pretty but its not a precedent 1132 * in this file. This very probably should be cleaned up at some point. 1133 */ 1134 private static native void 1135 populateFontFileNameMap(HashMap<String,String> fontToFileMap, 1136 HashMap<String,String> fontToFamilyNameMap, 1137 HashMap<String,ArrayList<String>> 1138 familyToFontListMap, 1139 Locale locale); 1140 1141 /* Obtained from Platform APIs (windows only) 1142 * Map from lower-case font full name to basename of font file. 1143 * Eg "arial bold" -> ARIALBD.TTF. 1144 * For TTC files, there is a mapping for each font in the file. 1145 */ 1146 private static HashMap<String,String> fontToFileMap = null; 1147 1148 /* Obtained from Platform APIs (windows only) 1149 * Map from lower-case font full name to the name of its font family 1150 * Eg "arial bold" -> "Arial" 1151 */ 1152 private static HashMap<String,String> fontToFamilyNameMap = null; 1153 1154 /* Obtained from Platform APIs (windows only) 1155 * Map from a lower-case family name to a list of full names of 1156 * the member fonts, eg: 1157 * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"] 1158 */ 1159 private static HashMap<String,ArrayList<String>> familyToFontListMap= null; 1160 1161 /* The directories which contain platform fonts */ 1162 private static String[] pathDirs = null; 1163 1164 private static boolean haveCheckedUnreferencedFontFiles; 1165 1166 private static String[] getFontFilesFromPath(boolean noType1) { 1167 final FilenameFilter filter; 1168 if (noType1) { 1169 filter = SunGraphicsEnvironment.ttFilter; 1170 } else { 1171 filter = new SunGraphicsEnvironment.TTorT1Filter(); 1172 } 1173 return (String[])AccessController.doPrivileged(new PrivilegedAction() { 1174 public Object run() { 1175 if (pathDirs.length == 1) { 1176 File dir = new File(pathDirs[0]); 1177 String[] files = dir.list(filter); 1178 if (files == null) { 1179 return new String[0]; 1180 } 1181 for (int f=0; f<files.length; f++) { 1182 files[f] = files[f].toLowerCase(); 1183 } 1184 return files; 1185 } else { 1186 ArrayList<String> fileList = new ArrayList<String>(); 1187 for (int i = 0; i< pathDirs.length; i++) { 1188 File dir = new File(pathDirs[i]); 1189 String[] files = dir.list(filter); 1190 if (files == null) { 1191 continue; 1192 } 1193 for (int f=0; f<files.length ; f++) { 1194 fileList.add(files[f].toLowerCase()); 1195 } 1196 } 1197 return fileList.toArray(STR_ARRAY); 1198 } 1199 } 1200 }); 1201 } 1202 1203 /* This is needed since some windows registry names don't match 1204 * the font names. 1205 * - UPC styled font names have a double space, but the 1206 * registry entry mapping to a file doesn't. 1207 * - Marlett is in a hidden file not listed in the registry 1208 * - The registry advertises that the file david.ttf contains a 1209 * font with the full name "David Regular" when in fact its 1210 * just "David". 1211 * Directly fix up these known cases as this is faster. 1212 * If a font which doesn't match these known cases has no file, 1213 * it may be a font that has been temporarily added to the known set 1214 * or it may be an installed font with a missing registry entry. 1215 * Installed fonts are those in the windows font directories. 1216 * Make a best effort attempt to locate these. 1217 * We obtain the list of TrueType fonts in these directories and 1218 * filter out all the font files we already know about from the registry. 1219 * What remains may be "bad" fonts, duplicate fonts, or perhaps the 1220 * missing font(s) we are looking for. 1221 * Open each of these files to find out. 1222 */ 1223 private static void resolveWindowsFonts() { 1224 1225 ArrayList<String> unmappedFontNames = null; 1226 for (String font : fontToFamilyNameMap.keySet()) { 1227 String file = fontToFileMap.get(font); 1228 if (file == null) { 1229 if (font.indexOf(" ") > 0) { 1230 String newName = font.replaceFirst(" ", " "); 1231 file = fontToFileMap.get(newName); 1232 /* If this name exists and isn't for a valid name 1233 * replace the mapping to the file with this font 1234 */ 1235 if (file != null && 1236 !fontToFamilyNameMap.containsKey(newName)) { 1237 fontToFileMap.remove(newName); 1238 fontToFileMap.put(font, file); 1239 } 1240 } else if (font.equals("marlett")) { 1241 fontToFileMap.put(font, "marlett.ttf"); 1242 } else if (font.equals("david")) { 1243 file = fontToFileMap.get("david regular"); 1244 if (file != null) { 1245 fontToFileMap.remove("david regular"); 1246 fontToFileMap.put("david", file); 1247 } 1248 } else { 1249 if (unmappedFontNames == null) { 1250 unmappedFontNames = new ArrayList<String>(); 1251 } 1252 unmappedFontNames.add(font); 1253 } 1254 } 1255 } 1256 1257 if (unmappedFontNames != null) { 1258 HashSet<String> unmappedFontFiles = new HashSet<String>(); 1259 1260 /* Every font key in fontToFileMap ought to correspond to a 1261 * font key in fontToFamilyNameMap. Entries that don't seem 1262 * to correspond are likely fonts that were named differently 1263 * by GDI than in the registry. One known cause of this is when 1264 * Windows has had its regional settings changed so that from 1265 * GDI we get a localised (eg Chinese or Japanese) name for the 1266 * font, but the registry retains the English version of the name 1267 * that corresponded to the "install" locale for windows. 1268 * Since we are in this code block because there are unmapped 1269 * font names, we can look to find unused font->file mappings 1270 * and then open the files to read the names. We don't generally 1271 * want to open font files, as its a performance hit, but this 1272 * occurs only for a small number of fonts on specific system 1273 * configs - ie is believed that a "true" Japanese windows would 1274 * have JA names in the registry too. 1275 * Clone fontToFileMap and remove from the clone all keys which 1276 * match a fontToFamilyNameMap key. What remains maps to the 1277 * files we want to open to find the fonts GDI returned. 1278 * A font in such a file is added to the fontToFileMap after 1279 * checking its one of the unmappedFontNames we are looking for. 1280 * The original name that didn't map is removed from fontToFileMap 1281 * so essentially this "fixes up" fontToFileMap to use the same 1282 * name as GDI. 1283 * Also note that typically the fonts for which this occurs in 1284 * CJK locales are TTC fonts and not all fonts in a TTC may have 1285 * localised names. Eg MSGOTHIC.TTC contains 3 fonts and one of 1286 * them "MS UI Gothic" has no JA name whereas the other two do. 1287 * So not every font in these files is unmapped or new. 1288 */ 1289 HashMap<String,String> ffmapCopy = 1290 (HashMap<String,String>)(fontToFileMap.clone()); 1291 for (String key : fontToFamilyNameMap.keySet()) { 1292 ffmapCopy.remove(key); 1293 } 1294 for (String key : ffmapCopy.keySet()) { 1295 unmappedFontFiles.add(ffmapCopy.get(key)); 1296 fontToFileMap.remove(key); 1297 } 1298 1299 resolveFontFiles(unmappedFontFiles, unmappedFontNames); 1300 1301 /* If there are still unmapped font names, this means there's 1302 * something that wasn't in the registry. We need to get all 1303 * the font files directly and look at the ones that weren't 1304 * found in the registry. 1305 */ 1306 if (unmappedFontNames.size() > 0) { 1307 1308 /* getFontFilesFromPath() returns all lower case names. 1309 * To compare we also need lower case 1310 * versions of the names from the registry. 1311 */ 1312 ArrayList<String> registryFiles = new ArrayList<String>(); 1313 1314 for (String regFile : fontToFileMap.values()) { 1315 registryFiles.add(regFile.toLowerCase()); 1316 } 1317 /* We don't look for Type1 files here as windows will 1318 * not enumerate these, so aren't useful in reconciling 1319 * GDI's unmapped files. We do find these later when 1320 * we enumerate all fonts. 1321 */ 1322 for (String pathFile : getFontFilesFromPath(true)) { 1323 if (!registryFiles.contains(pathFile)) { 1324 unmappedFontFiles.add(pathFile); 1325 } 1326 } 1327 1328 resolveFontFiles(unmappedFontFiles, unmappedFontNames); 1329 } 1330 1331 /* remove from the set of names that will be returned to the 1332 * user any fonts that can't be mapped to files. 1333 */ 1334 if (unmappedFontNames.size() > 0) { 1335 int sz = unmappedFontNames.size(); 1336 for (int i=0; i<sz; i++) { 1337 String name = unmappedFontNames.get(i); 1338 String familyName = fontToFamilyNameMap.get(name); 1339 if (familyName != null) { 1340 ArrayList family = familyToFontListMap.get(familyName); 1341 if (family != null) { 1342 if (family.size() <= 1) { 1343 familyToFontListMap.remove(familyName); 1344 } 1345 } 1346 } 1347 fontToFamilyNameMap.remove(name); 1348 if (logging) { 1349 logger.info("No file for font:" + name); 1350 } 1351 } 1352 } 1353 } 1354 } 1355 1356 /** 1357 * In some cases windows may have fonts in the fonts folder that 1358 * don't show up in the registry or in the GDI calls to enumerate fonts. 1359 * The only way to find these is to list the directory. We invoke this 1360 * only in getAllFonts/Families, so most searches for a specific 1361 * font that is satisfied by the GDI/registry calls don't take the 1362 * additional hit of listing the directory. This hit is small enough 1363 * that its not significant in these 'enumerate all the fonts' cases. 1364 * The basic approach is to cross-reference the files windows found 1365 * with the ones in the directory listing approach, and for each 1366 * in the latter list that is missing from the former list, register it. 1367 */ 1368 private static synchronized void checkForUnreferencedFontFiles() { 1369 if (haveCheckedUnreferencedFontFiles) { 1370 return; 1371 } 1372 haveCheckedUnreferencedFontFiles = true; 1373 if (!isWindows) { 1374 return; 1375 } 1376 /* getFontFilesFromPath() returns all lower case names. 1377 * To compare we also need lower case 1378 * versions of the names from the registry. 1379 */ 1380 ArrayList<String> registryFiles = new ArrayList<String>(); 1381 for (String regFile : fontToFileMap.values()) { 1382 registryFiles.add(regFile.toLowerCase()); 1383 } 1384 1385 /* To avoid any issues with concurrent modification, create 1386 * copies of the existing maps, add the new fonts into these 1387 * and then replace the references to the old ones with the 1388 * new maps. ConcurrentHashmap is another option but its a lot 1389 * more changes and with this exception, these maps are intended 1390 * to be static. 1391 */ 1392 HashMap<String,String> fontToFileMap2 = null; 1393 HashMap<String,String> fontToFamilyNameMap2 = null; 1394 HashMap<String,ArrayList<String>> familyToFontListMap2 = null;; 1395 1396 for (String pathFile : getFontFilesFromPath(false)) { 1397 if (!registryFiles.contains(pathFile)) { 1398 if (logging) { 1399 logger.info("Found non-registry file : " + pathFile); 1400 } 1401 PhysicalFont f = registerFontFile(getPathName(pathFile)); 1402 if (f == null) { 1403 continue; 1404 } 1405 if (fontToFileMap2 == null) { 1406 fontToFileMap2 = new HashMap<String,String>(fontToFileMap); 1407 fontToFamilyNameMap2 = 1408 new HashMap<String,String>(fontToFamilyNameMap); 1409 familyToFontListMap2 = new 1410 HashMap<String,ArrayList<String>>(familyToFontListMap); 1411 } 1412 String fontName = f.getFontName(null); 1413 String family = f.getFamilyName(null); 1414 String familyLC = family.toLowerCase(); 1415 fontToFamilyNameMap2.put(fontName, family); 1416 fontToFileMap2.put(fontName, pathFile); 1417 ArrayList<String> fonts = familyToFontListMap2.get(familyLC); 1418 if (fonts == null) { 1419 fonts = new ArrayList<String>(); 1420 } else { 1421 fonts = new ArrayList<String>(fonts); 1422 } 1423 fonts.add(fontName); 1424 familyToFontListMap2.put(familyLC, fonts); 1425 } 1426 } 1427 if (fontToFileMap2 != null) { 1428 fontToFileMap = fontToFileMap2; 1429 familyToFontListMap = familyToFontListMap2; 1430 fontToFamilyNameMap = fontToFamilyNameMap2; 1431 } 1432 } 1433 1434 private static void resolveFontFiles(HashSet<String> unmappedFiles, 1435 ArrayList<String> unmappedFonts) { 1436 1437 Locale l = SunToolkit.getStartupLocale(); 1438 1439 for (String file : unmappedFiles) { 1440 try { 1441 int fn = 0; 1442 TrueTypeFont ttf; 1443 String fullPath = getPathName(file); 1444 if (logging) { 1445 logger.info("Trying to resolve file " + fullPath); 1446 } 1447 do { 1448 ttf = new TrueTypeFont(fullPath, null, fn++, true); 1449 // prefer the font's locale name. 1450 String fontName = ttf.getFontName(l).toLowerCase(); 1451 if (unmappedFonts.contains(fontName)) { 1452 fontToFileMap.put(fontName, file); 1453 unmappedFonts.remove(fontName); 1454 if (logging) { 1455 logger.info("Resolved absent registry entry for " + 1456 fontName + " located in " + fullPath); 1457 } 1458 } 1459 } 1460 while (fn < ttf.getFontCount()); 1461 } catch (Exception e) { 1462 } 1463 } 1464 } 1465 1466 private static synchronized HashMap<String,String> getFullNameToFileMap() { 1467 if (fontToFileMap == null) { 1468 1469 initSGEnv(); 1470 pathDirs = sgEnv.getPlatformFontDirs(); 1471 1472 fontToFileMap = new HashMap<String,String>(100); 1473 fontToFamilyNameMap = new HashMap<String,String>(100); 1474 familyToFontListMap = new HashMap<String,ArrayList<String>>(50); 1475 populateFontFileNameMap(fontToFileMap, 1476 fontToFamilyNameMap, 1477 familyToFontListMap, 1478 Locale.ENGLISH); 1479 if (isWindows) { 1480 resolveWindowsFonts(); 1481 } 1482 if (logging) { 1483 logPlatformFontInfo(); 1484 } 1485 } 1486 return fontToFileMap; 1487 } 1488 1489 private static void logPlatformFontInfo() { 1490 for (int i=0; i< pathDirs.length;i++) { 1491 logger.info("fontdir="+pathDirs[i]); 1492 } 1493 for (String keyName : fontToFileMap.keySet()) { 1494 logger.info("font="+keyName+" file="+ fontToFileMap.get(keyName)); 1495 } 1496 for (String keyName : fontToFamilyNameMap.keySet()) { 1497 logger.info("font="+keyName+" family="+ 1498 fontToFamilyNameMap.get(keyName)); 1499 } 1500 for (String keyName : familyToFontListMap.keySet()) { 1501 logger.info("family="+keyName+ " fonts="+ 1502 familyToFontListMap.get(keyName)); 1503 } 1504 } 1505 1506 /* Note this return list excludes logical fonts and JRE fonts */ 1507 public static String[] getFontNamesFromPlatform() { 1508 if (getFullNameToFileMap().size() == 0) { 1509 return null; 1510 } 1511 checkForUnreferencedFontFiles(); 1512 /* This odd code with TreeMap is used to preserve a historical 1513 * behaviour wrt the sorting order .. */ 1514 ArrayList<String> fontNames = new ArrayList<String>(); 1515 for (ArrayList<String> a : familyToFontListMap.values()) { 1516 for (String s : a) { 1517 fontNames.add(s); 1518 } 1519 } 1520 return fontNames.toArray(STR_ARRAY); 1521 } 1522 1523 public static boolean gotFontsFromPlatform() { 1524 return getFullNameToFileMap().size() != 0; 1525 } 1526 1527 public static String getFileNameForFontName(String fontName) { 1528 String fontNameLC = fontName.toLowerCase(Locale.ENGLISH); 1529 return fontToFileMap.get(fontNameLC); 1530 } 1531 1532 private static PhysicalFont registerFontFile(String file) { 1533 if (new File(file).isAbsolute() && 1534 !registeredFontFiles.contains(file)) { 1535 int fontFormat = FONTFORMAT_NONE; 1536 int fontRank = Font2D.UNKNOWN_RANK; 1537 if (SunGraphicsEnvironment.ttFilter.accept(null, file)) { 1538 fontFormat = FONTFORMAT_TRUETYPE; 1539 fontRank = Font2D.TTF_RANK; 1540 } else if 1541 (SunGraphicsEnvironment.t1Filter.accept(null, file)) { 1542 fontFormat = FONTFORMAT_TYPE1; 1543 fontRank = Font2D.TYPE1_RANK; 1544 } 1545 if (fontFormat == FONTFORMAT_NONE) { 1546 return null; 1547 } 1548 return registerFontFile(file, null, fontFormat, false, fontRank); 1549 } 1550 return null; 1551 } 1552 1553 /* Used to register any font files that are found by platform APIs 1554 * that weren't previously found in the standard font locations. 1555 * the isAbsolute() check is needed since that's whats stored in the 1556 * set, and on windows, the fonts in the system font directory that 1557 * are in the fontToFileMap are just basenames. We don't want to try 1558 * to register those again, but we do want to register other registry 1559 * installed fonts. 1560 */ 1561 public static void registerOtherFontFiles(HashSet registeredFontFiles) { 1562 if (getFullNameToFileMap().size() == 0) { 1563 return; 1564 } 1565 for (String file : fontToFileMap.values()) { 1566 registerFontFile(file); 1567 } 1568 } 1569 1570 public static boolean 1571 getFamilyNamesFromPlatform(TreeMap<String,String> familyNames, 1572 Locale requestedLocale) { 1573 if (getFullNameToFileMap().size() == 0) { 1574 return false; 1575 } 1576 checkForUnreferencedFontFiles(); 1577 for (String name : fontToFamilyNameMap.values()) { 1578 familyNames.put(name.toLowerCase(requestedLocale), name); 1579 } 1580 return true; 1581 } 1582 1583 /* Path may be absolute or a base file name relative to one of 1584 * the platform font directories 1585 */ 1586 private static String getPathName(String s) { 1587 File f = new File(s); 1588 if (f.isAbsolute()) { 1589 return s; 1590 } else if (pathDirs.length==1) { 1591 return pathDirs[0] + File.separator + s; 1592 } else { 1593 for (int p=0; p<pathDirs.length; p++) { 1594 f = new File(pathDirs[p] + File.separator + s); 1595 if (f.exists()) { 1596 return f.getAbsolutePath(); 1597 } 1598 } 1599 } 1600 return s; // shouldn't happen, but harmless 1601 } 1602 1603 /* lcName is required to be lower case for use as a key. 1604 * lcName may be a full name, or a family name, and style may 1605 * be specified in addition to either of these. So be sure to 1606 * get the right one. Since an app *could* ask for "Foo Regular" 1607 * and later ask for "Foo Italic", if we don't register all the 1608 * styles, then logic in findFont2D may try to style the original 1609 * so we register the entire family if we get a match here. 1610 * This is still a big win because this code is invoked where 1611 * otherwise we would register all fonts. 1612 * It's also useful for the case where "Foo Bold" was specified with 1613 * style Font.ITALIC, as we would want in that case to try to return 1614 * "Foo Bold Italic" if it exists, and it is only by locating "Foo Bold" 1615 * and opening it that we really "know" it's Bold, and can look for 1616 * a font that supports that and the italic style. 1617 * The code in here is not overtly windows-specific but in fact it 1618 * is unlikely to be useful as is on other platforms. It is maintained 1619 * in this shared source file to be close to its sole client and 1620 * because so much of the logic is intertwined with the logic in 1621 * findFont2D. 1622 */ 1623 private static Font2D findFontFromPlatform(String lcName, int style) { 1624 if (getFullNameToFileMap().size() == 0) { 1625 return null; 1626 } 1627 1628 ArrayList<String> family = null; 1629 String fontFile = null; 1630 String familyName = fontToFamilyNameMap.get(lcName); 1631 if (familyName != null) { 1632 fontFile = fontToFileMap.get(lcName); 1633 family = familyToFontListMap.get 1634 (familyName.toLowerCase(Locale.ENGLISH)); 1635 } else { 1636 family = familyToFontListMap.get(lcName); // is lcName is a family? 1637 if (family != null && family.size() > 0) { 1638 String lcFontName = family.get(0).toLowerCase(Locale.ENGLISH); 1639 if (lcFontName != null) { 1640 familyName = fontToFamilyNameMap.get(lcFontName); 1641 } 1642 } 1643 } 1644 if (family == null || familyName == null) { 1645 return null; 1646 } 1647 String [] fontList = (String[])family.toArray(STR_ARRAY); 1648 if (fontList.length == 0) { 1649 return null; 1650 } 1651 1652 /* first check that for every font in this family we can find 1653 * a font file. The specific reason for doing this is that 1654 * in at least one case on Windows a font has the face name "David" 1655 * but the registry entry is "David Regular". That is the "unique" 1656 * name of the font but in other cases the registry contains the 1657 * "full" name. See the specifications of name ids 3 and 4 in the 1658 * TrueType 'name' table. 1659 * In general this could cause a problem that we fail to register 1660 * if we all members of a family that we may end up mapping to 1661 * the wrong font member: eg return Bold when Plain is needed. 1662 */ 1663 for (int f=0;f<fontList.length;f++) { 1664 String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH); 1665 String fileName = fontToFileMap.get(fontNameLC); 1666 if (fileName == null) { 1667 if (logging) { 1668 logger.info("Platform lookup : No file for font " + 1669 fontList[f] + " in family " +familyName); 1670 } 1671 return null; 1672 } 1673 } 1674 1675 /* Currently this code only looks for TrueType fonts, so format 1676 * and rank can be specified without looking at the filename. 1677 */ 1678 PhysicalFont physicalFont = null; 1679 if (fontFile != null) { 1680 physicalFont = registerFontFile(getPathName(fontFile), null, 1681 FONTFORMAT_TRUETYPE, false, 1682 Font2D.TTF_RANK); 1683 } 1684 /* Register all fonts in this family. */ 1685 for (int f=0;f<fontList.length;f++) { 1686 String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH); 1687 String fileName = fontToFileMap.get(fontNameLC); 1688 if (fontFile != null && fontFile.equals(fileName)) { 1689 continue; 1690 } 1691 /* Currently this code only looks for TrueType fonts, so format 1692 * and rank can be specified without looking at the filename. 1693 */ 1694 registerFontFile(getPathName(fileName), null, 1695 FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK); 1696 } 1697 1698 Font2D font = null; 1699 FontFamily fontFamily = FontFamily.getFamily(familyName); 1700 /* Handle case where request "MyFont Bold", style=Font.ITALIC */ 1701 if (physicalFont != null) { 1702 style |= physicalFont.style; 1703 } 1704 if (fontFamily != null) { 1705 font = fontFamily.getFont(style); 1706 if (font == null) { 1707 font = fontFamily.getClosestStyle(style); 1708 } 1709 } 1710 return font; 1711 } 1712 1713 private static ConcurrentHashMap<String, Font2D> fontNameCache = 1714 new ConcurrentHashMap<String, Font2D>(); 1715 1716 /* 1717 * The client supplies a name and a style. 1718 * The name could be a family name, or a full name. 1719 * A font may exist with the specified style, or it may 1720 * exist only in some other style. For non-native fonts the scaler 1721 * may be able to emulate the required style. 1722 */ 1723 public static Font2D findFont2D(String name, int style, int fallback) { 1724 String lowerCaseName = name.toLowerCase(Locale.ENGLISH); 1725 String mapName = lowerCaseName + dotStyleStr(style); 1726 Font2D font; 1727 1728 /* If preferLocaleFonts() or preferProportionalFonts() has been 1729 * called we may be using an alternate set of composite fonts in this 1730 * app context. The presence of a pre-built name map indicates whether 1731 * this is so, and gives access to the alternate composite for the 1732 * name. 1733 */ 1734 if (usingPerAppContextComposites) { 1735 ConcurrentHashMap<String, Font2D> altNameCache = 1736 (ConcurrentHashMap<String, Font2D>) 1737 AppContext.getAppContext().get(CompositeFont.class); 1738 if (altNameCache != null) { 1739 font = (Font2D)altNameCache.get(mapName); 1740 } else { 1741 font = null; 1742 } 1743 } else { 1744 font = fontNameCache.get(mapName); 1745 } 1746 if (font != null) { 1747 return font; 1748 } 1749 1750 if (logging) { 1751 logger.info("Search for font: " + name); 1752 } 1753 1754 // The check below is just so that the bitmap fonts being set by 1755 // AWT and Swing thru the desktop properties do not trigger the 1756 // the load fonts case. The two bitmap fonts are now mapped to 1757 // appropriate equivalents for serif and sansserif. 1758 // Note that the cost of this comparison is only for the first 1759 // call until the map is filled. 1760 if (isWindows) { 1761 if (lowerCaseName.equals("ms sans serif")) { 1762 name = "sansserif"; 1763 } else if (lowerCaseName.equals("ms serif")) { 1764 name = "serif"; 1765 } 1766 } 1767 1768 /* This isn't intended to support a client passing in the 1769 * string default, but if a client passes in null for the name 1770 * the java.awt.Font class internally substitutes this name. 1771 * So we need to recognise it here to prevent a loadFonts 1772 * on the unrecognised name. The only potential problem with 1773 * this is it would hide any real font called "default"! 1774 * But that seems like a potential problem we can ignore for now. 1775 */ 1776 if (lowerCaseName.equals("default")) { 1777 name = "dialog"; 1778 } 1779 1780 /* First see if its a family name. */ 1781 FontFamily family = FontFamily.getFamily(name); 1782 if (family != null) { 1783 font = family.getFontWithExactStyleMatch(style); 1784 if (font == null) { 1785 font = findDeferredFont(name, style); 1786 } 1787 if (font == null) { 1788 font = family.getFont(style); 1789 } 1790 if (font == null) { 1791 font = family.getClosestStyle(style); 1792 } 1793 if (font != null) { 1794 fontNameCache.put(mapName, font); 1795 return font; 1796 } 1797 } 1798 1799 /* If it wasn't a family name, it should be a full name of 1800 * either a composite, or a physical font 1801 */ 1802 font = fullNameToFont.get(lowerCaseName); 1803 if (font != null) { 1804 /* Check that the requested style matches the matched font's style. 1805 * But also match style automatically if the requested style is 1806 * "plain". This because the existing behaviour is that the fonts 1807 * listed via getAllFonts etc always list their style as PLAIN. 1808 * This does lead to non-commutative behaviours where you might 1809 * start with "Lucida Sans Regular" and ask for a BOLD version 1810 * and get "Lucida Sans DemiBold" but if you ask for the PLAIN 1811 * style of "Lucida Sans DemiBold" you get "Lucida Sans DemiBold". 1812 * This consistent however with what happens if you have a bold 1813 * version of a font and no plain version exists - alg. styling 1814 * doesn't "unbolden" the font. 1815 */ 1816 if (font.style == style || style == Font.PLAIN) { 1817 fontNameCache.put(mapName, font); 1818 return font; 1819 } else { 1820 /* If it was a full name like "Lucida Sans Regular", but 1821 * the style requested is "bold", then we want to see if 1822 * there's the appropriate match against another font in 1823 * that family before trying to load all fonts, or applying a 1824 * algorithmic styling 1825 */ 1826 family = FontFamily.getFamily(font.getFamilyName(null)); 1827 if (family != null) { 1828 Font2D familyFont = family.getFont(style|font.style); 1829 /* We exactly matched the requested style, use it! */ 1830 if (familyFont != null) { 1831 fontNameCache.put(mapName, familyFont); 1832 return familyFont; 1833 } else { 1834 /* This next call is designed to support the case 1835 * where bold italic is requested, and if we must 1836 * style, then base it on either bold or italic - 1837 * not on plain! 1838 */ 1839 familyFont = family.getClosestStyle(style|font.style); 1840 if (familyFont != null) { 1841 /* The next check is perhaps one 1842 * that shouldn't be done. ie if we get this 1843 * far we have probably as close a match as we 1844 * are going to get. We could load all fonts to 1845 * see if somehow some parts of the family are 1846 * loaded but not all of it. 1847 */ 1848 if (familyFont.canDoStyle(style|font.style)) { 1849 fontNameCache.put(mapName, familyFont); 1850 return familyFont; 1851 } 1852 } 1853 } 1854 } 1855 } 1856 } 1857 1858 /* If reach here its possible that this is in a client which never 1859 * loaded the GraphicsEnvironment, so we haven't even loaded ANY of 1860 * the fonts from the environment. Do so now and recurse. 1861 */ 1862 if (sgEnv == null) { 1863 initSGEnv(); 1864 return findFont2D(name, style, fallback); 1865 } 1866 1867 if (isWindows) { 1868 /* Don't want Windows to return a Lucida Sans font from 1869 * C:\Windows\Fonts 1870 */ 1871 if (deferredFontFiles.size() > 0) { 1872 font = findJREDeferredFont(lowerCaseName, style); 1873 if (font != null) { 1874 fontNameCache.put(mapName, font); 1875 return font; 1876 } 1877 } 1878 font = findFontFromPlatform(lowerCaseName, style); 1879 if (font != null) { 1880 if (logging) { 1881 logger.info("Found font via platform API for request:\"" + 1882 name + "\":, style="+style+ 1883 " found font: " + font); 1884 } 1885 fontNameCache.put(mapName, font); 1886 return font; 1887 } 1888 } 1889 1890 /* If reach here and no match has been located, then if there are 1891 * uninitialised deferred fonts, load as many of those as needed 1892 * to find the deferred font. If none is found through that 1893 * search continue on. 1894 * There is possibly a minor issue when more than one 1895 * deferred font implements the same font face. Since deferred 1896 * fonts are only those in font configuration files, this is a 1897 * controlled situation, the known case being Solaris euro_fonts 1898 * versions of Arial, Times New Roman, Courier New. However 1899 * the larger font will transparently replace the smaller one 1900 * - see addToFontList() - when it is needed by the composite font. 1901 */ 1902 if (deferredFontFiles.size() > 0) { 1903 font = findDeferredFont(name, style); 1904 if (font != null) { 1905 fontNameCache.put(mapName, font); 1906 return font; 1907 } 1908 } 1909 1910 /* Some apps use deprecated 1.0 names such as helvetica and courier. On 1911 * Solaris these are Type1 fonts in /usr/openwin/lib/X11/fonts/Type1. 1912 * If running on Solaris will register all the fonts in this 1913 * directory. 1914 * May as well register the whole directory without actually testing 1915 * the font name is one of the deprecated names as the next step would 1916 * load all fonts which are in this directory anyway. 1917 * In the event that this lookup is successful it potentially "hides" 1918 * TrueType versions of such fonts that are elsewhere but since they 1919 * do not exist on Solaris this is not a problem. 1920 * Set a flag to indicate we've done this registration to avoid 1921 * repetition and more seriously, to avoid recursion. 1922 */ 1923 if (isSolaris&&!loaded1dot0Fonts) { 1924 /* "timesroman" is a special case since that's not the 1925 * name of any known font on Solaris or elsewhere. 1926 */ 1927 if (lowerCaseName.equals("timesroman")) { 1928 font = findFont2D("serif", style, fallback); 1929 fontNameCache.put(mapName, font); 1930 } 1931 sgEnv.register1dot0Fonts(); 1932 loaded1dot0Fonts = true; 1933 Font2D ff = findFont2D(name, style, fallback); 1934 return ff; 1935 } 1936 1937 /* We check for application registered fonts before 1938 * explicitly loading all fonts as if necessary the registration 1939 * code will have done so anyway. And we don't want to needlessly 1940 * load the actual files for all fonts. 1941 * Just as for installed fonts we check for family before fullname. 1942 * We do not add these fonts to fontNameCache for the 1943 * app context case which eliminates the overhead of a per context 1944 * cache for these. 1945 */ 1946 1947 if (fontsAreRegistered || fontsAreRegisteredPerAppContext) { 1948 Hashtable<String, FontFamily> familyTable = null; 1949 Hashtable<String, Font2D> nameTable; 1950 1951 if (fontsAreRegistered) { 1952 familyTable = createdByFamilyName; 1953 nameTable = createdByFullName; 1954 } else { 1955 AppContext appContext = AppContext.getAppContext(); 1956 familyTable = 1957 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); 1958 nameTable = 1959 (Hashtable<String,Font2D>)appContext.get(regFullNameKey); 1960 } 1961 1962 family = familyTable.get(lowerCaseName); 1963 if (family != null) { 1964 font = family.getFontWithExactStyleMatch(style); 1965 if (font == null) { 1966 font = family.getFont(style); 1967 } 1968 if (font == null) { 1969 font = family.getClosestStyle(style); 1970 } 1971 if (font != null) { 1972 if (fontsAreRegistered) { 1973 fontNameCache.put(mapName, font); 1974 } 1975 return font; 1976 } 1977 } 1978 font = nameTable.get(lowerCaseName); 1979 if (font != null) { 1980 if (fontsAreRegistered) { 1981 fontNameCache.put(mapName, font); 1982 } 1983 return font; 1984 } 1985 } 1986 1987 /* If reach here and no match has been located, then if all fonts 1988 * are not yet loaded, do so, and then recurse. 1989 */ 1990 if (!loadedAllFonts) { 1991 if (logging) { 1992 logger.info("Load fonts looking for:" + name); 1993 } 1994 sgEnv.loadFonts(); 1995 loadedAllFonts = true; 1996 return findFont2D(name, style, fallback); 1997 } 1998 1999 if (!loadedAllFontFiles) { 2000 if (logging) { 2001 logger.info("Load font files looking for:" + name); 2002 } 2003 sgEnv.loadFontFiles(); 2004 loadedAllFontFiles = true; 2005 return findFont2D(name, style, fallback); 2006 } 2007 2008 /* The primary name is the locale default - ie not US/English but 2009 * whatever is the default in this locale. This is the way it always 2010 * has been but may be surprising to some developers if "Arial Regular" 2011 * were hard-coded in their app and yet "Arial Regular" was not the 2012 * default name. Fortunately for them, as a consequence of the JDK 2013 * supporting returning names and family names for arbitrary locales, 2014 * we also need to support searching all localised names for a match. 2015 * But because this case of the name used to reference a font is not 2016 * the same as the default for this locale is rare, it makes sense to 2017 * search a much shorter list of default locale names and only go to 2018 * a longer list of names in the event that no match was found. 2019 * So add here code which searches localised names too. 2020 * As in 1.4.x this happens only after loading all fonts, which 2021 * is probably the right order. 2022 */ 2023 if ((font = findFont2DAllLocales(name, style)) != null) { 2024 fontNameCache.put(mapName, font); 2025 return font; 2026 } 2027 2028 /* Perhaps its a "compatibility" name - timesroman, helvetica, 2029 * or courier, which 1.0 apps used for logical fonts. 2030 * We look for these "late" after a loadFonts as we must not 2031 * hide real fonts of these names. 2032 * Map these appropriately: 2033 * On windows this means according to the rules specified by the 2034 * FontConfiguration : do it only for encoding==Cp1252 2035 * 2036 * REMIND: this is something we plan to remove. 2037 */ 2038 if (isWindows) { 2039 String compatName = 2040 sgEnv.getFontConfiguration().getFallbackFamilyName(name, null); 2041 if (compatName != null) { 2042 font = findFont2D(compatName, style, fallback); 2043 fontNameCache.put(mapName, font); 2044 return font; 2045 } 2046 } else if (lowerCaseName.equals("timesroman")) { 2047 font = findFont2D("serif", style, fallback); 2048 fontNameCache.put(mapName, font); 2049 return font; 2050 } else if (lowerCaseName.equals("helvetica")) { 2051 font = findFont2D("sansserif", style, fallback); 2052 fontNameCache.put(mapName, font); 2053 return font; 2054 } else if (lowerCaseName.equals("courier")) { 2055 font = findFont2D("monospaced", style, fallback); 2056 fontNameCache.put(mapName, font); 2057 return font; 2058 } 2059 2060 if (logging) { 2061 logger.info("No font found for:" + name); 2062 } 2063 2064 switch (fallback) { 2065 case PHYSICAL_FALLBACK: return getDefaultPhysicalFont(); 2066 case LOGICAL_FALLBACK: return getDefaultLogicalFont(style); 2067 default: return null; 2068 } 2069 } 2070 2071 /* This method can be more efficient as it will only need to 2072 * do the lookup once, and subsequent calls on the java.awt.Font 2073 * instance can utilise the cached Font2D on that object. 2074 * Its unfortunate it needs to be a native method, but the font2D 2075 * variable has to be private. 2076 */ 2077 public static native Font2D getFont2D(Font font); 2078 2079 /* Stuff below was in NativeFontWrapper and needed a new home */ 2080 2081 /* 2082 * Workaround for apps which are dependent on a font metrics bug 2083 * in JDK 1.1. This is an unsupported win32 private setting. 2084 */ 2085 public static boolean usePlatformFontMetrics() { 2086 return usePlatformFontMetrics; 2087 } 2088 2089 static native boolean getPlatformFontVar(); 2090 2091 private static final short US_LCID = 0x0409; // US English - default 2092 private static Map<String, Short> lcidMap; 2093 2094 // Return a Microsoft LCID from the given Locale. 2095 // Used when getting localized font data. 2096 2097 public static short getLCIDFromLocale(Locale locale) { 2098 // optimize for common case 2099 if (locale.equals(Locale.US)) { 2100 return US_LCID; 2101 } 2102 2103 if (lcidMap == null) { 2104 createLCIDMap(); 2105 } 2106 2107 String key = locale.toString(); 2108 while (!"".equals(key)) { 2109 Short lcidObject = (Short) lcidMap.get(key); 2110 if (lcidObject != null) { 2111 return lcidObject.shortValue(); 2112 } 2113 int pos = key.lastIndexOf('_'); 2114 if (pos < 1) { 2115 return US_LCID; 2116 } 2117 key = key.substring(0, pos); 2118 } 2119 2120 return US_LCID; 2121 } 2122 2123 2124 private static void addLCIDMapEntry(Map<String, Short> map, 2125 String key, short value) { 2126 map.put(key, new Short(value)); 2127 } 2128 2129 private static synchronized void createLCIDMap() { 2130 if (lcidMap != null) { 2131 return; 2132 } 2133 2134 Map<String, Short> map = new HashMap<String, Short>(200); 2135 2136 // the following statements are derived from the langIDMap 2137 // in src/windows/native/java/lang/java_props_md.c using the following 2138 // awk script: 2139 // $1~/\/\*/ { next} 2140 // $3~/\?\?/ { next } 2141 // $3!~/_/ { next } 2142 // $1~/0x0409/ { next } 2143 // $1~/0x0c0a/ { next } 2144 // $1~/0x042c/ { next } 2145 // $1~/0x0443/ { next } 2146 // $1~/0x0812/ { next } 2147 // $1~/0x04/ { print " addLCIDMapEntry(map, " substr($3, 0, 3) "\", (short) " substr($1, 0, 6) ");" ; next } 2148 // $3~/,/ { print " addLCIDMapEntry(map, " $3 " (short) " substr($1, 0, 6) ");" ; next } 2149 // { print " addLCIDMapEntry(map, " $3 ", (short) " substr($1, 0, 6) ");" ; next } 2150 // The lines of this script: 2151 // - eliminate comments 2152 // - eliminate questionable locales 2153 // - eliminate language-only locales 2154 // - eliminate the default LCID value 2155 // - eliminate a few other unneeded LCID values 2156 // - print language-only locale entries for x04* LCID values 2157 // (apparently Microsoft doesn't use language-only LCID values - 2158 // see http://www.microsoft.com/OpenType/otspec/name.htm 2159 // - print complete entries for all other LCID values 2160 // Run 2161 // awk -f awk-script langIDMap > statements 2162 addLCIDMapEntry(map, "ar", (short) 0x0401); 2163 addLCIDMapEntry(map, "bg", (short) 0x0402); 2164 addLCIDMapEntry(map, "ca", (short) 0x0403); 2165 addLCIDMapEntry(map, "zh", (short) 0x0404); 2166 addLCIDMapEntry(map, "cs", (short) 0x0405); 2167 addLCIDMapEntry(map, "da", (short) 0x0406); 2168 addLCIDMapEntry(map, "de", (short) 0x0407); 2169 addLCIDMapEntry(map, "el", (short) 0x0408); 2170 addLCIDMapEntry(map, "es", (short) 0x040a); 2171 addLCIDMapEntry(map, "fi", (short) 0x040b); 2172 addLCIDMapEntry(map, "fr", (short) 0x040c); 2173 addLCIDMapEntry(map, "iw", (short) 0x040d); 2174 addLCIDMapEntry(map, "hu", (short) 0x040e); 2175 addLCIDMapEntry(map, "is", (short) 0x040f); 2176 addLCIDMapEntry(map, "it", (short) 0x0410); 2177 addLCIDMapEntry(map, "ja", (short) 0x0411); 2178 addLCIDMapEntry(map, "ko", (short) 0x0412); 2179 addLCIDMapEntry(map, "nl", (short) 0x0413); 2180 addLCIDMapEntry(map, "no", (short) 0x0414); 2181 addLCIDMapEntry(map, "pl", (short) 0x0415); 2182 addLCIDMapEntry(map, "pt", (short) 0x0416); 2183 addLCIDMapEntry(map, "rm", (short) 0x0417); 2184 addLCIDMapEntry(map, "ro", (short) 0x0418); 2185 addLCIDMapEntry(map, "ru", (short) 0x0419); 2186 addLCIDMapEntry(map, "hr", (short) 0x041a); 2187 addLCIDMapEntry(map, "sk", (short) 0x041b); 2188 addLCIDMapEntry(map, "sq", (short) 0x041c); 2189 addLCIDMapEntry(map, "sv", (short) 0x041d); 2190 addLCIDMapEntry(map, "th", (short) 0x041e); 2191 addLCIDMapEntry(map, "tr", (short) 0x041f); 2192 addLCIDMapEntry(map, "ur", (short) 0x0420); 2193 addLCIDMapEntry(map, "in", (short) 0x0421); 2194 addLCIDMapEntry(map, "uk", (short) 0x0422); 2195 addLCIDMapEntry(map, "be", (short) 0x0423); 2196 addLCIDMapEntry(map, "sl", (short) 0x0424); 2197 addLCIDMapEntry(map, "et", (short) 0x0425); 2198 addLCIDMapEntry(map, "lv", (short) 0x0426); 2199 addLCIDMapEntry(map, "lt", (short) 0x0427); 2200 addLCIDMapEntry(map, "fa", (short) 0x0429); 2201 addLCIDMapEntry(map, "vi", (short) 0x042a); 2202 addLCIDMapEntry(map, "hy", (short) 0x042b); 2203 addLCIDMapEntry(map, "eu", (short) 0x042d); 2204 addLCIDMapEntry(map, "mk", (short) 0x042f); 2205 addLCIDMapEntry(map, "tn", (short) 0x0432); 2206 addLCIDMapEntry(map, "xh", (short) 0x0434); 2207 addLCIDMapEntry(map, "zu", (short) 0x0435); 2208 addLCIDMapEntry(map, "af", (short) 0x0436); 2209 addLCIDMapEntry(map, "ka", (short) 0x0437); 2210 addLCIDMapEntry(map, "fo", (short) 0x0438); 2211 addLCIDMapEntry(map, "hi", (short) 0x0439); 2212 addLCIDMapEntry(map, "mt", (short) 0x043a); 2213 addLCIDMapEntry(map, "se", (short) 0x043b); 2214 addLCIDMapEntry(map, "gd", (short) 0x043c); 2215 addLCIDMapEntry(map, "ms", (short) 0x043e); 2216 addLCIDMapEntry(map, "kk", (short) 0x043f); 2217 addLCIDMapEntry(map, "ky", (short) 0x0440); 2218 addLCIDMapEntry(map, "sw", (short) 0x0441); 2219 addLCIDMapEntry(map, "tt", (short) 0x0444); 2220 addLCIDMapEntry(map, "bn", (short) 0x0445); 2221 addLCIDMapEntry(map, "pa", (short) 0x0446); 2222 addLCIDMapEntry(map, "gu", (short) 0x0447); 2223 addLCIDMapEntry(map, "ta", (short) 0x0449); 2224 addLCIDMapEntry(map, "te", (short) 0x044a); 2225 addLCIDMapEntry(map, "kn", (short) 0x044b); 2226 addLCIDMapEntry(map, "ml", (short) 0x044c); 2227 addLCIDMapEntry(map, "mr", (short) 0x044e); 2228 addLCIDMapEntry(map, "sa", (short) 0x044f); 2229 addLCIDMapEntry(map, "mn", (short) 0x0450); 2230 addLCIDMapEntry(map, "cy", (short) 0x0452); 2231 addLCIDMapEntry(map, "gl", (short) 0x0456); 2232 addLCIDMapEntry(map, "dv", (short) 0x0465); 2233 addLCIDMapEntry(map, "qu", (short) 0x046b); 2234 addLCIDMapEntry(map, "mi", (short) 0x0481); 2235 addLCIDMapEntry(map, "ar_IQ", (short) 0x0801); 2236 addLCIDMapEntry(map, "zh_CN", (short) 0x0804); 2237 addLCIDMapEntry(map, "de_CH", (short) 0x0807); 2238 addLCIDMapEntry(map, "en_GB", (short) 0x0809); 2239 addLCIDMapEntry(map, "es_MX", (short) 0x080a); 2240 addLCIDMapEntry(map, "fr_BE", (short) 0x080c); 2241 addLCIDMapEntry(map, "it_CH", (short) 0x0810); 2242 addLCIDMapEntry(map, "nl_BE", (short) 0x0813); 2243 addLCIDMapEntry(map, "no_NO_NY", (short) 0x0814); 2244 addLCIDMapEntry(map, "pt_PT", (short) 0x0816); 2245 addLCIDMapEntry(map, "ro_MD", (short) 0x0818); 2246 addLCIDMapEntry(map, "ru_MD", (short) 0x0819); 2247 addLCIDMapEntry(map, "sr_CS", (short) 0x081a); 2248 addLCIDMapEntry(map, "sv_FI", (short) 0x081d); 2249 addLCIDMapEntry(map, "az_AZ", (short) 0x082c); 2250 addLCIDMapEntry(map, "se_SE", (short) 0x083b); 2251 addLCIDMapEntry(map, "ga_IE", (short) 0x083c); 2252 addLCIDMapEntry(map, "ms_BN", (short) 0x083e); 2253 addLCIDMapEntry(map, "uz_UZ", (short) 0x0843); 2254 addLCIDMapEntry(map, "qu_EC", (short) 0x086b); 2255 addLCIDMapEntry(map, "ar_EG", (short) 0x0c01); 2256 addLCIDMapEntry(map, "zh_HK", (short) 0x0c04); 2257 addLCIDMapEntry(map, "de_AT", (short) 0x0c07); 2258 addLCIDMapEntry(map, "en_AU", (short) 0x0c09); 2259 addLCIDMapEntry(map, "fr_CA", (short) 0x0c0c); 2260 addLCIDMapEntry(map, "sr_CS", (short) 0x0c1a); 2261 addLCIDMapEntry(map, "se_FI", (short) 0x0c3b); 2262 addLCIDMapEntry(map, "qu_PE", (short) 0x0c6b); 2263 addLCIDMapEntry(map, "ar_LY", (short) 0x1001); 2264 addLCIDMapEntry(map, "zh_SG", (short) 0x1004); 2265 addLCIDMapEntry(map, "de_LU", (short) 0x1007); 2266 addLCIDMapEntry(map, "en_CA", (short) 0x1009); 2267 addLCIDMapEntry(map, "es_GT", (short) 0x100a); 2268 addLCIDMapEntry(map, "fr_CH", (short) 0x100c); 2269 addLCIDMapEntry(map, "hr_BA", (short) 0x101a); 2270 addLCIDMapEntry(map, "ar_DZ", (short) 0x1401); 2271 addLCIDMapEntry(map, "zh_MO", (short) 0x1404); 2272 addLCIDMapEntry(map, "de_LI", (short) 0x1407); 2273 addLCIDMapEntry(map, "en_NZ", (short) 0x1409); 2274 addLCIDMapEntry(map, "es_CR", (short) 0x140a); 2275 addLCIDMapEntry(map, "fr_LU", (short) 0x140c); 2276 addLCIDMapEntry(map, "bs_BA", (short) 0x141a); 2277 addLCIDMapEntry(map, "ar_MA", (short) 0x1801); 2278 addLCIDMapEntry(map, "en_IE", (short) 0x1809); 2279 addLCIDMapEntry(map, "es_PA", (short) 0x180a); 2280 addLCIDMapEntry(map, "fr_MC", (short) 0x180c); 2281 addLCIDMapEntry(map, "sr_BA", (short) 0x181a); 2282 addLCIDMapEntry(map, "ar_TN", (short) 0x1c01); 2283 addLCIDMapEntry(map, "en_ZA", (short) 0x1c09); 2284 addLCIDMapEntry(map, "es_DO", (short) 0x1c0a); 2285 addLCIDMapEntry(map, "sr_BA", (short) 0x1c1a); 2286 addLCIDMapEntry(map, "ar_OM", (short) 0x2001); 2287 addLCIDMapEntry(map, "en_JM", (short) 0x2009); 2288 addLCIDMapEntry(map, "es_VE", (short) 0x200a); 2289 addLCIDMapEntry(map, "ar_YE", (short) 0x2401); 2290 addLCIDMapEntry(map, "es_CO", (short) 0x240a); 2291 addLCIDMapEntry(map, "ar_SY", (short) 0x2801); 2292 addLCIDMapEntry(map, "en_BZ", (short) 0x2809); 2293 addLCIDMapEntry(map, "es_PE", (short) 0x280a); 2294 addLCIDMapEntry(map, "ar_JO", (short) 0x2c01); 2295 addLCIDMapEntry(map, "en_TT", (short) 0x2c09); 2296 addLCIDMapEntry(map, "es_AR", (short) 0x2c0a); 2297 addLCIDMapEntry(map, "ar_LB", (short) 0x3001); 2298 addLCIDMapEntry(map, "en_ZW", (short) 0x3009); 2299 addLCIDMapEntry(map, "es_EC", (short) 0x300a); 2300 addLCIDMapEntry(map, "ar_KW", (short) 0x3401); 2301 addLCIDMapEntry(map, "en_PH", (short) 0x3409); 2302 addLCIDMapEntry(map, "es_CL", (short) 0x340a); 2303 addLCIDMapEntry(map, "ar_AE", (short) 0x3801); 2304 addLCIDMapEntry(map, "es_UY", (short) 0x380a); 2305 addLCIDMapEntry(map, "ar_BH", (short) 0x3c01); 2306 addLCIDMapEntry(map, "es_PY", (short) 0x3c0a); 2307 addLCIDMapEntry(map, "ar_QA", (short) 0x4001); 2308 addLCIDMapEntry(map, "es_BO", (short) 0x400a); 2309 addLCIDMapEntry(map, "es_SV", (short) 0x440a); 2310 addLCIDMapEntry(map, "es_HN", (short) 0x480a); 2311 addLCIDMapEntry(map, "es_NI", (short) 0x4c0a); 2312 addLCIDMapEntry(map, "es_PR", (short) 0x500a); 2313 2314 lcidMap = map; 2315 } 2316 2317 public static int getNumFonts() { 2318 return physicalFonts.size()+maxCompFont; 2319 } 2320 2321 private static boolean fontSupportsEncoding(Font font, String encoding) { 2322 return getFont2D(font).supportsEncoding(encoding); 2323 } 2324 2325 public synchronized static native String getFontPath(boolean noType1Fonts); 2326 public synchronized static native void setNativeFontPath(String fontPath); 2327 2328 2329 private static Thread fileCloser = null; 2330 static Vector<File> tmpFontFiles = null; 2331 2332 public static Font2D createFont2D(File fontFile, int fontFormat, 2333 boolean isCopy, 2334 CreatedFontTracker tracker) 2335 throws FontFormatException { 2336 2337 String fontFilePath = fontFile.getPath(); 2338 FileFont font2D = null; 2339 final File fFile = fontFile; 2340 final CreatedFontTracker _tracker = tracker; 2341 try { 2342 switch (fontFormat) { 2343 case Font.TRUETYPE_FONT: 2344 font2D = new TrueTypeFont(fontFilePath, null, 0, true); 2345 break; 2346 case Font.TYPE1_FONT: 2347 font2D = new Type1Font(fontFilePath, null, isCopy); 2348 break; 2349 default: 2350 throw new FontFormatException("Unrecognised Font Format"); 2351 } 2352 } catch (FontFormatException e) { 2353 if (isCopy) { 2354 java.security.AccessController.doPrivileged( 2355 new java.security.PrivilegedAction() { 2356 public Object run() { 2357 if (_tracker != null) { 2358 _tracker.subBytes((int)fFile.length()); 2359 } 2360 fFile.delete(); 2361 return null; 2362 } 2363 }); 2364 } 2365 throw(e); 2366 } 2367 if (isCopy) { 2368 font2D.setFileToRemove(fontFile, tracker); 2369 synchronized (FontManager.class) { 2370 2371 if (tmpFontFiles == null) { 2372 tmpFontFiles = new Vector<File>(); 2373 } 2374 tmpFontFiles.add(fontFile); 2375 2376 if (fileCloser == null) { 2377 final Runnable fileCloserRunnable = new Runnable() { 2378 public void run() { 2379 java.security.AccessController.doPrivileged( 2380 new java.security.PrivilegedAction() { 2381 public Object run() { 2382 2383 for (int i=0;i<CHANNELPOOLSIZE;i++) { 2384 if (fontFileCache[i] != null) { 2385 try { 2386 fontFileCache[i].close(); 2387 } catch (Exception e) { 2388 } 2389 } 2390 } 2391 if (tmpFontFiles != null) { 2392 File[] files = new File[tmpFontFiles.size()]; 2393 files = tmpFontFiles.toArray(files); 2394 for (int f=0; f<files.length;f++) { 2395 try { 2396 files[f].delete(); 2397 } catch (Exception e) { 2398 } 2399 } 2400 } 2401 2402 return null; 2403 } 2404 2405 }); 2406 } 2407 }; 2408 java.security.AccessController.doPrivileged( 2409 new java.security.PrivilegedAction() { 2410 public Object run() { 2411 /* The thread must be a member of a thread group 2412 * which will not get GCed before VM exit. 2413 * Make its parent the top-level thread group. 2414 */ 2415 ThreadGroup tg = 2416 Thread.currentThread().getThreadGroup(); 2417 for (ThreadGroup tgn = tg; 2418 tgn != null; 2419 tg = tgn, tgn = tg.getParent()); 2420 fileCloser = new Thread(tg, fileCloserRunnable); 2421 Runtime.getRuntime().addShutdownHook(fileCloser); 2422 return null; 2423 } 2424 }); 2425 } 2426 } 2427 } 2428 return font2D; 2429 } 2430 2431 /* remind: used in X11GraphicsEnvironment and called often enough 2432 * that we ought to obsolete this code 2433 */ 2434 public synchronized static String getFullNameByFileName(String fileName) { 2435 PhysicalFont[] physFonts = getPhysicalFonts(); 2436 for (int i=0;i<physFonts.length;i++) { 2437 if (physFonts[i].platName.equals(fileName)) { 2438 return (physFonts[i].getFontName(null)); 2439 } 2440 } 2441 return null; 2442 } 2443 2444 /* 2445 * This is called when font is determined to be invalid/bad. 2446 * It designed to be called (for example) by the font scaler 2447 * when in processing a font file it is discovered to be incorrect. 2448 * This is different than the case where fonts are discovered to 2449 * be incorrect during initial verification, as such fonts are 2450 * never registered. 2451 * Handles to this font held are re-directed to a default font. 2452 * This default may not be an ideal substitute buts it better than 2453 * crashing This code assumes a PhysicalFont parameter as it doesn't 2454 * make sense for a Composite to be "bad". 2455 */ 2456 public static synchronized void deRegisterBadFont(Font2D font2D) { 2457 if (!(font2D instanceof PhysicalFont)) { 2458 /* We should never reach here, but just in case */ 2459 return; 2460 } else { 2461 if (logging) { 2462 logger.severe("Deregister bad font: " + font2D); 2463 } 2464 replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont()); 2465 } 2466 } 2467 2468 /* 2469 * This encapsulates all the work that needs to be done when a 2470 * Font2D is replaced by a different Font2D. 2471 */ 2472 public static synchronized void replaceFont(PhysicalFont oldFont, 2473 PhysicalFont newFont) { 2474 2475 if (oldFont.handle.font2D != oldFont) { 2476 /* already done */ 2477 return; 2478 } 2479 2480 /* If we try to replace the font with itself, that won't work, 2481 * so pick any alternative physical font 2482 */ 2483 if (oldFont == newFont) { 2484 if (logging) { 2485 logger.severe("Can't replace bad font with itself " + oldFont); 2486 } 2487 PhysicalFont[] physFonts = getPhysicalFonts(); 2488 for (int i=0; i<physFonts.length;i++) { 2489 if (physFonts[i] != newFont) { 2490 newFont = physFonts[i]; 2491 break; 2492 } 2493 } 2494 if (oldFont == newFont) { 2495 if (logging) { 2496 logger.severe("This is bad. No good physicalFonts found."); 2497 } 2498 return; 2499 } 2500 } 2501 2502 /* eliminate references to this font, so it won't be located 2503 * by future callers, and will be eligible for GC when all 2504 * references are removed 2505 */ 2506 oldFont.handle.font2D = newFont; 2507 physicalFonts.remove(oldFont.fullName); 2508 fullNameToFont.remove(oldFont.fullName.toLowerCase(Locale.ENGLISH)); 2509 FontFamily.remove(oldFont); 2510 2511 if (localeFullNamesToFont != null) { 2512 Map.Entry[] mapEntries = 2513 (Map.Entry[])localeFullNamesToFont.entrySet(). 2514 toArray(new Map.Entry[0]); 2515 /* Should I be replacing these, or just I just remove 2516 * the names from the map? 2517 */ 2518 for (int i=0; i<mapEntries.length;i++) { 2519 if (mapEntries[i].getValue() == oldFont) { 2520 try { 2521 mapEntries[i].setValue(newFont); 2522 } catch (Exception e) { 2523 /* some maps don't support this operation. 2524 * In this case just give up and remove the entry. 2525 */ 2526 localeFullNamesToFont.remove(mapEntries[i].getKey()); 2527 } 2528 } 2529 } 2530 } 2531 2532 for (int i=0; i<maxCompFont; i++) { 2533 /* Deferred initialization of composites shouldn't be 2534 * a problem for this case, since a font must have been 2535 * initialised to be discovered to be bad. 2536 * Some JRE composites on Solaris use two versions of the same 2537 * font. The replaced font isn't bad, just "smaller" so there's 2538 * no need to make the slot point to the new font. 2539 * Since composites have a direct reference to the Font2D (not 2540 * via a handle) making this substitution is not safe and could 2541 * cause an additional problem and so this substitution is 2542 * warranted only when a font is truly "bad" and could cause 2543 * a crash. So we now replace it only if its being substituted 2544 * with some font other than a fontconfig rank font 2545 * Since in practice a substitution will have the same rank 2546 * this may never happen, but the code is safer even if its 2547 * also now a no-op. 2548 * The only obvious "glitch" from this stems from the current 2549 * implementation that when asked for the number of glyphs in a 2550 * composite it lies and returns the number in slot 0 because 2551 * composite glyphs aren't contiguous. Since we live with that 2552 * we can live with the glitch that depending on how it was 2553 * initialised a composite may return different values for this. 2554 * Fixing the issues with composite glyph ids is tricky as 2555 * there are exclusion ranges and unlike other fonts even the 2556 * true "numGlyphs" isn't a contiguous range. Likely the only 2557 * solution is an API that returns an array of glyph ranges 2558 * which takes precedence over the existing API. That might 2559 * also need to address excluding ranges which represent a 2560 * code point supported by an earlier component. 2561 */ 2562 if (newFont.getRank() > Font2D.FONT_CONFIG_RANK) { 2563 compFonts[i].replaceComponentFont(oldFont, newFont); 2564 } 2565 } 2566 } 2567 2568 private static synchronized void loadLocaleNames() { 2569 if (localeFullNamesToFont != null) { 2570 return; 2571 } 2572 localeFullNamesToFont = new HashMap<String, TrueTypeFont>(); 2573 Font2D[] fonts = getRegisteredFonts(); 2574 for (int i=0; i<fonts.length; i++) { 2575 if (fonts[i] instanceof TrueTypeFont) { 2576 TrueTypeFont ttf = (TrueTypeFont)fonts[i]; 2577 String[] fullNames = ttf.getAllFullNames(); 2578 for (int n=0; n<fullNames.length; n++) { 2579 localeFullNamesToFont.put(fullNames[n], ttf); 2580 } 2581 FontFamily family = FontFamily.getFamily(ttf.familyName); 2582 if (family != null) { 2583 FontFamily.addLocaleNames(family, ttf.getAllFamilyNames()); 2584 } 2585 } 2586 } 2587 } 2588 2589 /* This replicate the core logic of findFont2D but operates on 2590 * all the locale names. This hasn't been merged into findFont2D to 2591 * keep the logic simpler and reduce overhead, since this case is 2592 * almost never used. The main case in which it is called is when 2593 * a bogus font name is used and we need to check all possible names 2594 * before returning the default case. 2595 */ 2596 private static Font2D findFont2DAllLocales(String name, int style) { 2597 2598 if (logging) { 2599 logger.info("Searching localised font names for:" + name); 2600 } 2601 2602 /* If reach here and no match has been located, then if we have 2603 * not yet built the map of localeFullNamesToFont for TT fonts, do so 2604 * now. This method must be called after all fonts have been loaded. 2605 */ 2606 if (localeFullNamesToFont == null) { 2607 loadLocaleNames(); 2608 } 2609 String lowerCaseName = name.toLowerCase(); 2610 Font2D font = null; 2611 2612 /* First see if its a family name. */ 2613 FontFamily family = FontFamily.getLocaleFamily(lowerCaseName); 2614 if (family != null) { 2615 font = family.getFont(style); 2616 if (font == null) { 2617 font = family.getClosestStyle(style); 2618 } 2619 if (font != null) { 2620 return font; 2621 } 2622 } 2623 2624 /* If it wasn't a family name, it should be a full name. */ 2625 synchronized (FontManager.class) { 2626 font = localeFullNamesToFont.get(name); 2627 } 2628 if (font != null) { 2629 if (font.style == style || style == Font.PLAIN) { 2630 return font; 2631 } else { 2632 family = FontFamily.getFamily(font.getFamilyName(null)); 2633 if (family != null) { 2634 Font2D familyFont = family.getFont(style); 2635 /* We exactly matched the requested style, use it! */ 2636 if (familyFont != null) { 2637 return familyFont; 2638 } else { 2639 familyFont = family.getClosestStyle(style); 2640 if (familyFont != null) { 2641 /* The next check is perhaps one 2642 * that shouldn't be done. ie if we get this 2643 * far we have probably as close a match as we 2644 * are going to get. We could load all fonts to 2645 * see if somehow some parts of the family are 2646 * loaded but not all of it. 2647 * This check is commented out for now. 2648 */ 2649 if (!familyFont.canDoStyle(style)) { 2650 familyFont = null; 2651 } 2652 return familyFont; 2653 } 2654 } 2655 } 2656 } 2657 } 2658 return font; 2659 } 2660 2661 /* Supporting "alternate" composite fonts on 2D graphics objects 2662 * is accessed by the application by calling methods on the local 2663 * GraphicsEnvironment. The overall implementation is described 2664 * in one place, here, since otherwise the implementation is spread 2665 * around it may be difficult to track. 2666 * The methods below call into SunGraphicsEnvironment which creates a 2667 * new FontConfiguration instance. The FontConfiguration class, 2668 * and its platform sub-classes are updated to take parameters requesting 2669 * these behaviours. This is then used to create new composite font 2670 * instances. Since this calls the initCompositeFont method in 2671 * SunGraphicsEnvironment it performs the same initialization as is 2672 * performed normally. There may be some duplication of effort, but 2673 * that code is already written to be able to perform properly if called 2674 * to duplicate work. The main difference is that if we detect we are 2675 * running in an applet/browser/Java plugin environment these new fonts 2676 * are not placed in the "default" maps but into an AppContext instance. 2677 * The font lookup mechanism in java.awt.Font.getFont2D() is also updated 2678 * so that look-up for composite fonts will in that case always 2679 * do a lookup rather than returning a cached result. 2680 * This is inefficient but necessary else singleton java.awt.Font 2681 * instances would not retrieve the correct Font2D for the appcontext. 2682 * sun.font.FontManager.findFont2D is also updated to that it uses 2683 * a name map cache specific to that appcontext. 2684 * 2685 * Getting an AppContext is expensive, so there is a global variable 2686 * that records whether these methods have ever been called and can 2687 * avoid the expense for almost all applications. Once the correct 2688 * CompositeFont is associated with the Font, everything should work 2689 * through existing mechanisms. 2690 * A special case is that GraphicsEnvironment.getAllFonts() must 2691 * return an AppContext specific list. 2692 * 2693 * Calling the methods below is "heavyweight" but it is expected that 2694 * these methods will be called very rarely. 2695 * 2696 * If usingPerAppContextComposites is true, we are in "applet" 2697 * (eg browser) enviroment and at least one context has selected 2698 * an alternate composite font behaviour. 2699 * If usingAlternateComposites is true, we are not in an "applet" 2700 * environment and the (single) application has selected 2701 * an alternate composite font behaviour. 2702 * 2703 * - Printing: The implementation delegates logical fonts to an AWT 2704 * mechanism which cannot use these alternate configurations. 2705 * We can detect that alternate fonts are in use and back-off to 2D, but 2706 * that uses outlines. Much of this can be fixed with additional work 2707 * but that may have to wait. The results should be correct, just not 2708 * optimal. 2709 */ 2710 private static final Object altJAFontKey = new Object(); 2711 private static final Object localeFontKey = new Object(); 2712 private static final Object proportionalFontKey = new Object(); 2713 public static boolean usingPerAppContextComposites = false; 2714 private static boolean usingAlternateComposites = false; 2715 2716 /* These values are used only if we are running as a standalone 2717 * application, as determined by maybeMultiAppContext(); 2718 */ 2719 private static boolean gAltJAFont = false; 2720 private static boolean gLocalePref = false; 2721 private static boolean gPropPref = false; 2722 2723 /* This method doesn't check if alternates are selected in this app 2724 * context. Its used by the FontMetrics caching code which in such 2725 * a case cannot retrieve a cached metrics solely on the basis of 2726 * the Font.equals() method since it needs to also check if the Font2D 2727 * is the same. 2728 * We also use non-standard composites for Swing native L&F fonts on 2729 * Windows. In that case the policy is that the metrics reported are 2730 * based solely on the physical font in the first slot which is the 2731 * visible java.awt.Font. So in that case the metrics cache which tests 2732 * the Font does what we want. In the near future when we expand the GTK 2733 * logical font definitions we may need to revisit this if GTK reports 2734 * combined metrics instead. For now though this test can be simple. 2735 */ 2736 static boolean maybeUsingAlternateCompositeFonts() { 2737 return usingAlternateComposites || usingPerAppContextComposites; 2738 } 2739 2740 public static boolean usingAlternateCompositeFonts() { 2741 return (usingAlternateComposites || 2742 (usingPerAppContextComposites && 2743 AppContext.getAppContext().get(CompositeFont.class) != null)); 2744 } 2745 2746 private static boolean maybeMultiAppContext() { 2747 Boolean appletSM = (Boolean) 2748 java.security.AccessController.doPrivileged( 2749 new java.security.PrivilegedAction() { 2750 public Object run() { 2751 SecurityManager sm = System.getSecurityManager(); 2752 return new Boolean 2753 (sm instanceof sun.applet.AppletSecurity); 2754 } 2755 }); 2756 return appletSM.booleanValue(); 2757 } 2758 2759 /* Modifies the behaviour of a subsequent call to preferLocaleFonts() 2760 * to use Mincho instead of Gothic for dialoginput in JA locales 2761 * on windows. Not needed on other platforms. 2762 */ 2763 public static synchronized void useAlternateFontforJALocales() { 2764 2765 if (!isWindows) { 2766 return; 2767 } 2768 2769 initSGEnv(); 2770 if (!maybeMultiAppContext()) { 2771 gAltJAFont = true; 2772 } else { 2773 AppContext appContext = AppContext.getAppContext(); 2774 appContext.put(altJAFontKey, altJAFontKey); 2775 } 2776 } 2777 2778 public static boolean usingAlternateFontforJALocales() { 2779 if (!maybeMultiAppContext()) { 2780 return gAltJAFont; 2781 } else { 2782 AppContext appContext = AppContext.getAppContext(); 2783 return appContext.get(altJAFontKey) == altJAFontKey; 2784 } 2785 } 2786 2787 public static synchronized void preferLocaleFonts() { 2788 2789 initSGEnv(); 2790 2791 /* Test if re-ordering will have any effect */ 2792 if (!FontConfiguration.willReorderForStartupLocale()) { 2793 return; 2794 } 2795 2796 if (!maybeMultiAppContext()) { 2797 if (gLocalePref == true) { 2798 return; 2799 } 2800 gLocalePref = true; 2801 sgEnv.createCompositeFonts(fontNameCache, gLocalePref, gPropPref); 2802 usingAlternateComposites = true; 2803 } else { 2804 AppContext appContext = AppContext.getAppContext(); 2805 if (appContext.get(localeFontKey) == localeFontKey) { 2806 return; 2807 } 2808 appContext.put(localeFontKey, localeFontKey); 2809 boolean acPropPref = 2810 appContext.get(proportionalFontKey) == proportionalFontKey; 2811 ConcurrentHashMap<String, Font2D> 2812 altNameCache = new ConcurrentHashMap<String, Font2D> (); 2813 /* If there is an existing hashtable, we can drop it. */ 2814 appContext.put(CompositeFont.class, altNameCache); 2815 usingPerAppContextComposites = true; 2816 sgEnv.createCompositeFonts(altNameCache, true, acPropPref); 2817 } 2818 } 2819 2820 public static synchronized void preferProportionalFonts() { 2821 2822 /* If no proportional fonts are configured, there's no need 2823 * to take any action. 2824 */ 2825 if (!FontConfiguration.hasMonoToPropMap()) { 2826 return; 2827 } 2828 2829 initSGEnv(); 2830 2831 if (!maybeMultiAppContext()) { 2832 if (gPropPref == true) { 2833 return; 2834 } 2835 gPropPref = true; 2836 sgEnv.createCompositeFonts(fontNameCache, gLocalePref, gPropPref); 2837 usingAlternateComposites = true; 2838 } else { 2839 AppContext appContext = AppContext.getAppContext(); 2840 if (appContext.get(proportionalFontKey) == proportionalFontKey) { 2841 return; 2842 } 2843 appContext.put(proportionalFontKey, proportionalFontKey); 2844 boolean acLocalePref = 2845 appContext.get(localeFontKey) == localeFontKey; 2846 ConcurrentHashMap<String, Font2D> 2847 altNameCache = new ConcurrentHashMap<String, Font2D> (); 2848 /* If there is an existing hashtable, we can drop it. */ 2849 appContext.put(CompositeFont.class, altNameCache); 2850 usingPerAppContextComposites = true; 2851 sgEnv.createCompositeFonts(altNameCache, acLocalePref, true); 2852 } 2853 } 2854 2855 private static HashSet<String> installedNames = null; 2856 private static HashSet<String> getInstalledNames() { 2857 if (installedNames == null) { 2858 Locale l = sgEnv.getSystemStartupLocale(); 2859 String[] installedFamilies = sgEnv.getInstalledFontFamilyNames(l); 2860 Font[] installedFonts = sgEnv.getAllInstalledFonts(); 2861 HashSet<String> names = new HashSet<String>(); 2862 for (int i=0; i<installedFamilies.length; i++) { 2863 names.add(installedFamilies[i].toLowerCase(l)); 2864 } 2865 for (int i=0; i<installedFonts.length; i++) { 2866 names.add(installedFonts[i].getFontName(l).toLowerCase(l)); 2867 } 2868 installedNames = names; 2869 } 2870 return installedNames; 2871 } 2872 2873 /* Keys are used to lookup per-AppContext Hashtables */ 2874 private static final Object regFamilyKey = new Object(); 2875 private static final Object regFullNameKey = new Object(); 2876 private static Hashtable<String,FontFamily> createdByFamilyName; 2877 private static Hashtable<String,Font2D> createdByFullName; 2878 private static boolean fontsAreRegistered = false; 2879 private static boolean fontsAreRegisteredPerAppContext = false; 2880 2881 public static boolean registerFont(Font font) { 2882 /* This method should not be called with "null". 2883 * It is the caller's responsibility to ensure that. 2884 */ 2885 if (font == null) { 2886 return false; 2887 } 2888 2889 /* Initialise these objects only once we start to use this API */ 2890 synchronized (regFamilyKey) { 2891 if (createdByFamilyName == null) { 2892 createdByFamilyName = new Hashtable<String,FontFamily>(); 2893 createdByFullName = new Hashtable<String,Font2D>(); 2894 } 2895 } 2896 2897 if (!isCreatedFont(font)) { 2898 return false; 2899 } 2900 if (sgEnv == null) { 2901 initSGEnv(); 2902 } 2903 /* We want to ensure that this font cannot override existing 2904 * installed fonts. Check these conditions : 2905 * - family name is not that of an installed font 2906 * - full name is not that of an installed font 2907 * - family name is not the same as the full name of an installed font 2908 * - full name is not the same as the family name of an installed font 2909 * The last two of these may initially look odd but the reason is 2910 * that (unfortunately) Font constructors do not distinuguish these. 2911 * An extreme example of such a problem would be a font which has 2912 * family name "Dialog.Plain" and full name of "Dialog". 2913 * The one arguably overly stringent restriction here is that if an 2914 * application wants to supply a new member of an existing family 2915 * It will get rejected. But since the JRE can perform synthetic 2916 * styling in many cases its not necessary. 2917 * We don't apply the same logic to registered fonts. If apps want 2918 * to do this lets assume they have a reason. It won't cause problems 2919 * except for themselves. 2920 */ 2921 HashSet<String> names = getInstalledNames(); 2922 Locale l = sgEnv.getSystemStartupLocale(); 2923 String familyName = font.getFamily(l).toLowerCase(); 2924 String fullName = font.getFontName(l).toLowerCase(); 2925 if (names.contains(familyName) || names.contains(fullName)) { 2926 return false; 2927 } 2928 2929 /* Checks passed, now register the font */ 2930 Hashtable<String,FontFamily> familyTable; 2931 Hashtable<String,Font2D> fullNameTable; 2932 if (!maybeMultiAppContext()) { 2933 familyTable = createdByFamilyName; 2934 fullNameTable = createdByFullName; 2935 fontsAreRegistered = true; 2936 } else { 2937 AppContext appContext = AppContext.getAppContext(); 2938 familyTable = 2939 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); 2940 fullNameTable = 2941 (Hashtable<String,Font2D>)appContext.get(regFullNameKey); 2942 if (familyTable == null) { 2943 familyTable = new Hashtable<String,FontFamily>(); 2944 fullNameTable = new Hashtable<String,Font2D>(); 2945 appContext.put(regFamilyKey, familyTable); 2946 appContext.put(regFullNameKey, fullNameTable); 2947 } 2948 fontsAreRegisteredPerAppContext = true; 2949 } 2950 /* Create the FontFamily and add font to the tables */ 2951 Font2D font2D = getFont2D(font); 2952 int style = font2D.getStyle(); 2953 FontFamily family = familyTable.get(familyName); 2954 if (family == null) { 2955 family = new FontFamily(font.getFamily(l)); 2956 familyTable.put(familyName, family); 2957 } 2958 /* Remove name cache entries if not using app contexts. 2959 * To accommodate a case where code may have registered first a plain 2960 * family member and then used it and is now registering a bold family 2961 * member, we need to remove all members of the family, so that the 2962 * new style can get picked up rather than continuing to synthesise. 2963 */ 2964 if (fontsAreRegistered) { 2965 removeFromCache(family.getFont(Font.PLAIN)); 2966 removeFromCache(family.getFont(Font.BOLD)); 2967 removeFromCache(family.getFont(Font.ITALIC)); 2968 removeFromCache(family.getFont(Font.BOLD|Font.ITALIC)); 2969 removeFromCache(fullNameTable.get(fullName)); 2970 } 2971 family.setFont(font2D, style); 2972 fullNameTable.put(fullName, font2D); 2973 return true; 2974 } 2975 2976 /* Remove from the name cache all references to the Font2D */ 2977 private static void removeFromCache(Font2D font) { 2978 if (font == null) { 2979 return; 2980 } 2981 String[] keys = (String[])(fontNameCache.keySet().toArray(STR_ARRAY)); 2982 for (int k=0; k<keys.length;k++) { 2983 if (fontNameCache.get(keys[k]) == font) { 2984 fontNameCache.remove(keys[k]); 2985 } 2986 } 2987 } 2988 2989 // It may look odd to use TreeMap but its more convenient to the caller. 2990 public static TreeMap<String, String> getCreatedFontFamilyNames() { 2991 2992 Hashtable<String,FontFamily> familyTable; 2993 if (fontsAreRegistered) { 2994 familyTable = createdByFamilyName; 2995 } else if (fontsAreRegisteredPerAppContext) { 2996 AppContext appContext = AppContext.getAppContext(); 2997 familyTable = 2998 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); 2999 } else { 3000 return null; 3001 } 3002 3003 Locale l = sgEnv.getSystemStartupLocale(); 3004 synchronized (familyTable) { 3005 TreeMap<String, String> map = new TreeMap<String, String>(); 3006 for (FontFamily f : familyTable.values()) { 3007 Font2D font2D = f.getFont(Font.PLAIN); 3008 if (font2D == null) { 3009 font2D = f.getClosestStyle(Font.PLAIN); 3010 } 3011 String name = font2D.getFamilyName(l); 3012 map.put(name.toLowerCase(l), name); 3013 } 3014 return map; 3015 } 3016 } 3017 3018 public static Font[] getCreatedFonts() { 3019 3020 Hashtable<String,Font2D> nameTable; 3021 if (fontsAreRegistered) { 3022 nameTable = createdByFullName; 3023 } else if (fontsAreRegisteredPerAppContext) { 3024 AppContext appContext = AppContext.getAppContext(); 3025 nameTable = 3026 (Hashtable<String,Font2D>)appContext.get(regFullNameKey); 3027 } else { 3028 return null; 3029 } 3030 3031 Locale l = sgEnv.getSystemStartupLocale(); 3032 synchronized (nameTable) { 3033 Font[] fonts = new Font[nameTable.size()]; 3034 int i=0; 3035 for (Font2D font2D : nameTable.values()) { 3036 fonts[i++] = new Font(font2D.getFontName(l), Font.PLAIN, 1); 3037 } 3038 return fonts; 3039 } 3040 } 3041 3042 /* Begin support for GTK Look and Feel - query libfontconfig and 3043 * return a composite Font to Swing that uses the desktop font(s). 3044 */ 3045 3046 /* A small "map" from GTK/fontconfig names to the equivalent JDK 3047 * logical font name. 3048 */ 3049 private static final String[][] nameMap = { 3050 {"sans", "sansserif"}, 3051 {"sans-serif", "sansserif"}, 3052 {"serif", "serif"}, 3053 {"monospace", "monospaced"} 3054 }; 3055 3056 public static String mapFcName(String name) { 3057 for (int i = 0; i < nameMap.length; i++) { 3058 if (name.equals(nameMap[i][0])) { 3059 return nameMap[i][1]; 3060 } 3061 } 3062 return null; 3063 } 3064 3065 /* fontconfig recognises slants roman, italic, as well as oblique, 3066 * and a slew of weights, where the ones that matter here are 3067 * regular and bold. 3068 * To fully qualify what we want, we can for example ask for (eg) 3069 * Font.PLAIN : "serif:regular:roman" 3070 * Font.BOLD : "serif:bold:roman" 3071 * Font.ITALIC : "serif:regular:italic" 3072 * Font.BOLD|Font.ITALIC : "serif:bold:italic" 3073 */ 3074 private static String[] fontConfigNames = { 3075 "sans:regular:roman", 3076 "sans:bold:roman", 3077 "sans:regular:italic", 3078 "sans:bold:italic", 3079 3080 "serif:regular:roman", 3081 "serif:bold:roman", 3082 "serif:regular:italic", 3083 "serif:bold:italic", 3084 3085 "monospace:regular:roman", 3086 "monospace:bold:roman", 3087 "monospace:regular:italic", 3088 "monospace:bold:italic", 3089 }; 3090 3091 /* This class is just a data structure. 3092 */ 3093 private static class FontConfigInfo { 3094 String fcName; // eg sans 3095 String fcFamily; // eg sans 3096 String jdkName; // eg sansserif 3097 int style; // eg 0=PLAIN 3098 String familyName; // eg Bitstream Vera Sans 3099 String fontFile; // eg /usr/X11/lib/fonts/foo.ttf 3100 //boolean preferBitmaps; // if embedded bitmaps preferred over AA 3101 CompositeFont compFont; // null if not yet created/known. 3102 } 3103 3104 3105 private static String getFCLocaleStr() { 3106 Locale l = SunToolkit.getStartupLocale(); 3107 String localeStr = l.getLanguage(); 3108 String country = l.getCountry(); 3109 if (!country.equals("")) { 3110 localeStr = localeStr + "-" + country; 3111 } 3112 return localeStr; 3113 } 3114 3115 private static native int 3116 getFontConfigAASettings(String locale, String fcFamily); 3117 3118 /* This is public solely so that for debugging purposes it can be called 3119 * with other names, which might (eg) include a size, eg "sans-24" 3120 * The return value is a text aa rendering hint value. 3121 * Normally we should call the no-args version. 3122 */ 3123 public static Object getFontConfigAAHint(String fcFamily) { 3124 if (isWindows) { 3125 return null; 3126 } else { 3127 int hint = getFontConfigAASettings(getFCLocaleStr(), fcFamily); 3128 if (hint < 0) { 3129 return null; 3130 } else { 3131 return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, 3132 hint); 3133 } 3134 } 3135 } 3136 3137 /* Called from code that needs to know what are the AA settings 3138 * that apps using FC would pick up for the default desktop font. 3139 * Note apps can change the default desktop font. etc, so this 3140 * isn't certain to be right but its going to correct for most cases. 3141 * Native return values map to the text aa values in sun.awt.SunHints. 3142 * which is used to look up the renderinghint value object. 3143 */ 3144 public static Object getFontConfigAAHint() { 3145 return getFontConfigAAHint("sans"); 3146 } 3147 3148 /* This array has the array elements created in Java code and is 3149 * passed down to native to be filled in. 3150 */ 3151 private static FontConfigInfo[] fontConfigFonts; 3152 3153 /* Return an array of FontConfigInfo structs describing the primary 3154 * font located for each of fontconfig/GTK/Pango's logical font names. 3155 */ 3156 private static native void getFontConfig(String locale, 3157 FontConfigInfo[] fonts); 3158 3159 3160 /* This can be made public if it's needed to force a re-read 3161 * rather than using the cached values. The re-read would be needed 3162 * only if some event signalled that the fontconfig has changed. 3163 * In that event this method would need to return directly the array 3164 * to be used by the caller in case it subsequently changed. 3165 */ 3166 private static void initFontConfigFonts() { 3167 3168 if (fontConfigFonts != null) { 3169 return; 3170 } 3171 3172 if (isWindows) { 3173 return; 3174 } 3175 3176 long t0 = 0; 3177 if (logging) { 3178 t0 = System.currentTimeMillis(); 3179 } 3180 3181 FontConfigInfo[] fontArr = new FontConfigInfo[fontConfigNames.length]; 3182 for (int i = 0; i< fontArr.length; i++) { 3183 fontArr[i] = new FontConfigInfo(); 3184 fontArr[i].fcName = fontConfigNames[i]; 3185 int colonPos = fontArr[i].fcName.indexOf(':'); 3186 fontArr[i].fcFamily = fontArr[i].fcName.substring(0, colonPos); 3187 fontArr[i].jdkName = mapFcName(fontArr[i].fcFamily); 3188 fontArr[i].style = i % 4; // depends on array order. 3189 } 3190 getFontConfig(getFCLocaleStr(), fontArr); 3191 fontConfigFonts = fontArr; 3192 3193 if (logging) { 3194 long t1 = System.currentTimeMillis(); 3195 logger.info("Time spent accessing fontconfig="+(t1-t0)+"ms."); 3196 3197 for (int i = 0; i< fontConfigFonts.length; i++) { 3198 FontConfigInfo fci = fontConfigFonts[i]; 3199 logger.info("FC font " + fci.fcName+" maps to family " + 3200 fci.familyName + " in file " + fci.fontFile); 3201 } 3202 } 3203 } 3204 3205 private static PhysicalFont registerFromFcInfo(FontConfigInfo fcInfo) { 3206 3207 /* If it's a TTC file we need to know that as we will need to 3208 * make sure we return the right font */ 3209 int offset = fcInfo.fontFile.length()-4; 3210 if (offset <= 0) { 3211 return null; 3212 } 3213 String ext = fcInfo.fontFile.substring(offset).toLowerCase(); 3214 boolean isTTC = ext.equals(".ttc"); 3215 3216 /* If this file is already registered, can just return its font. 3217 * However we do need to check in case it's a TTC as we need 3218 * a specific font, so rather than directly returning it, let 3219 * findFont2D resolve that. 3220 */ 3221 PhysicalFont physFont = registeredFontFiles.get(fcInfo.fontFile); 3222 if (physFont != null) { 3223 if (isTTC) { 3224 Font2D f2d = findFont2D(fcInfo.familyName, 3225 fcInfo.style, NO_FALLBACK); 3226 if (f2d instanceof PhysicalFont) { /* paranoia */ 3227 return (PhysicalFont)f2d; 3228 } else { 3229 return null; 3230 } 3231 } else { 3232 return physFont; 3233 } 3234 } 3235 3236 /* If the font may hide a JRE font (eg fontconfig says it is 3237 * Lucida Sans), we want to use the JRE version, so make it 3238 * point to the JRE font. 3239 */ 3240 physFont = findJREDeferredFont(fcInfo.familyName, fcInfo.style); 3241 3242 /* It is also possible the font file is on the "deferred" list, 3243 * in which case we can just initialise it now. 3244 */ 3245 if (physFont == null && 3246 deferredFontFiles.get(fcInfo.fontFile) != null) { 3247 physFont = initialiseDeferredFont(fcInfo.fontFile); 3248 /* use findFont2D to get the right font from TTC's */ 3249 if (physFont != null) { 3250 if (isTTC) { 3251 Font2D f2d = findFont2D(fcInfo.familyName, 3252 fcInfo.style, NO_FALLBACK); 3253 if (f2d instanceof PhysicalFont) { /* paranoia */ 3254 return (PhysicalFont)f2d; 3255 } else { 3256 return null; 3257 } 3258 } else { 3259 return physFont; 3260 } 3261 } 3262 } 3263 3264 /* In the majority of cases we reach here, and need to determine 3265 * the type and rank to register the font. 3266 */ 3267 if (physFont == null) { 3268 int fontFormat = FONTFORMAT_NONE; 3269 int fontRank = Font2D.UNKNOWN_RANK; 3270 3271 if (ext.equals(".ttf") || ext.equals(".otf") || isTTC) { 3272 fontFormat = FONTFORMAT_TRUETYPE; 3273 fontRank = Font2D.TTF_RANK; 3274 } else if (ext.equals(".pfa") || ext.equals(".pfb")) { 3275 fontFormat = FONTFORMAT_TYPE1; 3276 fontRank = Font2D.TYPE1_RANK; 3277 } 3278 physFont = registerFontFile(fcInfo.fontFile, null, 3279 fontFormat, true, fontRank); 3280 } 3281 return physFont; 3282 } 3283 3284 private static String[] getPlatformFontDirs() { 3285 String path = getFontPath(true); 3286 StringTokenizer parser = 3287 new StringTokenizer(path, File.pathSeparator); 3288 ArrayList<String> pathList = new ArrayList<String>(); 3289 try { 3290 while (parser.hasMoreTokens()) { 3291 pathList.add(parser.nextToken()); 3292 } 3293 } catch (NoSuchElementException e) { 3294 } 3295 return pathList.toArray(new String[0]); 3296 } 3297 3298 /** returns an array of two strings. The first element is the 3299 * name of the font. The second element is the file name. 3300 */ 3301 private static String[] defaultPlatformFont = null; 3302 public static String[] getDefaultPlatformFont() { 3303 3304 if (defaultPlatformFont != null) { 3305 return defaultPlatformFont; 3306 } 3307 3308 String[] info = new String[2]; 3309 if (isWindows) { 3310 info[0] = "Arial"; 3311 info[1] = "c:\\windows\\fonts"; 3312 final String[] dirs = getPlatformFontDirs(); 3313 if (dirs.length > 1) { 3314 String dir = (String) 3315 AccessController.doPrivileged(new PrivilegedAction() { 3316 public Object run() { 3317 for (int i=0; i<dirs.length; i++) { 3318 String path = 3319 dirs[i] + File.separator + "arial.ttf"; 3320 File file = new File(path); 3321 if (file.exists()) { 3322 return dirs[i]; 3323 } 3324 } 3325 return null; 3326 } 3327 }); 3328 if (dir != null) { 3329 info[1] = dir; 3330 } 3331 } else { 3332 info[1] = dirs[0]; 3333 } 3334 info[1] = info[1] + File.separator + "arial.ttf"; 3335 } else { 3336 initFontConfigFonts(); 3337 for (int i=0; i<fontConfigFonts.length; i++) { 3338 if ("sans".equals(fontConfigFonts[i].fcFamily) && 3339 0 == fontConfigFonts[i].style) { 3340 info[0] = fontConfigFonts[i].familyName; 3341 info[1] = fontConfigFonts[i].fontFile; 3342 break; 3343 } 3344 } 3345 /* Absolute last ditch attempt in the face of fontconfig problems. 3346 * If we didn't match, pick the first, or just make something 3347 * up so we don't NPE. 3348 */ 3349 if (info[0] == null) { 3350 if (fontConfigFonts.length > 0 && 3351 fontConfigFonts[0].fontFile != null) { 3352 info[0] = fontConfigFonts[0].familyName; 3353 info[1] = fontConfigFonts[0].fontFile; 3354 } else { 3355 info[0] = "Dialog"; 3356 info[1] = "/dialog.ttf"; 3357 } 3358 } 3359 } 3360 defaultPlatformFont = info; 3361 return defaultPlatformFont; 3362 } 3363 3364 private FontConfigInfo getFontConfigInfo() { 3365 initFontConfigFonts(); 3366 for (int i=0; i<fontConfigFonts.length; i++) { 3367 if ("sans".equals(fontConfigFonts[i].fcFamily) && 3368 0 == fontConfigFonts[i].style) { 3369 return fontConfigFonts[i]; 3370 } 3371 } 3372 return null; 3373 } 3374 /* 3375 * We need to return a Composite font which has as the font in 3376 * its first slot one obtained from fontconfig. 3377 */ 3378 private static CompositeFont getFontConfigFont(String name, int style) { 3379 3380 name = name.toLowerCase(); 3381 3382 initFontConfigFonts(); 3383 3384 FontConfigInfo fcInfo = null; 3385 for (int i=0; i<fontConfigFonts.length; i++) { 3386 if (name.equals(fontConfigFonts[i].fcFamily) && 3387 style == fontConfigFonts[i].style) { 3388 fcInfo = fontConfigFonts[i]; 3389 break; 3390 } 3391 } 3392 if (fcInfo == null) { 3393 fcInfo = fontConfigFonts[0]; 3394 } 3395 3396 if (logging) { 3397 logger.info("FC name=" + name + " style=" + style + " uses " + 3398 fcInfo.familyName + " in file: " + fcInfo.fontFile); 3399 } 3400 3401 if (fcInfo.compFont != null) { 3402 return fcInfo.compFont; 3403 } 3404 3405 /* jdkFont is going to be used for slots 1..N and as a fallback. 3406 * Slot 0 will be the physical font from fontconfig. 3407 */ 3408 CompositeFont jdkFont = (CompositeFont) 3409 findFont2D(fcInfo.jdkName, style, LOGICAL_FALLBACK); 3410 3411 if (fcInfo.familyName == null || fcInfo.fontFile == null) { 3412 return (fcInfo.compFont = jdkFont); 3413 } 3414 3415 /* First, see if the family and exact style is already registered. 3416 * If it is, use it. If it's not, then try to register it. 3417 * If that registration fails (signalled by null) just return the 3418 * regular JDK composite. 3419 * Algorithmically styled fonts won't match on exact style, so 3420 * will fall through this code, but the regisration code will 3421 * find that file already registered and return its font. 3422 */ 3423 FontFamily family = FontFamily.getFamily(fcInfo.familyName); 3424 PhysicalFont physFont = null; 3425 if (family != null) { 3426 Font2D f2D = family.getFontWithExactStyleMatch(fcInfo.style); 3427 if (f2D instanceof PhysicalFont) { 3428 physFont = (PhysicalFont)f2D; 3429 } 3430 } 3431 3432 if (physFont == null || !fcInfo.fontFile.equals(physFont.platName)) { 3433 physFont = registerFromFcInfo(fcInfo); 3434 if (physFont == null) { 3435 return (fcInfo.compFont = jdkFont); 3436 } 3437 family = FontFamily.getFamily(physFont.getFamilyName(null)); 3438 } 3439 3440 /* Now register the fonts in the family (the other styles) after 3441 * checking that they aren't already registered and are actually in 3442 * a different file. They may be the same file in CJK cases. 3443 * For cases where they are different font files - eg as is common for 3444 * Latin fonts, then we rely on fontconfig to report these correctly. 3445 * Assume that all styles of this font are found by fontconfig, 3446 * so we can find all the family members which must be registered 3447 * together to prevent synthetic styling. 3448 */ 3449 for (int i=0; i<fontConfigFonts.length; i++) { 3450 FontConfigInfo fc = fontConfigFonts[i]; 3451 if (fc != fcInfo && 3452 physFont.getFamilyName(null).equals(fc.familyName) && 3453 !fc.fontFile.equals(physFont.platName) && 3454 family.getFontWithExactStyleMatch(fc.style) == null) { 3455 3456 registerFromFcInfo(fontConfigFonts[i]); 3457 } 3458 } 3459 3460 /* Now we have a physical font. We will back this up with the JDK 3461 * logical font (sansserif, serif, or monospaced) that corresponds 3462 * to the Pango/GTK/FC logical font name. 3463 */ 3464 return (fcInfo.compFont = new CompositeFont(physFont, jdkFont)); 3465 } 3466 3467 /* This is called by Swing passing in a fontconfig family name 3468 * such as "sans". In return Swing gets a FontUIResource instance 3469 * that has queried fontconfig to resolve the font(s) used for this. 3470 * Fontconfig will if asked return a list of fonts to give the largest 3471 * possible code point coverage. 3472 * For now we use only the first font returned by fontconfig, and 3473 * back it up with the most closely matching JDK logical font. 3474 * Essentially this means pre-pending what we return now with fontconfig's 3475 * preferred physical font. This could lead to some duplication in cases, 3476 * if we already included that font later. We probably should remove such 3477 * duplicates, but it is not a significant problem. It can be addressed 3478 * later as part of creating a Composite which uses more of the 3479 * same fonts as fontconfig. At that time we also should pay more 3480 * attention to the special rendering instructions fontconfig returns, 3481 * such as whether we should prefer embedded bitmaps over antialiasing. 3482 * There's no way to express that via a Font at present. 3483 */ 3484 public static FontUIResource getFontConfigFUIR(String fcFamily, 3485 int style, int size) { 3486 3487 String mappedName = mapFcName(fcFamily); 3488 if (mappedName == null) { 3489 mappedName = "sansserif"; 3490 } 3491 3492 /* If GTK L&F were to be used on windows, we need to return 3493 * something. Since on windows Swing won't have the code to 3494 * call fontconfig, even if it is present, fcFamily and mapped 3495 * name will default to sans and therefore sansserif so this 3496 * should be fine. 3497 */ 3498 if (isWindows) { 3499 return new FontUIResource(mappedName, style, size); 3500 } 3501 3502 CompositeFont font2D = getFontConfigFont(fcFamily, style); 3503 if (font2D == null) { // Not expected, just a precaution. 3504 return new FontUIResource(mappedName, style, size); 3505 } 3506 3507 /* The name of the font will be that of the physical font in slot, 3508 * but by setting the handle to that of the CompositeFont it 3509 * renders as that CompositeFont. 3510 * It also needs to be marked as a created font which is the 3511 * current mechanism to signal that deriveFont etc must copy 3512 * the handle from the original font. 3513 */ 3514 FontUIResource fuir = 3515 new FontUIResource(font2D.getFamilyName(null), style, size); 3516 setFont2D(fuir, font2D.handle); 3517 setCreatedFont(fuir); 3518 return fuir; 3519 } 3520 3521 /* The following fields and methods which relate to layout 3522 * perhaps belong in some other class but FontManager is already 3523 * widely used as an entry point for other JDK code that needs 3524 * access to the font system internals. 3525 */ 3526 3527 /** 3528 * Referenced by code in the JDK which wants to test for the 3529 * minimum char code for which layout may be required. 3530 * Note that even basic latin text can benefit from ligatures, 3531 * eg "ffi" but we presently apply those only if explicitly 3532 * requested with TextAttribute.LIGATURES_ON. 3533 * The value here indicates the lowest char code for which failing 3534 * to invoke layout would prevent acceptable rendering. 3535 */ 3536 public static final int MIN_LAYOUT_CHARCODE = 0x0300; 3537 3538 /** 3539 * Referenced by code in the JDK which wants to test for the 3540 * maximum char code for which layout may be required. 3541 * Note this does not account for supplementary characters 3542 * where the caller interprets 'layout' to mean any case where 3543 * one 'char' (ie the java type char) does not map to one glyph 3544 */ 3545 public static final int MAX_LAYOUT_CHARCODE = 0x206F; 3546 3547 /* If the character code falls into any of a number of unicode ranges 3548 * where we know that simple left->right layout mapping chars to glyphs 3549 * 1:1 and accumulating advances is going to produce incorrect results, 3550 * we want to know this so the caller can use a more intelligent layout 3551 * approach. A caller who cares about optimum performance may want to 3552 * check the first case and skip the method call if its in that range. 3553 * Although there's a lot of tests in here, knowing you can skip 3554 * CTL saves a great deal more. The rest of the checks are ordered 3555 * so that rather than checking explicitly if (>= start & <= end) 3556 * which would mean all ranges would need to be checked so be sure 3557 * CTL is not needed, the method returns as soon as it recognises 3558 * the code point is outside of a CTL ranges. 3559 * NOTE: Since this method accepts an 'int' it is asssumed to properly 3560 * represent a CHARACTER. ie it assumes the caller has already 3561 * converted surrogate pairs into supplementary characters, and so 3562 * can handle this case and doesn't need to be told such a case is 3563 * 'complex'. 3564 */ 3565 static boolean isComplexCharCode(int code) { 3566 3567 if (code < MIN_LAYOUT_CHARCODE || code > MAX_LAYOUT_CHARCODE) { 3568 return false; 3569 } 3570 else if (code <= 0x036f) { 3571 // Trigger layout for combining diacriticals 0x0300->0x036f 3572 return true; 3573 } 3574 else if (code < 0x0590) { 3575 // No automatic layout for Greek, Cyrillic, Armenian. 3576 return false; 3577 } 3578 else if (code <= 0x06ff) { 3579 // Hebrew 0590 - 05ff 3580 // Arabic 0600 - 06ff 3581 return true; 3582 } 3583 else if (code < 0x0900) { 3584 return false; // Syriac and Thaana 3585 } 3586 else if (code <= 0x0e7f) { 3587 // if Indic, assume shaping for conjuncts, reordering: 3588 // 0900 - 097F Devanagari 3589 // 0980 - 09FF Bengali 3590 // 0A00 - 0A7F Gurmukhi 3591 // 0A80 - 0AFF Gujarati 3592 // 0B00 - 0B7F Oriya 3593 // 0B80 - 0BFF Tamil 3594 // 0C00 - 0C7F Telugu 3595 // 0C80 - 0CFF Kannada 3596 // 0D00 - 0D7F Malayalam 3597 // 0D80 - 0DFF Sinhala 3598 // 0E00 - 0E7F if Thai, assume shaping for vowel, tone marks 3599 return true; 3600 } 3601 else if (code < 0x0f00) { 3602 return false; 3603 } 3604 else if (code <= 0x0fff) { // U+0F00 - U+0FFF Tibetan 3605 return true; 3606 } 3607 else if (code < 0x1100) { 3608 return false; 3609 } 3610 else if (code < 0x11ff) { // U+1100 - U+11FF Old Hangul 3611 return true; 3612 } 3613 else if (code < 0x1780) { 3614 return false; 3615 } 3616 else if (code <= 0x17ff) { // 1780 - 17FF Khmer 3617 return true; 3618 } 3619 else if (code < 0x200c) { 3620 return false; 3621 } 3622 else if (code <= 0x200d) { // zwj or zwnj 3623 return true; 3624 } 3625 else if (code >= 0x202a && code <= 0x202e) { // directional control 3626 return true; 3627 } 3628 else if (code >= 0x206a && code <= 0x206f) { // directional control 3629 return true; 3630 } 3631 return false; 3632 } 3633 3634 /* This is almost the same as the method above, except it takes a 3635 * char which means it may include undecoded surrogate pairs. 3636 * The distinction is made so that code which needs to identify all 3637 * cases in which we do not have a simple mapping from 3638 * char->unicode character->glyph can be be identified. 3639 * For example measurement cannot simply sum advances of 'chars', 3640 * the caret in editable text cannot advance one 'char' at a time, etc. 3641 * These callers really are asking for more than whether 'layout' 3642 * needs to be run, they need to know if they can assume 1->1 3643 * char->glyph mapping. 3644 */ 3645 static boolean isNonSimpleChar(char ch) { 3646 return 3647 isComplexCharCode(ch) || 3648 (ch >= CharToGlyphMapper.HI_SURROGATE_START && 3649 ch <= CharToGlyphMapper.LO_SURROGATE_END); 3650 } 3651 3652 /** 3653 * If there is anything in the text which triggers a case 3654 * where char->glyph does not map 1:1 in straightforward 3655 * left->right ordering, then this method returns true. 3656 * Scripts which might require it but are not treated as such 3657 * due to JDK implementations will not return true. 3658 * ie a 'true' return is an indication of the treatment by 3659 * the implementation. 3660 * Whether supplementary characters should be considered is dependent 3661 * on the needs of the caller. Since this method accepts the 'char' type 3662 * then such chars are always represented by a pair. From a rendering 3663 * perspective these will all (in the cases I know of) still be one 3664 * unicode character -> one glyph. But if a caller is using this to 3665 * discover any case where it cannot make naive assumptions about 3666 * the number of chars, and how to index through them, then it may 3667 * need the option to have a 'true' return in such a case. 3668 */ 3669 public static boolean isComplexText(char [] chs, int start, int limit) { 3670 3671 for (int i = start; i < limit; i++) { 3672 if (chs[i] < MIN_LAYOUT_CHARCODE) { 3673 continue; 3674 } 3675 else if (isNonSimpleChar(chs[i])) { 3676 return true; 3677 } 3678 } 3679 return false; 3680 } 3681 3682 /** 3683 * Used by windows printing to assess if a font is likely to 3684 * be layout compatible with JDK 3685 * TrueType fonts should be, but if they have no GPOS table, 3686 * but do have a GSUB table, then they are probably older 3687 * fonts GDI handles differently. 3688 */ 3689 public static boolean textLayoutIsCompatible(Font font) { 3690 3691 Font2D font2D = FontManager.getFont2D(font); 3692 if (font2D instanceof TrueTypeFont) { 3693 TrueTypeFont ttf = (TrueTypeFont)font2D; 3694 return 3695 ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null || 3696 ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null; 3697 } else { 3698 return false; 3699 } 3700 } 3701 3702 private static FontScaler nullScaler = null; 3703 private static Constructor<FontScaler> scalerConstructor = null; 3704 3705 //Find preferred font scaler 3706 // 3707 //NB: we can allow property based preferences 3708 // (theoretically logic can be font type specific) 3709 static { 3710 Class scalerClass = null; 3711 Class arglst[] = new Class[] {Font2D.class, int.class, 3712 boolean.class, int.class}; 3713 3714 try { 3715 if (SunGraphicsEnvironment.isOpenJDK()) { 3716 scalerClass = Class.forName("sun.font.FreetypeFontScaler"); 3717 } else { 3718 scalerClass = Class.forName("sun.font.T2KFontScaler"); 3719 } 3720 } catch (ClassNotFoundException e) { 3721 scalerClass = NullFontScaler.class; 3722 } 3723 3724 //NB: rewrite using factory? constructor is ugly way 3725 try { 3726 scalerConstructor = scalerClass.getConstructor(arglst); 3727 } catch (NoSuchMethodException e) { 3728 //should not happen 3729 } 3730 } 3731 3732 /* At the moment it is harmless to create 2 null scalers 3733 so, technically, syncronized keyword is not needed. 3734 3735 But it is safer to keep it to avoid subtle problems if we will be 3736 adding checks like whether scaler is null scaler. */ 3737 public synchronized static FontScaler getNullScaler() { 3738 if (nullScaler == null) { 3739 nullScaler = new NullFontScaler(); 3740 } 3741 return nullScaler; 3742 } 3743 3744 /* This is the only place to instantiate new FontScaler. 3745 * Therefore this is very convinient place to register 3746 * scaler with Disposer as well as trigger deregistring bad font 3747 * in case when scaler reports this. 3748 */ 3749 3750 public static FontScaler getScaler(Font2D font, 3751 int indexInCollection, 3752 boolean supportsCJK, 3753 int filesize) { 3754 FontScaler scaler = null; 3755 3756 try { 3757 Object args[] = new Object[] {font, indexInCollection, 3758 supportsCJK, filesize}; 3759 scaler = scalerConstructor.newInstance(args); 3760 Disposer.addObjectRecord(font, scaler); 3761 } catch (Throwable e) { 3762 scaler = nullScaler; 3763 3764 //if we can not instantiate scaler assume bad font 3765 //NB: technically it could be also because of internal scaler 3766 // error but here we are assuming scaler is ok. 3767 deRegisterBadFont(font); 3768 } 3769 return scaler; 3770 } 3771 }