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