1 /* 2 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.font; 27 28 import java.awt.Font; 29 import java.awt.FontFormatException; 30 import java.io.BufferedReader; 31 import java.io.File; 32 import java.io.FileInputStream; 33 import java.io.FilenameFilter; 34 import java.io.IOException; 35 import java.io.InputStreamReader; 36 import java.security.AccessController; 37 import java.security.PrivilegedAction; 38 import java.util.ArrayList; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.Hashtable; 42 import java.util.Iterator; 43 import java.util.Locale; 44 import java.util.Map; 45 import java.util.NoSuchElementException; 46 import java.util.StringTokenizer; 47 import java.util.TreeMap; 48 import java.util.Vector; 49 import java.util.concurrent.ConcurrentHashMap; 50 51 import javax.swing.plaf.FontUIResource; 52 import sun.awt.AppContext; 53 import sun.awt.FontConfiguration; 54 import sun.awt.SunToolkit; 55 import sun.java2d.FontSupport; 56 import sun.util.logging.PlatformLogger; 57 58 /** 59 * The base implementation of the {@link FontManager} interface. It implements 60 * the platform independent, shared parts of OpenJDK's FontManager 61 * implementations. The platform specific parts are declared as abstract 62 * methods that have to be implemented by specific implementations. 63 */ 64 public abstract class SunFontManager implements FontSupport, FontManagerForSGE { 65 66 private static class TTFilter implements FilenameFilter { 67 public boolean accept(File dir,String name) { 68 /* all conveniently have the same suffix length */ 69 int offset = name.length()-4; 70 if (offset <= 0) { /* must be at least A.ttf */ 71 return false; 72 } else { 73 return(name.startsWith(".ttf", offset) || 74 name.startsWith(".TTF", offset) || 75 name.startsWith(".ttc", offset) || 76 name.startsWith(".TTC", offset) || 77 name.startsWith(".otf", offset) || 78 name.startsWith(".OTF", offset)); 79 } 80 } 81 } 82 83 private static class T1Filter implements FilenameFilter { 84 public boolean accept(File dir,String name) { 85 if (noType1Font) { 86 return false; 87 } 88 /* all conveniently have the same suffix length */ 89 int offset = name.length()-4; 90 if (offset <= 0) { /* must be at least A.pfa */ 91 return false; 92 } else { 93 return(name.startsWith(".pfa", offset) || 94 name.startsWith(".pfb", offset) || 95 name.startsWith(".PFA", offset) || 96 name.startsWith(".PFB", offset)); 97 } 98 } 99 } 100 101 private static class TTorT1Filter implements FilenameFilter { 102 public boolean accept(File dir, String name) { 103 104 /* all conveniently have the same suffix length */ 105 int offset = name.length()-4; 106 if (offset <= 0) { /* must be at least A.ttf or A.pfa */ 107 return false; 108 } else { 109 boolean isTT = 110 name.startsWith(".ttf", offset) || 111 name.startsWith(".TTF", offset) || 112 name.startsWith(".ttc", offset) || 113 name.startsWith(".TTC", offset) || 114 name.startsWith(".otf", offset) || 115 name.startsWith(".OTF", offset); 116 if (isTT) { 117 return true; 118 } else if (noType1Font) { 119 return false; 120 } else { 121 return(name.startsWith(".pfa", offset) || 122 name.startsWith(".pfb", offset) || 123 name.startsWith(".PFA", offset) || 124 name.startsWith(".PFB", offset)); 125 } 126 } 127 } 128 } 129 130 public static final int FONTFORMAT_NONE = -1; 131 public static final int FONTFORMAT_TRUETYPE = 0; 132 public static final int FONTFORMAT_TYPE1 = 1; 133 public static final int FONTFORMAT_T2K = 2; 134 public static final int FONTFORMAT_TTC = 3; 135 public static final int FONTFORMAT_COMPOSITE = 4; 136 public static final int FONTFORMAT_NATIVE = 5; 137 138 /* Pool of 20 font file channels chosen because some UTF-8 locale 139 * composite fonts can use up to 16 platform fonts (including the 140 * Lucida fall back). This should prevent channel thrashing when 141 * dealing with one of these fonts. 142 * The pool array stores the fonts, rather than directly referencing 143 * the channels, as the font needs to do the open/close work. 144 */ 145 private static final int CHANNELPOOLSIZE = 20; 146 private int lastPoolIndex = 0; 147 private FileFont fontFileCache[] = new FileFont[CHANNELPOOLSIZE]; 148 149 /* Need to implement a simple linked list scheme for fast 150 * traversal and lookup. 151 * Also want to "fast path" dialog so there's minimal overhead. 152 */ 153 /* There are at exactly 20 composite fonts: 5 faces (but some are not 154 * usually different), in 4 styles. The array may be auto-expanded 155 * later if more are needed, eg for user-defined composites or locale 156 * variants. 157 */ 158 private int maxCompFont = 0; 159 private CompositeFont [] compFonts = new CompositeFont[20]; 160 private ConcurrentHashMap<String, CompositeFont> 161 compositeFonts = new ConcurrentHashMap<String, CompositeFont>(); 162 private ConcurrentHashMap<String, PhysicalFont> 163 physicalFonts = new ConcurrentHashMap<String, PhysicalFont>(); 164 private ConcurrentHashMap<String, PhysicalFont> 165 registeredFonts = new ConcurrentHashMap<String, PhysicalFont>(); 166 167 /* given a full name find the Font. Remind: there's duplication 168 * here in that this contains the content of compositeFonts + 169 * physicalFonts. 170 */ 171 private ConcurrentHashMap<String, Font2D> 172 fullNameToFont = new ConcurrentHashMap<String, Font2D>(); 173 174 /* TrueType fonts have localised names. Support searching all 175 * of these before giving up on a name. 176 */ 177 private HashMap<String, TrueTypeFont> localeFullNamesToFont; 178 179 private PhysicalFont defaultPhysicalFont; 180 181 static boolean longAddresses; 182 private boolean loaded1dot0Fonts = false; 183 boolean loadedAllFonts = false; 184 boolean loadedAllFontFiles = false; 185 HashMap<String,String> jreFontMap; 186 HashSet<String> jreLucidaFontFiles; 187 String[] jreOtherFontFiles; 188 boolean noOtherJREFontFiles = false; // initial assumption. 189 190 public static final String lucidaFontName = "Lucida Sans Regular"; 191 public static String jreLibDirName; 192 public static String jreFontDirName; 193 private static HashSet<String> missingFontFiles = null; 194 private String defaultFontName; 195 private String defaultFontFileName; 196 protected HashSet registeredFontFiles = new HashSet(); 197 198 private ArrayList badFonts; 199 /* fontPath is the location of all fonts on the system, excluding the 200 * JRE's own font directory but including any path specified using the 201 * sun.java2d.fontpath property. Together with that property, it is 202 * initialised by the getPlatformFontPath() method 203 * This call must be followed by a call to registerFontDirs(fontPath) 204 * once any extra debugging path has been appended. 205 */ 206 protected String fontPath; 207 private FontConfiguration fontConfig; 208 /* discoveredAllFonts is set to true when all fonts on the font path are 209 * discovered. This usually also implies opening, validating and 210 * registering, but an implementation may be optimized to avold this. 211 * So see also "loadedAllFontFiles" 212 */ 213 private boolean discoveredAllFonts = false; 214 215 /* No need to keep consing up new instances - reuse a singleton. 216 * The trade-off is that these objects don't get GC'd. 217 */ 218 private static final FilenameFilter ttFilter = new TTFilter(); 219 private static final FilenameFilter t1Filter = new T1Filter(); 220 221 private Font[] allFonts; 222 private String[] allFamilies; // cache for default locale only 223 private Locale lastDefaultLocale; 224 225 public static boolean noType1Font; 226 227 /* Used to indicate required return type from toArray(..); */ 228 private static String[] STR_ARRAY = new String[0]; 229 230 /** 231 * Deprecated, unsupported hack - actually invokes a bug! 232 * Left in for a customer, don't remove. 233 */ 234 private boolean usePlatformFontMetrics = false; 235 236 /** 237 * Returns the global SunFontManager instance. This is similar to 238 * {@link FontManagerFactory#getInstance()} but it returns a 239 * SunFontManager instance instead. This is only used in internal classes 240 * where we can safely assume that a SunFontManager is to be used. 241 * 242 * @return the global SunFontManager instance 243 */ 244 public static SunFontManager getInstance() { 245 FontManager fm = FontManagerFactory.getInstance(); 246 return (SunFontManager) fm; 247 } 248 249 public FilenameFilter getTrueTypeFilter() { 250 return ttFilter; 251 } 252 253 public FilenameFilter getType1Filter() { 254 return t1Filter; 255 } 256 257 @Override 258 public boolean usingPerAppContextComposites() { 259 return _usingPerAppContextComposites; 260 } 261 262 private void initJREFontMap() { 263 264 /* Key is familyname+style value as an int. 265 * Value is filename containing the font. 266 * If no mapping exists, it means there is no font file for the style 267 * If the mapping exists but the file doesn't exist in the deferred 268 * list then it means its not installed. 269 * This looks like a lot of code and strings but if it saves even 270 * a single file being opened at JRE start-up there's a big payoff. 271 * Lucida Sans is probably the only important case as the others 272 * are rarely used. Consider removing the other mappings if there's 273 * no evidence they are useful in practice. 274 */ 275 jreFontMap = new HashMap<String,String>(); 276 jreLucidaFontFiles = new HashSet<String>(); 277 if (isOpenJDK()) { 278 return; 279 } 280 /* Lucida Sans Family */ 281 jreFontMap.put("lucida sans0", "LucidaSansRegular.ttf"); 282 jreFontMap.put("lucida sans1", "LucidaSansDemiBold.ttf"); 283 /* Lucida Sans full names (map Bold and DemiBold to same file) */ 284 jreFontMap.put("lucida sans regular0", "LucidaSansRegular.ttf"); 285 jreFontMap.put("lucida sans regular1", "LucidaSansDemiBold.ttf"); 286 jreFontMap.put("lucida sans bold1", "LucidaSansDemiBold.ttf"); 287 jreFontMap.put("lucida sans demibold1", "LucidaSansDemiBold.ttf"); 288 289 /* Lucida Sans Typewriter Family */ 290 jreFontMap.put("lucida sans typewriter0", 291 "LucidaTypewriterRegular.ttf"); 292 jreFontMap.put("lucida sans typewriter1", "LucidaTypewriterBold.ttf"); 293 /* Typewriter full names (map Bold and DemiBold to same file) */ 294 jreFontMap.put("lucida sans typewriter regular0", 295 "LucidaTypewriter.ttf"); 296 jreFontMap.put("lucida sans typewriter regular1", 297 "LucidaTypewriterBold.ttf"); 298 jreFontMap.put("lucida sans typewriter bold1", 299 "LucidaTypewriterBold.ttf"); 300 jreFontMap.put("lucida sans typewriter demibold1", 301 "LucidaTypewriterBold.ttf"); 302 303 /* Lucida Bright Family */ 304 jreFontMap.put("lucida bright0", "LucidaBrightRegular.ttf"); 305 jreFontMap.put("lucida bright1", "LucidaBrightDemiBold.ttf"); 306 jreFontMap.put("lucida bright2", "LucidaBrightItalic.ttf"); 307 jreFontMap.put("lucida bright3", "LucidaBrightDemiItalic.ttf"); 308 /* Lucida Bright full names (map Bold and DemiBold to same file) */ 309 jreFontMap.put("lucida bright regular0", "LucidaBrightRegular.ttf"); 310 jreFontMap.put("lucida bright regular1", "LucidaBrightDemiBold.ttf"); 311 jreFontMap.put("lucida bright regular2", "LucidaBrightItalic.ttf"); 312 jreFontMap.put("lucida bright regular3", "LucidaBrightDemiItalic.ttf"); 313 jreFontMap.put("lucida bright bold1", "LucidaBrightDemiBold.ttf"); 314 jreFontMap.put("lucida bright bold3", "LucidaBrightDemiItalic.ttf"); 315 jreFontMap.put("lucida bright demibold1", "LucidaBrightDemiBold.ttf"); 316 jreFontMap.put("lucida bright demibold3","LucidaBrightDemiItalic.ttf"); 317 jreFontMap.put("lucida bright italic2", "LucidaBrightItalic.ttf"); 318 jreFontMap.put("lucida bright italic3", "LucidaBrightDemiItalic.ttf"); 319 jreFontMap.put("lucida bright bold italic3", 320 "LucidaBrightDemiItalic.ttf"); 321 jreFontMap.put("lucida bright demibold italic3", 322 "LucidaBrightDemiItalic.ttf"); 323 for (String ffile : jreFontMap.values()) { 324 jreLucidaFontFiles.add(ffile); 325 } 326 } 327 328 static { 329 330 java.security.AccessController.doPrivileged( 331 new java.security.PrivilegedAction() { 332 333 public Object run() { 334 FontManagerNativeLibrary.load(); 335 336 // JNI throws an exception if a class/method/field is not found, 337 // so there's no need to do anything explicit here. 338 initIDs(); 339 340 switch (StrikeCache.nativeAddressSize) { 341 case 8: longAddresses = true; break; 342 case 4: longAddresses = false; break; 343 default: throw new RuntimeException("Unexpected address size"); 344 } 345 346 noType1Font = 347 "true".equals(System.getProperty("sun.java2d.noType1Font")); 348 jreLibDirName = 349 System.getProperty("java.home","") + File.separator + "lib"; 350 jreFontDirName = jreLibDirName + File.separator + "fonts"; 351 File lucidaFile = 352 new File(jreFontDirName + File.separator + FontUtilities.LUCIDA_FILE_NAME); 353 354 return null; 355 } 356 }); 357 } 358 359 public TrueTypeFont getEUDCFont() { 360 // Overridden in Windows. 361 return null; 362 } 363 364 /* Initialise ptrs used by JNI methods */ 365 private static native void initIDs(); 366 367 @SuppressWarnings("unchecked") 368 protected SunFontManager() { 369 370 initJREFontMap(); 371 java.security.AccessController.doPrivileged( 372 new java.security.PrivilegedAction() { 373 public Object run() { 374 File badFontFile = 375 new File(jreFontDirName + File.separator + 376 "badfonts.txt"); 377 if (badFontFile.exists()) { 378 FileInputStream fis = null; 379 try { 380 badFonts = new ArrayList(); 381 fis = new FileInputStream(badFontFile); 382 InputStreamReader isr = new InputStreamReader(fis); 383 BufferedReader br = new BufferedReader(isr); 384 while (true) { 385 String name = br.readLine(); 386 if (name == null) { 387 break; 388 } else { 389 if (FontUtilities.debugFonts()) { 390 FontUtilities.getLogger().warning("read bad font: " + 391 name); 392 } 393 badFonts.add(name); 394 } 395 } 396 } catch (IOException e) { 397 try { 398 if (fis != null) { 399 fis.close(); 400 } 401 } catch (IOException ioe) { 402 } 403 } 404 } 405 406 /* Here we get the fonts in jre/lib/fonts and register 407 * them so they are always available and preferred over 408 * other fonts. This needs to be registered before the 409 * composite fonts as otherwise some native font that 410 * corresponds may be found as we don't have a way to 411 * handle two fonts of the same name, so the JRE one 412 * must be the first one registered. Pass "true" to 413 * registerFonts method as on-screen these JRE fonts 414 * always go through the T2K rasteriser. 415 */ 416 if (FontUtilities.isLinux) { 417 /* Linux font configuration uses these fonts */ 418 registerFontDir(jreFontDirName); 419 } 420 registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK, 421 true, false); 422 423 /* Create the font configuration and get any font path 424 * that might be specified. 425 */ 426 fontConfig = createFontConfiguration(); 427 if (isOpenJDK()) { 428 String[] fontInfo = getDefaultPlatformFont(); 429 defaultFontName = fontInfo[0]; 430 defaultFontFileName = fontInfo[1]; 431 } 432 433 String extraFontPath = fontConfig.getExtraFontPath(); 434 435 /* In prior releases the debugging font path replaced 436 * all normally located font directories except for the 437 * JRE fonts dir. This directory is still always located 438 * and placed at the head of the path but as an 439 * augmentation to the previous behaviour the 440 * changes below allow you to additionally append to 441 * the font path by starting with append: or prepend by 442 * starting with a prepend: sign. Eg: to append 443 * -Dsun.java2d.fontpath=append:/usr/local/myfonts 444 * and to prepend 445 * -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp 446 * 447 * If there is an appendedfontpath it in the font 448 * configuration it is used instead of searching the 449 * system for dirs. 450 * The behaviour of append and prepend is then similar 451 * to the normal case. ie it goes after what 452 * you prepend and * before what you append. If the 453 * sun.java2d.fontpath property is used, but it 454 * neither the append or prepend syntaxes is used then 455 * as except for the JRE dir the path is replaced and it 456 * is up to you to make sure that all the right 457 * directories are located. This is platform and 458 * locale-specific so its almost impossible to get 459 * right, so it should be used with caution. 460 */ 461 boolean prependToPath = false; 462 boolean appendToPath = false; 463 String dbgFontPath = 464 System.getProperty("sun.java2d.fontpath"); 465 466 if (dbgFontPath != null) { 467 if (dbgFontPath.startsWith("prepend:")) { 468 prependToPath = true; 469 dbgFontPath = 470 dbgFontPath.substring("prepend:".length()); 471 } else if (dbgFontPath.startsWith("append:")) { 472 appendToPath = true; 473 dbgFontPath = 474 dbgFontPath.substring("append:".length()); 475 } 476 } 477 478 if (FontUtilities.debugFonts()) { 479 PlatformLogger logger = FontUtilities.getLogger(); 480 logger.info("JRE font directory: " + jreFontDirName); 481 logger.info("Extra font path: " + extraFontPath); 482 logger.info("Debug font path: " + dbgFontPath); 483 } 484 485 if (dbgFontPath != null) { 486 /* In debugging mode we register all the paths 487 * Caution: this is a very expensive call on Solaris:- 488 */ 489 fontPath = getPlatformFontPath(noType1Font); 490 491 if (extraFontPath != null) { 492 fontPath = 493 extraFontPath + File.pathSeparator + fontPath; 494 } 495 if (appendToPath) { 496 fontPath = 497 fontPath + File.pathSeparator + dbgFontPath; 498 } else if (prependToPath) { 499 fontPath = 500 dbgFontPath + File.pathSeparator + fontPath; 501 } else { 502 fontPath = dbgFontPath; 503 } 504 registerFontDirs(fontPath); 505 } else if (extraFontPath != null) { 506 /* If the font configuration contains an 507 * "appendedfontpath" entry, it is interpreted as a 508 * set of locations that should always be registered. 509 * It may be additional to locations normally found 510 * for that place, or it may be locations that need 511 * to have all their paths registered to locate all 512 * the needed platform names. 513 * This is typically when the same .TTF file is 514 * referenced from multiple font.dir files and all 515 * of these must be read to find all the native 516 * (XLFD) names for the font, so that X11 font APIs 517 * can be used for as many code points as possible. 518 */ 519 registerFontDirs(extraFontPath); 520 } 521 522 /* On Solaris, we need to register the Japanese TrueType 523 * directory so that we can find the corresponding 524 * bitmap fonts. This could be done by listing the 525 * directory in the font configuration file, but we 526 * don't want to confuse users with this quirk. There 527 * are no bitmap fonts for other writing systems that 528 * correspond to TrueType fonts and have matching XLFDs. 529 * We need to register the bitmap fonts only in 530 * environments where they're on the X font path, i.e., 531 * in the Japanese locale. Note that if the X Toolkit 532 * is in use the font path isn't set up by JDK, but 533 * users of a JA locale should have it 534 * set up already by their login environment. 535 */ 536 if (FontUtilities.isSolaris && Locale.JAPAN.equals(Locale.getDefault())) { 537 registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT"); 538 } 539 540 initCompositeFonts(fontConfig, null); 541 542 return null; 543 } 544 }); 545 546 boolean platformFont = AccessController.doPrivileged( 547 new PrivilegedAction<Boolean>() { 548 public Boolean run() { 549 String prop = 550 System.getProperty("java2d.font.usePlatformFont"); 551 String env = System.getenv("JAVA2D_USEPLATFORMFONT"); 552 return "true".equals(prop) || env != null; 553 } 554 }); 555 556 if (platformFont) { 557 usePlatformFontMetrics = true; 558 System.out.println("Enabling platform font metrics for win32. This is an unsupported option."); 559 System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases."); 560 System.out.println("It is appropriate only for use by applications which do not use any Java 2"); 561 System.out.println("functionality. This property will be removed in a later release."); 562 } 563 } 564 565 /** 566 * This method is provided for internal and exclusive use by Swing. 567 * 568 * @param font representing a physical font. 569 * @return true if the underlying font is a TrueType or OpenType font 570 * that claims to support the Microsoft Windows encoding corresponding to 571 * the default file.encoding property of this JRE instance. 572 * This narrow value is useful for Swing to decide if the font is useful 573 * for the the Windows Look and Feel, or, if a composite font should be 574 * used instead. 575 * The information used to make the decision is obtained from 576 * the ulCodePageRange fields in the font. 577 * A caller can use isLogicalFont(Font) in this class before calling 578 * this method and would not need to call this method if that 579 * returns true. 580 */ 581 // static boolean fontSupportsDefaultEncoding(Font font) { 582 // String encoding = 583 // (String) java.security.AccessController.doPrivileged( 584 // new sun.security.action.GetPropertyAction("file.encoding")); 585 586 // if (encoding == null || font == null) { 587 // return false; 588 // } 589 590 // encoding = encoding.toLowerCase(Locale.ENGLISH); 591 592 // return FontManager.fontSupportsEncoding(font, encoding); 593 // } 594 595 public Font2DHandle getNewComposite(String family, int style, 596 Font2DHandle handle) { 597 598 if (!(handle.font2D instanceof CompositeFont)) { 599 return handle; 600 } 601 602 CompositeFont oldComp = (CompositeFont)handle.font2D; 603 PhysicalFont oldFont = oldComp.getSlotFont(0); 604 605 if (family == null) { 606 family = oldFont.getFamilyName(null); 607 } 608 if (style == -1) { 609 style = oldComp.getStyle(); 610 } 611 612 Font2D newFont = findFont2D(family, style, NO_FALLBACK); 613 if (!(newFont instanceof PhysicalFont)) { 614 newFont = oldFont; 615 } 616 PhysicalFont physicalFont = (PhysicalFont)newFont; 617 CompositeFont dialog2D = 618 (CompositeFont)findFont2D("dialog", style, NO_FALLBACK); 619 if (dialog2D == null) { /* shouldn't happen */ 620 return handle; 621 } 622 CompositeFont compFont = new CompositeFont(physicalFont, dialog2D); 623 Font2DHandle newHandle = new Font2DHandle(compFont); 624 return newHandle; 625 } 626 627 protected void registerCompositeFont(String compositeName, 628 String[] componentFileNames, 629 String[] componentNames, 630 int numMetricsSlots, 631 int[] exclusionRanges, 632 int[] exclusionMaxIndex, 633 boolean defer) { 634 635 CompositeFont cf = new CompositeFont(compositeName, 636 componentFileNames, 637 componentNames, 638 numMetricsSlots, 639 exclusionRanges, 640 exclusionMaxIndex, defer, this); 641 addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK); 642 synchronized (compFonts) { 643 compFonts[maxCompFont++] = cf; 644 } 645 } 646 647 /* This variant is used only when the application specifies 648 * a variant of composite fonts which prefers locale specific or 649 * proportional fonts. 650 */ 651 protected static void registerCompositeFont(String compositeName, 652 String[] componentFileNames, 653 String[] componentNames, 654 int numMetricsSlots, 655 int[] exclusionRanges, 656 int[] exclusionMaxIndex, 657 boolean defer, 658 ConcurrentHashMap<String, Font2D> 659 altNameCache) { 660 661 CompositeFont cf = new CompositeFont(compositeName, 662 componentFileNames, 663 componentNames, 664 numMetricsSlots, 665 exclusionRanges, 666 exclusionMaxIndex, defer, 667 SunFontManager.getInstance()); 668 669 /* if the cache has an existing composite for this case, make 670 * its handle point to this new font. 671 * This ensures that when the altNameCache that is passed in 672 * is the global mapNameCache - ie we are running as an application - 673 * that any statically created java.awt.Font instances which already 674 * have a Font2D instance will have that re-directed to the new Font 675 * on subsequent uses. This is particularly important for "the" 676 * default font instance, or similar cases where a UI toolkit (eg 677 * Swing) has cached a java.awt.Font. Note that if Swing is using 678 * a custom composite APIs which update the standard composites have 679 * no effect - this is typically the case only when using the Windows 680 * L&F where these APIs would conflict with that L&F anyway. 681 */ 682 Font2D oldFont = (Font2D) 683 altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH)); 684 if (oldFont instanceof CompositeFont) { 685 oldFont.handle.font2D = cf; 686 } 687 altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf); 688 } 689 690 private void addCompositeToFontList(CompositeFont f, int rank) { 691 692 if (FontUtilities.isLogging()) { 693 FontUtilities.getLogger().info("Add to Family "+ f.familyName + 694 ", Font " + f.fullName + " rank="+rank); 695 } 696 f.setRank(rank); 697 compositeFonts.put(f.fullName, f); 698 fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f); 699 700 FontFamily family = FontFamily.getFamily(f.familyName); 701 if (family == null) { 702 family = new FontFamily(f.familyName, true, rank); 703 } 704 family.setFont(f, f.style); 705 } 706 707 /* 708 * Systems may have fonts with the same name. 709 * We want to register only one of such fonts (at least until 710 * such time as there might be APIs which can accommodate > 1). 711 * Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts, 712 * 4) Type1 fonts, 5) native fonts. 713 * 714 * If the new font has the same name as the old font, the higher 715 * ranked font gets added, replacing the lower ranked one. 716 * If the fonts are of equal rank, then make a special case of 717 * font configuration rank fonts, which are on closer inspection, 718 * OT/TT fonts such that the larger font is registered. This is 719 * a heuristic since a font may be "larger" in the sense of more 720 * code points, or be a larger "file" because it has more bitmaps. 721 * So it is possible that using filesize may lead to less glyphs, and 722 * using glyphs may lead to lower quality display. Probably number 723 * of glyphs is the ideal, but filesize is information we already 724 * have and is good enough for the known cases. 725 * Also don't want to register fonts that match JRE font families 726 * but are coming from a source other than the JRE. 727 * This will ensure that we will algorithmically style the JRE 728 * plain font and get the same set of glyphs for all styles. 729 * 730 * Note that this method returns a value 731 * if it returns the same object as its argument that means this 732 * font was newly registered. 733 * If it returns a different object it means this font already exists, 734 * and you should use that one. 735 * If it returns null means this font was not registered and none 736 * in that name is registered. The caller must find a substitute 737 */ 738 private PhysicalFont addToFontList(PhysicalFont f, int rank) { 739 740 String fontName = f.fullName; 741 String familyName = f.familyName; 742 if (fontName == null || "".equals(fontName)) { 743 return null; 744 } 745 if (compositeFonts.containsKey(fontName)) { 746 /* Don't register any font that has the same name as a composite */ 747 return null; 748 } 749 f.setRank(rank); 750 if (!physicalFonts.containsKey(fontName)) { 751 if (FontUtilities.isLogging()) { 752 FontUtilities.getLogger().info("Add to Family "+familyName + 753 ", Font " + fontName + " rank="+rank); 754 } 755 physicalFonts.put(fontName, f); 756 FontFamily family = FontFamily.getFamily(familyName); 757 if (family == null) { 758 family = new FontFamily(familyName, false, rank); 759 family.setFont(f, f.style); 760 } else if (family.getRank() >= rank) { 761 family.setFont(f, f.style); 762 } 763 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f); 764 return f; 765 } else { 766 PhysicalFont newFont = f; 767 PhysicalFont oldFont = physicalFonts.get(fontName); 768 if (oldFont == null) { 769 return null; 770 } 771 /* If the new font is of an equal or higher rank, it is a 772 * candidate to replace the current one, subject to further tests. 773 */ 774 if (oldFont.getRank() >= rank) { 775 776 /* All fonts initialise their mapper when first 777 * used. If the mapper is non-null then this font 778 * has been accessed at least once. In that case 779 * do not replace it. This may be overly stringent, 780 * but its probably better not to replace a font that 781 * someone is already using without a compelling reason. 782 * Additionally the primary case where it is known 783 * this behaviour is important is in certain composite 784 * fonts, and since all the components of a given 785 * composite are usually initialised together this 786 * is unlikely. For this to be a problem, there would 787 * have to be a case where two different composites used 788 * different versions of the same-named font, and they 789 * were initialised and used at separate times. 790 * In that case we continue on and allow the new font to 791 * be installed, but replaceFont will continue to allow 792 * the original font to be used in Composite fonts. 793 */ 794 if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) { 795 return oldFont; 796 } 797 798 /* Normally we require a higher rank to replace a font, 799 * but as a special case, if the two fonts are the same rank, 800 * and are instances of TrueTypeFont we want the 801 * more complete (larger) one. 802 */ 803 if (oldFont.getRank() == rank) { 804 if (oldFont instanceof TrueTypeFont && 805 newFont instanceof TrueTypeFont) { 806 TrueTypeFont oldTTFont = (TrueTypeFont)oldFont; 807 TrueTypeFont newTTFont = (TrueTypeFont)newFont; 808 if (oldTTFont.fileSize >= newTTFont.fileSize) { 809 return oldFont; 810 } 811 } else { 812 return oldFont; 813 } 814 } 815 /* Don't replace ever JRE fonts. 816 * This test is in case a font configuration references 817 * a Lucida font, which has been mapped to a Lucida 818 * from the host O/S. The assumption here is that any 819 * such font configuration file is probably incorrect, or 820 * the host O/S version is for the use of AWT. 821 * In other words if we reach here, there's a possible 822 * problem with our choice of font configuration fonts. 823 */ 824 if (oldFont.platName.startsWith(jreFontDirName)) { 825 if (FontUtilities.isLogging()) { 826 FontUtilities.getLogger() 827 .warning("Unexpected attempt to replace a JRE " + 828 " font " + fontName + " from " + 829 oldFont.platName + 830 " with " + newFont.platName); 831 } 832 return oldFont; 833 } 834 835 if (FontUtilities.isLogging()) { 836 FontUtilities.getLogger() 837 .info("Replace in Family " + familyName + 838 ",Font " + fontName + " new rank="+rank + 839 " from " + oldFont.platName + 840 " with " + newFont.platName); 841 } 842 replaceFont(oldFont, newFont); 843 physicalFonts.put(fontName, newFont); 844 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), 845 newFont); 846 847 FontFamily family = FontFamily.getFamily(familyName); 848 if (family == null) { 849 family = new FontFamily(familyName, false, rank); 850 family.setFont(newFont, newFont.style); 851 } else if (family.getRank() >= rank) { 852 family.setFont(newFont, newFont.style); 853 } 854 return newFont; 855 } else { 856 return oldFont; 857 } 858 } 859 } 860 861 public Font2D[] getRegisteredFonts() { 862 PhysicalFont[] physFonts = getPhysicalFonts(); 863 int mcf = maxCompFont; /* for MT-safety */ 864 Font2D[] regFonts = new Font2D[physFonts.length+mcf]; 865 System.arraycopy(compFonts, 0, regFonts, 0, mcf); 866 System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length); 867 return regFonts; 868 } 869 870 protected PhysicalFont[] getPhysicalFonts() { 871 return physicalFonts.values().toArray(new PhysicalFont[0]); 872 } 873 874 875 /* The class FontRegistrationInfo is used when a client says not 876 * to register a font immediately. This mechanism is used to defer 877 * initialisation of all the components of composite fonts at JRE 878 * start-up. The CompositeFont class is "aware" of this and when it 879 * is first used it asks for the registration of its components. 880 * Also in the event that any physical font is requested the 881 * deferred fonts are initialised before triggering a search of the 882 * system. 883 * Two maps are used. One to track the deferred fonts. The 884 * other to track the fonts that have been initialised through this 885 * mechanism. 886 */ 887 888 private static final class FontRegistrationInfo { 889 890 String fontFilePath; 891 String[] nativeNames; 892 int fontFormat; 893 boolean javaRasterizer; 894 int fontRank; 895 896 FontRegistrationInfo(String fontPath, String[] names, int format, 897 boolean useJavaRasterizer, int rank) { 898 this.fontFilePath = fontPath; 899 this.nativeNames = names; 900 this.fontFormat = format; 901 this.javaRasterizer = useJavaRasterizer; 902 this.fontRank = rank; 903 } 904 } 905 906 private final ConcurrentHashMap<String, FontRegistrationInfo> 907 deferredFontFiles = 908 new ConcurrentHashMap<String, FontRegistrationInfo>(); 909 private final ConcurrentHashMap<String, Font2DHandle> 910 initialisedFonts = new ConcurrentHashMap<String, Font2DHandle>(); 911 912 /* Remind: possibly enhance initialiseDeferredFonts() to be 913 * optionally given a name and a style and it could stop when it 914 * finds that font - but this would be a problem if two of the 915 * fonts reference the same font face name (cf the Solaris 916 * euro fonts). 917 */ 918 protected synchronized void initialiseDeferredFonts() { 919 for (String fileName : deferredFontFiles.keySet()) { 920 initialiseDeferredFont(fileName); 921 } 922 } 923 924 protected synchronized void registerDeferredJREFonts(String jreDir) { 925 for (FontRegistrationInfo info : deferredFontFiles.values()) { 926 if (info.fontFilePath != null && 927 info.fontFilePath.startsWith(jreDir)) { 928 initialiseDeferredFont(info.fontFilePath); 929 } 930 } 931 } 932 933 public boolean isDeferredFont(String fileName) { 934 return deferredFontFiles.containsKey(fileName); 935 } 936 937 /* We keep a map of the files which contain the Lucida fonts so we 938 * don't need to search for them. 939 * But since we know what fonts these files contain, we can also avoid 940 * opening them to look for a font name we don't recognise - see 941 * findDeferredFont(). 942 * For typical cases where the font isn't a JRE one the overhead is 943 * this method call, HashMap.get() and null reference test, then 944 * a boolean test of noOtherJREFontFiles. 945 */ 946 public 947 /*private*/ PhysicalFont findJREDeferredFont(String name, int style) { 948 949 PhysicalFont physicalFont; 950 String nameAndStyle = name.toLowerCase(Locale.ENGLISH) + style; 951 String fileName = jreFontMap.get(nameAndStyle); 952 if (fileName != null) { 953 fileName = jreFontDirName + File.separator + fileName; 954 if (deferredFontFiles.get(fileName) != null) { 955 physicalFont = initialiseDeferredFont(fileName); 956 if (physicalFont != null && 957 (physicalFont.getFontName(null).equalsIgnoreCase(name) || 958 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) 959 && physicalFont.style == style) { 960 return physicalFont; 961 } 962 } 963 } 964 965 /* Iterate over the deferred font files looking for any in the 966 * jre directory that we didn't recognise, open each of these. 967 * In almost all installations this will quickly fall through 968 * because only the Lucidas will be present and jreOtherFontFiles 969 * will be empty. 970 * noOtherJREFontFiles is used so we can skip this block as soon 971 * as its determined that its not needed - almost always after the 972 * very first time through. 973 */ 974 if (noOtherJREFontFiles) { 975 return null; 976 } 977 synchronized (jreLucidaFontFiles) { 978 if (jreOtherFontFiles == null) { 979 HashSet<String> otherFontFiles = new HashSet<String>(); 980 for (String deferredFile : deferredFontFiles.keySet()) { 981 File file = new File(deferredFile); 982 String dir = file.getParent(); 983 String fname = file.getName(); 984 /* skip names which aren't absolute, aren't in the JRE 985 * directory, or are known Lucida fonts. 986 */ 987 if (dir == null || 988 !dir.equals(jreFontDirName) || 989 jreLucidaFontFiles.contains(fname)) { 990 continue; 991 } 992 otherFontFiles.add(deferredFile); 993 } 994 jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY); 995 if (jreOtherFontFiles.length == 0) { 996 noOtherJREFontFiles = true; 997 } 998 } 999 1000 for (int i=0; i<jreOtherFontFiles.length;i++) { 1001 fileName = jreOtherFontFiles[i]; 1002 if (fileName == null) { 1003 continue; 1004 } 1005 jreOtherFontFiles[i] = null; 1006 physicalFont = initialiseDeferredFont(fileName); 1007 if (physicalFont != null && 1008 (physicalFont.getFontName(null).equalsIgnoreCase(name) || 1009 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) 1010 && physicalFont.style == style) { 1011 return physicalFont; 1012 } 1013 } 1014 } 1015 1016 return null; 1017 } 1018 1019 /* This skips JRE installed fonts. */ 1020 private PhysicalFont findOtherDeferredFont(String name, int style) { 1021 for (String fileName : deferredFontFiles.keySet()) { 1022 File file = new File(fileName); 1023 String dir = file.getParent(); 1024 String fname = file.getName(); 1025 if (dir != null && 1026 dir.equals(jreFontDirName) && 1027 jreLucidaFontFiles.contains(fname)) { 1028 continue; 1029 } 1030 PhysicalFont physicalFont = initialiseDeferredFont(fileName); 1031 if (physicalFont != null && 1032 (physicalFont.getFontName(null).equalsIgnoreCase(name) || 1033 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) && 1034 physicalFont.style == style) { 1035 return physicalFont; 1036 } 1037 } 1038 return null; 1039 } 1040 1041 private PhysicalFont findDeferredFont(String name, int style) { 1042 1043 PhysicalFont physicalFont = findJREDeferredFont(name, style); 1044 if (physicalFont != null) { 1045 return physicalFont; 1046 } else { 1047 return findOtherDeferredFont(name, style); 1048 } 1049 } 1050 1051 public void registerDeferredFont(String fileNameKey, 1052 String fullPathName, 1053 String[] nativeNames, 1054 int fontFormat, 1055 boolean useJavaRasterizer, 1056 int fontRank) { 1057 FontRegistrationInfo regInfo = 1058 new FontRegistrationInfo(fullPathName, nativeNames, fontFormat, 1059 useJavaRasterizer, fontRank); 1060 deferredFontFiles.put(fileNameKey, regInfo); 1061 } 1062 1063 1064 public synchronized 1065 PhysicalFont initialiseDeferredFont(String fileNameKey) { 1066 1067 if (fileNameKey == null) { 1068 return null; 1069 } 1070 if (FontUtilities.isLogging()) { 1071 FontUtilities.getLogger() 1072 .info("Opening deferred font file " + fileNameKey); 1073 } 1074 1075 PhysicalFont physicalFont; 1076 FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey); 1077 if (regInfo != null) { 1078 deferredFontFiles.remove(fileNameKey); 1079 physicalFont = registerFontFile(regInfo.fontFilePath, 1080 regInfo.nativeNames, 1081 regInfo.fontFormat, 1082 regInfo.javaRasterizer, 1083 regInfo.fontRank); 1084 1085 1086 if (physicalFont != null) { 1087 /* Store the handle, so that if a font is bad, we 1088 * retrieve the substituted font. 1089 */ 1090 initialisedFonts.put(fileNameKey, physicalFont.handle); 1091 } else { 1092 initialisedFonts.put(fileNameKey, 1093 getDefaultPhysicalFont().handle); 1094 } 1095 } else { 1096 Font2DHandle handle = initialisedFonts.get(fileNameKey); 1097 if (handle == null) { 1098 /* Probably shouldn't happen, but just in case */ 1099 physicalFont = getDefaultPhysicalFont(); 1100 } else { 1101 physicalFont = (PhysicalFont)(handle.font2D); 1102 } 1103 } 1104 return physicalFont; 1105 } 1106 1107 public boolean isRegisteredFontFile(String name) { 1108 return registeredFonts.containsKey(name); 1109 } 1110 1111 public PhysicalFont getRegisteredFontFile(String name) { 1112 return registeredFonts.get(name); 1113 } 1114 1115 /* Note that the return value from this method is not always 1116 * derived from this file, and may be null. See addToFontList for 1117 * some explanation of this. 1118 */ 1119 public PhysicalFont registerFontFile(String fileName, 1120 String[] nativeNames, 1121 int fontFormat, 1122 boolean useJavaRasterizer, 1123 int fontRank) { 1124 1125 PhysicalFont regFont = registeredFonts.get(fileName); 1126 if (regFont != null) { 1127 return regFont; 1128 } 1129 1130 PhysicalFont physicalFont = null; 1131 try { 1132 String name; 1133 1134 switch (fontFormat) { 1135 1136 case FONTFORMAT_TRUETYPE: 1137 int fn = 0; 1138 TrueTypeFont ttf; 1139 do { 1140 ttf = new TrueTypeFont(fileName, nativeNames, fn++, 1141 useJavaRasterizer); 1142 PhysicalFont pf = addToFontList(ttf, fontRank); 1143 if (physicalFont == null) { 1144 physicalFont = pf; 1145 } 1146 } 1147 while (fn < ttf.getFontCount()); 1148 break; 1149 1150 case FONTFORMAT_TYPE1: 1151 Type1Font t1f = new Type1Font(fileName, nativeNames); 1152 physicalFont = addToFontList(t1f, fontRank); 1153 break; 1154 1155 case FONTFORMAT_NATIVE: 1156 NativeFont nf = new NativeFont(fileName, false); 1157 physicalFont = addToFontList(nf, fontRank); 1158 default: 1159 1160 } 1161 if (FontUtilities.isLogging()) { 1162 FontUtilities.getLogger() 1163 .info("Registered file " + fileName + " as font " + 1164 physicalFont + " rank=" + fontRank); 1165 } 1166 } catch (FontFormatException ffe) { 1167 if (FontUtilities.isLogging()) { 1168 FontUtilities.getLogger().warning("Unusable font: " + 1169 fileName + " " + ffe.toString()); 1170 } 1171 } 1172 if (physicalFont != null && 1173 fontFormat != FONTFORMAT_NATIVE) { 1174 registeredFonts.put(fileName, physicalFont); 1175 } 1176 return physicalFont; 1177 } 1178 1179 public void registerFonts(String[] fileNames, 1180 String[][] nativeNames, 1181 int fontCount, 1182 int fontFormat, 1183 boolean useJavaRasterizer, 1184 int fontRank, boolean defer) { 1185 1186 for (int i=0; i < fontCount; i++) { 1187 if (defer) { 1188 registerDeferredFont(fileNames[i],fileNames[i], nativeNames[i], 1189 fontFormat, useJavaRasterizer, fontRank); 1190 } else { 1191 registerFontFile(fileNames[i], nativeNames[i], 1192 fontFormat, useJavaRasterizer, fontRank); 1193 } 1194 } 1195 } 1196 1197 /* 1198 * This is the Physical font used when some other font on the system 1199 * can't be located. There has to be at least one font or the font 1200 * system is not useful and the graphics environment cannot sustain 1201 * the Java platform. 1202 */ 1203 public PhysicalFont getDefaultPhysicalFont() { 1204 if (defaultPhysicalFont == null) { 1205 /* findFont2D will load all fonts before giving up the search. 1206 * If the JRE Lucida isn't found (eg because the JRE fonts 1207 * directory is missing), it could find another version of Lucida 1208 * from the host system. This is OK because at that point we are 1209 * trying to gracefully handle/recover from a system 1210 * misconfiguration and this is probably a reasonable substitution. 1211 */ 1212 defaultPhysicalFont = (PhysicalFont) 1213 findFont2D("Lucida Sans Regular", Font.PLAIN, NO_FALLBACK); 1214 if (defaultPhysicalFont == null) { 1215 defaultPhysicalFont = (PhysicalFont) 1216 findFont2D("Arial", Font.PLAIN, NO_FALLBACK); 1217 } 1218 if (defaultPhysicalFont == null) { 1219 /* Because of the findFont2D call above, if we reach here, we 1220 * know all fonts have already been loaded, just accept any 1221 * match at this point. If this fails we are in real trouble 1222 * and I don't know how to recover from there being absolutely 1223 * no fonts anywhere on the system. 1224 */ 1225 Iterator i = physicalFonts.values().iterator(); 1226 if (i.hasNext()) { 1227 defaultPhysicalFont = (PhysicalFont)i.next(); 1228 } else { 1229 throw new Error("Probable fatal error:No fonts found."); 1230 } 1231 } 1232 } 1233 return defaultPhysicalFont; 1234 } 1235 1236 public CompositeFont getDefaultLogicalFont(int style) { 1237 return (CompositeFont)findFont2D("dialog", style, NO_FALLBACK); 1238 } 1239 1240 /* 1241 * return String representation of style prepended with "." 1242 * This is useful for performance to avoid unnecessary string operations. 1243 */ 1244 private static String dotStyleStr(int num) { 1245 switch(num){ 1246 case Font.BOLD: 1247 return ".bold"; 1248 case Font.ITALIC: 1249 return ".italic"; 1250 case Font.ITALIC | Font.BOLD: 1251 return ".bolditalic"; 1252 default: 1253 return ".plain"; 1254 } 1255 } 1256 1257 /* This is implemented only on windows and is called from code that 1258 * executes only on windows. This isn't pretty but its not a precedent 1259 * in this file. This very probably should be cleaned up at some point. 1260 */ 1261 protected void 1262 populateFontFileNameMap(HashMap<String,String> fontToFileMap, 1263 HashMap<String,String> fontToFamilyNameMap, 1264 HashMap<String,ArrayList<String>> 1265 familyToFontListMap, 1266 Locale locale) { 1267 } 1268 1269 /* Obtained from Platform APIs (windows only) 1270 * Map from lower-case font full name to basename of font file. 1271 * Eg "arial bold" -> ARIALBD.TTF. 1272 * For TTC files, there is a mapping for each font in the file. 1273 */ 1274 private HashMap<String,String> fontToFileMap = null; 1275 1276 /* Obtained from Platform APIs (windows only) 1277 * Map from lower-case font full name to the name of its font family 1278 * Eg "arial bold" -> "Arial" 1279 */ 1280 private HashMap<String,String> fontToFamilyNameMap = null; 1281 1282 /* Obtained from Platform APIs (windows only) 1283 * Map from a lower-case family name to a list of full names of 1284 * the member fonts, eg: 1285 * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"] 1286 */ 1287 private HashMap<String,ArrayList<String>> familyToFontListMap= null; 1288 1289 /* The directories which contain platform fonts */ 1290 private String[] pathDirs = null; 1291 1292 private boolean haveCheckedUnreferencedFontFiles; 1293 1294 private String[] getFontFilesFromPath(boolean noType1) { 1295 final FilenameFilter filter; 1296 if (noType1) { 1297 filter = ttFilter; 1298 } else { 1299 filter = new TTorT1Filter(); 1300 } 1301 return (String[])AccessController.doPrivileged(new PrivilegedAction() { 1302 public Object run() { 1303 if (pathDirs.length == 1) { 1304 File dir = new File(pathDirs[0]); 1305 String[] files = dir.list(filter); 1306 if (files == null) { 1307 return new String[0]; 1308 } 1309 for (int f=0; f<files.length; f++) { 1310 files[f] = files[f].toLowerCase(); 1311 } 1312 return files; 1313 } else { 1314 ArrayList<String> fileList = new ArrayList<String>(); 1315 for (int i = 0; i< pathDirs.length; i++) { 1316 File dir = new File(pathDirs[i]); 1317 String[] files = dir.list(filter); 1318 if (files == null) { 1319 continue; 1320 } 1321 for (int f=0; f<files.length ; f++) { 1322 fileList.add(files[f].toLowerCase()); 1323 } 1324 } 1325 return fileList.toArray(STR_ARRAY); 1326 } 1327 } 1328 }); 1329 } 1330 1331 /* This is needed since some windows registry names don't match 1332 * the font names. 1333 * - UPC styled font names have a double space, but the 1334 * registry entry mapping to a file doesn't. 1335 * - Marlett is in a hidden file not listed in the registry 1336 * - The registry advertises that the file david.ttf contains a 1337 * font with the full name "David Regular" when in fact its 1338 * just "David". 1339 * Directly fix up these known cases as this is faster. 1340 * If a font which doesn't match these known cases has no file, 1341 * it may be a font that has been temporarily added to the known set 1342 * or it may be an installed font with a missing registry entry. 1343 * Installed fonts are those in the windows font directories. 1344 * Make a best effort attempt to locate these. 1345 * We obtain the list of TrueType fonts in these directories and 1346 * filter out all the font files we already know about from the registry. 1347 * What remains may be "bad" fonts, duplicate fonts, or perhaps the 1348 * missing font(s) we are looking for. 1349 * Open each of these files to find out. 1350 */ 1351 private void resolveWindowsFonts() { 1352 1353 ArrayList<String> unmappedFontNames = null; 1354 for (String font : fontToFamilyNameMap.keySet()) { 1355 String file = fontToFileMap.get(font); 1356 if (file == null) { 1357 if (font.indexOf(" ") > 0) { 1358 String newName = font.replaceFirst(" ", " "); 1359 file = fontToFileMap.get(newName); 1360 /* If this name exists and isn't for a valid name 1361 * replace the mapping to the file with this font 1362 */ 1363 if (file != null && 1364 !fontToFamilyNameMap.containsKey(newName)) { 1365 fontToFileMap.remove(newName); 1366 fontToFileMap.put(font, file); 1367 } 1368 } else if (font.equals("marlett")) { 1369 fontToFileMap.put(font, "marlett.ttf"); 1370 } else if (font.equals("david")) { 1371 file = fontToFileMap.get("david regular"); 1372 if (file != null) { 1373 fontToFileMap.remove("david regular"); 1374 fontToFileMap.put("david", file); 1375 } 1376 } else { 1377 if (unmappedFontNames == null) { 1378 unmappedFontNames = new ArrayList<String>(); 1379 } 1380 unmappedFontNames.add(font); 1381 } 1382 } 1383 } 1384 1385 if (unmappedFontNames != null) { 1386 HashSet<String> unmappedFontFiles = new HashSet<String>(); 1387 1388 /* Every font key in fontToFileMap ought to correspond to a 1389 * font key in fontToFamilyNameMap. Entries that don't seem 1390 * to correspond are likely fonts that were named differently 1391 * by GDI than in the registry. One known cause of this is when 1392 * Windows has had its regional settings changed so that from 1393 * GDI we get a localised (eg Chinese or Japanese) name for the 1394 * font, but the registry retains the English version of the name 1395 * that corresponded to the "install" locale for windows. 1396 * Since we are in this code block because there are unmapped 1397 * font names, we can look to find unused font->file mappings 1398 * and then open the files to read the names. We don't generally 1399 * want to open font files, as its a performance hit, but this 1400 * occurs only for a small number of fonts on specific system 1401 * configs - ie is believed that a "true" Japanese windows would 1402 * have JA names in the registry too. 1403 * Clone fontToFileMap and remove from the clone all keys which 1404 * match a fontToFamilyNameMap key. What remains maps to the 1405 * files we want to open to find the fonts GDI returned. 1406 * A font in such a file is added to the fontToFileMap after 1407 * checking its one of the unmappedFontNames we are looking for. 1408 * The original name that didn't map is removed from fontToFileMap 1409 * so essentially this "fixes up" fontToFileMap to use the same 1410 * name as GDI. 1411 * Also note that typically the fonts for which this occurs in 1412 * CJK locales are TTC fonts and not all fonts in a TTC may have 1413 * localised names. Eg MSGOTHIC.TTC contains 3 fonts and one of 1414 * them "MS UI Gothic" has no JA name whereas the other two do. 1415 * So not every font in these files is unmapped or new. 1416 */ 1417 HashMap<String,String> ffmapCopy = 1418 (HashMap<String,String>)(fontToFileMap.clone()); 1419 for (String key : fontToFamilyNameMap.keySet()) { 1420 ffmapCopy.remove(key); 1421 } 1422 for (String key : ffmapCopy.keySet()) { 1423 unmappedFontFiles.add(ffmapCopy.get(key)); 1424 fontToFileMap.remove(key); 1425 } 1426 1427 resolveFontFiles(unmappedFontFiles, unmappedFontNames); 1428 1429 /* If there are still unmapped font names, this means there's 1430 * something that wasn't in the registry. We need to get all 1431 * the font files directly and look at the ones that weren't 1432 * found in the registry. 1433 */ 1434 if (unmappedFontNames.size() > 0) { 1435 1436 /* getFontFilesFromPath() returns all lower case names. 1437 * To compare we also need lower case 1438 * versions of the names from the registry. 1439 */ 1440 ArrayList<String> registryFiles = new ArrayList<String>(); 1441 1442 for (String regFile : fontToFileMap.values()) { 1443 registryFiles.add(regFile.toLowerCase()); 1444 } 1445 /* We don't look for Type1 files here as windows will 1446 * not enumerate these, so aren't useful in reconciling 1447 * GDI's unmapped files. We do find these later when 1448 * we enumerate all fonts. 1449 */ 1450 for (String pathFile : getFontFilesFromPath(true)) { 1451 if (!registryFiles.contains(pathFile)) { 1452 unmappedFontFiles.add(pathFile); 1453 } 1454 } 1455 1456 resolveFontFiles(unmappedFontFiles, unmappedFontNames); 1457 } 1458 1459 /* remove from the set of names that will be returned to the 1460 * user any fonts that can't be mapped to files. 1461 */ 1462 if (unmappedFontNames.size() > 0) { 1463 int sz = unmappedFontNames.size(); 1464 for (int i=0; i<sz; i++) { 1465 String name = unmappedFontNames.get(i); 1466 String familyName = fontToFamilyNameMap.get(name); 1467 if (familyName != null) { 1468 ArrayList family = familyToFontListMap.get(familyName); 1469 if (family != null) { 1470 if (family.size() <= 1) { 1471 familyToFontListMap.remove(familyName); 1472 } 1473 } 1474 } 1475 fontToFamilyNameMap.remove(name); 1476 if (FontUtilities.isLogging()) { 1477 FontUtilities.getLogger() 1478 .info("No file for font:" + name); 1479 } 1480 } 1481 } 1482 } 1483 } 1484 1485 /** 1486 * In some cases windows may have fonts in the fonts folder that 1487 * don't show up in the registry or in the GDI calls to enumerate fonts. 1488 * The only way to find these is to list the directory. We invoke this 1489 * only in getAllFonts/Families, so most searches for a specific 1490 * font that is satisfied by the GDI/registry calls don't take the 1491 * additional hit of listing the directory. This hit is small enough 1492 * that its not significant in these 'enumerate all the fonts' cases. 1493 * The basic approach is to cross-reference the files windows found 1494 * with the ones in the directory listing approach, and for each 1495 * in the latter list that is missing from the former list, register it. 1496 */ 1497 private synchronized void checkForUnreferencedFontFiles() { 1498 if (haveCheckedUnreferencedFontFiles) { 1499 return; 1500 } 1501 haveCheckedUnreferencedFontFiles = true; 1502 if (!FontUtilities.isWindows) { 1503 return; 1504 } 1505 /* getFontFilesFromPath() returns all lower case names. 1506 * To compare we also need lower case 1507 * versions of the names from the registry. 1508 */ 1509 ArrayList<String> registryFiles = new ArrayList<String>(); 1510 for (String regFile : fontToFileMap.values()) { 1511 registryFiles.add(regFile.toLowerCase()); 1512 } 1513 1514 /* To avoid any issues with concurrent modification, create 1515 * copies of the existing maps, add the new fonts into these 1516 * and then replace the references to the old ones with the 1517 * new maps. ConcurrentHashmap is another option but its a lot 1518 * more changes and with this exception, these maps are intended 1519 * to be static. 1520 */ 1521 HashMap<String,String> fontToFileMap2 = null; 1522 HashMap<String,String> fontToFamilyNameMap2 = null; 1523 HashMap<String,ArrayList<String>> familyToFontListMap2 = null;; 1524 1525 for (String pathFile : getFontFilesFromPath(false)) { 1526 if (!registryFiles.contains(pathFile)) { 1527 if (FontUtilities.isLogging()) { 1528 FontUtilities.getLogger() 1529 .info("Found non-registry file : " + pathFile); 1530 } 1531 PhysicalFont f = registerFontFile(getPathName(pathFile)); 1532 if (f == null) { 1533 continue; 1534 } 1535 if (fontToFileMap2 == null) { 1536 fontToFileMap2 = new HashMap<String,String>(fontToFileMap); 1537 fontToFamilyNameMap2 = 1538 new HashMap<String,String>(fontToFamilyNameMap); 1539 familyToFontListMap2 = new 1540 HashMap<String,ArrayList<String>>(familyToFontListMap); 1541 } 1542 String fontName = f.getFontName(null); 1543 String family = f.getFamilyName(null); 1544 String familyLC = family.toLowerCase(); 1545 fontToFamilyNameMap2.put(fontName, family); 1546 fontToFileMap2.put(fontName, pathFile); 1547 ArrayList<String> fonts = familyToFontListMap2.get(familyLC); 1548 if (fonts == null) { 1549 fonts = new ArrayList<String>(); 1550 } else { 1551 fonts = new ArrayList<String>(fonts); 1552 } 1553 fonts.add(fontName); 1554 familyToFontListMap2.put(familyLC, fonts); 1555 } 1556 } 1557 if (fontToFileMap2 != null) { 1558 fontToFileMap = fontToFileMap2; 1559 familyToFontListMap = familyToFontListMap2; 1560 fontToFamilyNameMap = fontToFamilyNameMap2; 1561 } 1562 } 1563 1564 private void resolveFontFiles(HashSet<String> unmappedFiles, 1565 ArrayList<String> unmappedFonts) { 1566 1567 Locale l = SunToolkit.getStartupLocale(); 1568 1569 for (String file : unmappedFiles) { 1570 try { 1571 int fn = 0; 1572 TrueTypeFont ttf; 1573 String fullPath = getPathName(file); 1574 if (FontUtilities.isLogging()) { 1575 FontUtilities.getLogger() 1576 .info("Trying to resolve file " + fullPath); 1577 } 1578 do { 1579 ttf = new TrueTypeFont(fullPath, null, fn++, false); 1580 // prefer the font's locale name. 1581 String fontName = ttf.getFontName(l).toLowerCase(); 1582 if (unmappedFonts.contains(fontName)) { 1583 fontToFileMap.put(fontName, file); 1584 unmappedFonts.remove(fontName); 1585 if (FontUtilities.isLogging()) { 1586 FontUtilities.getLogger() 1587 .info("Resolved absent registry entry for " + 1588 fontName + " located in " + fullPath); 1589 } 1590 } 1591 } 1592 while (fn < ttf.getFontCount()); 1593 } catch (Exception e) { 1594 } 1595 } 1596 } 1597 1598 /* Hardwire the English names and expected file names of fonts 1599 * commonly used at start up. Avoiding until later even the small 1600 * cost of calling platform APIs to locate these can help. 1601 * The code that registers these fonts needs to "bail" if any 1602 * of the files do not exist, so it will verify the existence of 1603 * all non-null file names first. 1604 * They are added in to a map with nominally the first 1605 * word in the name of the family as the key. In all the cases 1606 * we are using the the family name is a single word, and as is 1607 * more or less required the family name is the initial sequence 1608 * in a full name. So lookup first finds the matching description, 1609 * then registers the whole family, returning the right font. 1610 */ 1611 public static class FamilyDescription { 1612 public String familyName; 1613 public String plainFullName; 1614 public String boldFullName; 1615 public String italicFullName; 1616 public String boldItalicFullName; 1617 public String plainFileName; 1618 public String boldFileName; 1619 public String italicFileName; 1620 public String boldItalicFileName; 1621 } 1622 1623 static HashMap<String, FamilyDescription> platformFontMap; 1624 1625 /** 1626 * default implementation does nothing. 1627 */ 1628 public HashMap<String, FamilyDescription> populateHardcodedFileNameMap() { 1629 return new HashMap<String, FamilyDescription>(0); 1630 } 1631 1632 Font2D findFontFromPlatformMap(String lcName, int style) { 1633 if (platformFontMap == null) { 1634 platformFontMap = populateHardcodedFileNameMap(); 1635 } 1636 1637 if (platformFontMap == null || platformFontMap.size() == 0) { 1638 return null; 1639 } 1640 1641 int spaceIndex = lcName.indexOf(' '); 1642 String firstWord = lcName; 1643 if (spaceIndex > 0) { 1644 firstWord = lcName.substring(0, spaceIndex); 1645 } 1646 1647 FamilyDescription fd = platformFontMap.get(firstWord); 1648 if (fd == null) { 1649 return null; 1650 } 1651 /* Once we've established that its at least the first word, 1652 * we need to dig deeper to make sure its a match for either 1653 * a full name, or the family name, to make sure its not 1654 * a request for some other font that just happens to start 1655 * with the same first word. 1656 */ 1657 int styleIndex = -1; 1658 if (lcName.equalsIgnoreCase(fd.plainFullName)) { 1659 styleIndex = 0; 1660 } else if (lcName.equalsIgnoreCase(fd.boldFullName)) { 1661 styleIndex = 1; 1662 } else if (lcName.equalsIgnoreCase(fd.italicFullName)) { 1663 styleIndex = 2; 1664 } else if (lcName.equalsIgnoreCase(fd.boldItalicFullName)) { 1665 styleIndex = 3; 1666 } 1667 if (styleIndex == -1 && !lcName.equalsIgnoreCase(fd.familyName)) { 1668 return null; 1669 } 1670 1671 String plainFile = null, boldFile = null, 1672 italicFile = null, boldItalicFile = null; 1673 1674 boolean failure = false; 1675 /* In a terminal server config, its possible that getPathName() 1676 * will return null, if the file doesn't exist, hence the null 1677 * checks on return. But in the normal client config we need to 1678 * follow this up with a check to see if all the files really 1679 * exist for the non-null paths. 1680 */ 1681 getPlatformFontDirs(noType1Font); 1682 1683 if (fd.plainFileName != null) { 1684 plainFile = getPathName(fd.plainFileName); 1685 if (plainFile == null) { 1686 failure = true; 1687 } 1688 } 1689 1690 if (fd.boldFileName != null) { 1691 boldFile = getPathName(fd.boldFileName); 1692 if (boldFile == null) { 1693 failure = true; 1694 } 1695 } 1696 1697 if (fd.italicFileName != null) { 1698 italicFile = getPathName(fd.italicFileName); 1699 if (italicFile == null) { 1700 failure = true; 1701 } 1702 } 1703 1704 if (fd.boldItalicFileName != null) { 1705 boldItalicFile = getPathName(fd.boldItalicFileName); 1706 if (boldItalicFile == null) { 1707 failure = true; 1708 } 1709 } 1710 1711 if (failure) { 1712 if (FontUtilities.isLogging()) { 1713 FontUtilities.getLogger(). 1714 info("Hardcoded file missing looking for " + lcName); 1715 } 1716 platformFontMap.remove(firstWord); 1717 return null; 1718 } 1719 1720 /* Some of these may be null,as not all styles have to exist */ 1721 final String[] files = { 1722 plainFile, boldFile, italicFile, boldItalicFile } ; 1723 1724 failure = java.security.AccessController.doPrivileged( 1725 new java.security.PrivilegedAction<Boolean>() { 1726 public Boolean run() { 1727 for (int i=0; i<files.length; i++) { 1728 if (files[i] == null) { 1729 continue; 1730 } 1731 File f = new File(files[i]); 1732 if (!f.exists()) { 1733 return Boolean.TRUE; 1734 } 1735 } 1736 return Boolean.FALSE; 1737 } 1738 }); 1739 1740 if (failure) { 1741 if (FontUtilities.isLogging()) { 1742 FontUtilities.getLogger(). 1743 info("Hardcoded file missing looking for " + lcName); 1744 } 1745 platformFontMap.remove(firstWord); 1746 return null; 1747 } 1748 1749 /* If we reach here we know that we have all the files we 1750 * expect, so all should be fine so long as the contents 1751 * are what we'd expect. Now on to registering the fonts. 1752 * Currently this code only looks for TrueType fonts, so format 1753 * and rank can be specified without looking at the filename. 1754 */ 1755 Font2D font = null; 1756 for (int f=0;f<files.length;f++) { 1757 if (files[f] == null) { 1758 continue; 1759 } 1760 PhysicalFont pf = 1761 registerFontFile(files[f], null, 1762 FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK); 1763 if (f == styleIndex) { 1764 font = pf; 1765 } 1766 } 1767 1768 1769 /* Two general cases need a bit more work here. 1770 * 1) If font is null, then it was perhaps a request for a 1771 * non-existent font, such as "Tahoma Italic", or a family name - 1772 * where family and full name of the plain font differ. 1773 * Fall back to finding the closest one in the family. 1774 * This could still fail if a client specified "Segoe" instead of 1775 * "Segoe UI". 1776 * 2) The request is of the form "MyFont Bold", style=Font.ITALIC, 1777 * and so we want to see if there's a Bold Italic font, or 1778 * "MyFamily", style=Font.BOLD, and we may have matched the plain, 1779 * but now need to revise that to the BOLD font. 1780 */ 1781 FontFamily fontFamily = FontFamily.getFamily(fd.familyName); 1782 if (fontFamily != null) { 1783 if (font == null) { 1784 font = fontFamily.getFont(style); 1785 if (font == null) { 1786 font = fontFamily.getClosestStyle(style); 1787 } 1788 } else if (style > 0 && style != font.style) { 1789 style |= font.style; 1790 font = fontFamily.getFont(style); 1791 if (font == null) { 1792 font = fontFamily.getClosestStyle(style); 1793 } 1794 } 1795 } 1796 1797 return font; 1798 } 1799 private synchronized HashMap<String,String> getFullNameToFileMap() { 1800 if (fontToFileMap == null) { 1801 1802 pathDirs = getPlatformFontDirs(noType1Font); 1803 1804 fontToFileMap = new HashMap<String,String>(100); 1805 fontToFamilyNameMap = new HashMap<String,String>(100); 1806 familyToFontListMap = new HashMap<String,ArrayList<String>>(50); 1807 populateFontFileNameMap(fontToFileMap, 1808 fontToFamilyNameMap, 1809 familyToFontListMap, 1810 Locale.ENGLISH); 1811 if (FontUtilities.isWindows) { 1812 resolveWindowsFonts(); 1813 } 1814 if (FontUtilities.isLogging()) { 1815 logPlatformFontInfo(); 1816 } 1817 } 1818 return fontToFileMap; 1819 } 1820 1821 private void logPlatformFontInfo() { 1822 PlatformLogger logger = FontUtilities.getLogger(); 1823 for (int i=0; i< pathDirs.length;i++) { 1824 logger.info("fontdir="+pathDirs[i]); 1825 } 1826 for (String keyName : fontToFileMap.keySet()) { 1827 logger.info("font="+keyName+" file="+ fontToFileMap.get(keyName)); 1828 } 1829 for (String keyName : fontToFamilyNameMap.keySet()) { 1830 logger.info("font="+keyName+" family="+ 1831 fontToFamilyNameMap.get(keyName)); 1832 } 1833 for (String keyName : familyToFontListMap.keySet()) { 1834 logger.info("family="+keyName+ " fonts="+ 1835 familyToFontListMap.get(keyName)); 1836 } 1837 } 1838 1839 /* Note this return list excludes logical fonts and JRE fonts */ 1840 protected String[] getFontNamesFromPlatform() { 1841 if (getFullNameToFileMap().size() == 0) { 1842 return null; 1843 } 1844 checkForUnreferencedFontFiles(); 1845 /* This odd code with TreeMap is used to preserve a historical 1846 * behaviour wrt the sorting order .. */ 1847 ArrayList<String> fontNames = new ArrayList<String>(); 1848 for (ArrayList<String> a : familyToFontListMap.values()) { 1849 for (String s : a) { 1850 fontNames.add(s); 1851 } 1852 } 1853 return fontNames.toArray(STR_ARRAY); 1854 } 1855 1856 public boolean gotFontsFromPlatform() { 1857 return getFullNameToFileMap().size() != 0; 1858 } 1859 1860 public String getFileNameForFontName(String fontName) { 1861 String fontNameLC = fontName.toLowerCase(Locale.ENGLISH); 1862 return fontToFileMap.get(fontNameLC); 1863 } 1864 1865 private PhysicalFont registerFontFile(String file) { 1866 if (new File(file).isAbsolute() && 1867 !registeredFonts.contains(file)) { 1868 int fontFormat = FONTFORMAT_NONE; 1869 int fontRank = Font2D.UNKNOWN_RANK; 1870 if (ttFilter.accept(null, file)) { 1871 fontFormat = FONTFORMAT_TRUETYPE; 1872 fontRank = Font2D.TTF_RANK; 1873 } else if 1874 (t1Filter.accept(null, file)) { 1875 fontFormat = FONTFORMAT_TYPE1; 1876 fontRank = Font2D.TYPE1_RANK; 1877 } 1878 if (fontFormat == FONTFORMAT_NONE) { 1879 return null; 1880 } 1881 return registerFontFile(file, null, fontFormat, false, fontRank); 1882 } 1883 return null; 1884 } 1885 1886 /* Used to register any font files that are found by platform APIs 1887 * that weren't previously found in the standard font locations. 1888 * the isAbsolute() check is needed since that's whats stored in the 1889 * set, and on windows, the fonts in the system font directory that 1890 * are in the fontToFileMap are just basenames. We don't want to try 1891 * to register those again, but we do want to register other registry 1892 * installed fonts. 1893 */ 1894 protected void registerOtherFontFiles(HashSet registeredFontFiles) { 1895 if (getFullNameToFileMap().size() == 0) { 1896 return; 1897 } 1898 for (String file : fontToFileMap.values()) { 1899 registerFontFile(file); 1900 } 1901 } 1902 1903 public boolean 1904 getFamilyNamesFromPlatform(TreeMap<String,String> familyNames, 1905 Locale requestedLocale) { 1906 if (getFullNameToFileMap().size() == 0) { 1907 return false; 1908 } 1909 checkForUnreferencedFontFiles(); 1910 for (String name : fontToFamilyNameMap.values()) { 1911 familyNames.put(name.toLowerCase(requestedLocale), name); 1912 } 1913 return true; 1914 } 1915 1916 /* Path may be absolute or a base file name relative to one of 1917 * the platform font directories 1918 */ 1919 private String getPathName(final String s) { 1920 File f = new File(s); 1921 if (f.isAbsolute()) { 1922 return s; 1923 } else if (pathDirs.length==1) { 1924 return pathDirs[0] + File.separator + s; 1925 } else { 1926 String path = java.security.AccessController.doPrivileged( 1927 new java.security.PrivilegedAction<String>() { 1928 public String run() { 1929 for (int p=0; p<pathDirs.length; p++) { 1930 File f = new File(pathDirs[p] +File.separator+ s); 1931 if (f.exists()) { 1932 return f.getAbsolutePath(); 1933 } 1934 } 1935 return null; 1936 } 1937 }); 1938 if (path != null) { 1939 return path; 1940 } 1941 } 1942 return s; // shouldn't happen, but harmless 1943 } 1944 1945 /* lcName is required to be lower case for use as a key. 1946 * lcName may be a full name, or a family name, and style may 1947 * be specified in addition to either of these. So be sure to 1948 * get the right one. Since an app *could* ask for "Foo Regular" 1949 * and later ask for "Foo Italic", if we don't register all the 1950 * styles, then logic in findFont2D may try to style the original 1951 * so we register the entire family if we get a match here. 1952 * This is still a big win because this code is invoked where 1953 * otherwise we would register all fonts. 1954 * It's also useful for the case where "Foo Bold" was specified with 1955 * style Font.ITALIC, as we would want in that case to try to return 1956 * "Foo Bold Italic" if it exists, and it is only by locating "Foo Bold" 1957 * and opening it that we really "know" it's Bold, and can look for 1958 * a font that supports that and the italic style. 1959 * The code in here is not overtly windows-specific but in fact it 1960 * is unlikely to be useful as is on other platforms. It is maintained 1961 * in this shared source file to be close to its sole client and 1962 * because so much of the logic is intertwined with the logic in 1963 * findFont2D. 1964 */ 1965 private Font2D findFontFromPlatform(String lcName, int style) { 1966 if (getFullNameToFileMap().size() == 0) { 1967 return null; 1968 } 1969 1970 ArrayList<String> family = null; 1971 String fontFile = null; 1972 String familyName = fontToFamilyNameMap.get(lcName); 1973 if (familyName != null) { 1974 fontFile = fontToFileMap.get(lcName); 1975 family = familyToFontListMap.get 1976 (familyName.toLowerCase(Locale.ENGLISH)); 1977 } else { 1978 family = familyToFontListMap.get(lcName); // is lcName is a family? 1979 if (family != null && family.size() > 0) { 1980 String lcFontName = family.get(0).toLowerCase(Locale.ENGLISH); 1981 if (lcFontName != null) { 1982 familyName = fontToFamilyNameMap.get(lcFontName); 1983 } 1984 } 1985 } 1986 if (family == null || familyName == null) { 1987 return null; 1988 } 1989 String [] fontList = (String[])family.toArray(STR_ARRAY); 1990 if (fontList.length == 0) { 1991 return null; 1992 } 1993 1994 /* first check that for every font in this family we can find 1995 * a font file. The specific reason for doing this is that 1996 * in at least one case on Windows a font has the face name "David" 1997 * but the registry entry is "David Regular". That is the "unique" 1998 * name of the font but in other cases the registry contains the 1999 * "full" name. See the specifications of name ids 3 and 4 in the 2000 * TrueType 'name' table. 2001 * In general this could cause a problem that we fail to register 2002 * if we all members of a family that we may end up mapping to 2003 * the wrong font member: eg return Bold when Plain is needed. 2004 */ 2005 for (int f=0;f<fontList.length;f++) { 2006 String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH); 2007 String fileName = fontToFileMap.get(fontNameLC); 2008 if (fileName == null) { 2009 if (FontUtilities.isLogging()) { 2010 FontUtilities.getLogger() 2011 .info("Platform lookup : No file for font " + 2012 fontList[f] + " in family " +familyName); 2013 } 2014 return null; 2015 } 2016 } 2017 2018 /* Currently this code only looks for TrueType fonts, so format 2019 * and rank can be specified without looking at the filename. 2020 */ 2021 PhysicalFont physicalFont = null; 2022 if (fontFile != null) { 2023 physicalFont = registerFontFile(getPathName(fontFile), null, 2024 FONTFORMAT_TRUETYPE, false, 2025 Font2D.TTF_RANK); 2026 } 2027 /* Register all fonts in this family. */ 2028 for (int f=0;f<fontList.length;f++) { 2029 String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH); 2030 String fileName = fontToFileMap.get(fontNameLC); 2031 if (fontFile != null && fontFile.equals(fileName)) { 2032 continue; 2033 } 2034 /* Currently this code only looks for TrueType fonts, so format 2035 * and rank can be specified without looking at the filename. 2036 */ 2037 registerFontFile(getPathName(fileName), null, 2038 FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK); 2039 } 2040 2041 Font2D font = null; 2042 FontFamily fontFamily = FontFamily.getFamily(familyName); 2043 /* Handle case where request "MyFont Bold", style=Font.ITALIC */ 2044 if (physicalFont != null) { 2045 style |= physicalFont.style; 2046 } 2047 if (fontFamily != null) { 2048 font = fontFamily.getFont(style); 2049 if (font == null) { 2050 font = fontFamily.getClosestStyle(style); 2051 } 2052 } 2053 return font; 2054 } 2055 2056 private ConcurrentHashMap<String, Font2D> fontNameCache = 2057 new ConcurrentHashMap<String, Font2D>(); 2058 2059 /* 2060 * The client supplies a name and a style. 2061 * The name could be a family name, or a full name. 2062 * A font may exist with the specified style, or it may 2063 * exist only in some other style. For non-native fonts the scaler 2064 * may be able to emulate the required style. 2065 */ 2066 public Font2D findFont2D(String name, int style, int fallback) { 2067 String lowerCaseName = name.toLowerCase(Locale.ENGLISH); 2068 String mapName = lowerCaseName + dotStyleStr(style); 2069 Font2D font; 2070 2071 /* If preferLocaleFonts() or preferProportionalFonts() has been 2072 * called we may be using an alternate set of composite fonts in this 2073 * app context. The presence of a pre-built name map indicates whether 2074 * this is so, and gives access to the alternate composite for the 2075 * name. 2076 */ 2077 if (_usingPerAppContextComposites) { 2078 ConcurrentHashMap<String, Font2D> altNameCache = 2079 (ConcurrentHashMap<String, Font2D>) 2080 AppContext.getAppContext().get(CompositeFont.class); 2081 if (altNameCache != null) { 2082 font = (Font2D)altNameCache.get(mapName); 2083 } else { 2084 font = null; 2085 } 2086 } else { 2087 font = fontNameCache.get(mapName); 2088 } 2089 if (font != null) { 2090 return font; 2091 } 2092 2093 if (FontUtilities.isLogging()) { 2094 FontUtilities.getLogger().info("Search for font: " + name); 2095 } 2096 2097 // The check below is just so that the bitmap fonts being set by 2098 // AWT and Swing thru the desktop properties do not trigger the 2099 // the load fonts case. The two bitmap fonts are now mapped to 2100 // appropriate equivalents for serif and sansserif. 2101 // Note that the cost of this comparison is only for the first 2102 // call until the map is filled. 2103 if (FontUtilities.isWindows) { 2104 if (lowerCaseName.equals("ms sans serif")) { 2105 name = "sansserif"; 2106 } else if (lowerCaseName.equals("ms serif")) { 2107 name = "serif"; 2108 } 2109 } 2110 2111 /* This isn't intended to support a client passing in the 2112 * string default, but if a client passes in null for the name 2113 * the java.awt.Font class internally substitutes this name. 2114 * So we need to recognise it here to prevent a loadFonts 2115 * on the unrecognised name. The only potential problem with 2116 * this is it would hide any real font called "default"! 2117 * But that seems like a potential problem we can ignore for now. 2118 */ 2119 if (lowerCaseName.equals("default")) { 2120 name = "dialog"; 2121 } 2122 2123 /* First see if its a family name. */ 2124 FontFamily family = FontFamily.getFamily(name); 2125 if (family != null) { 2126 font = family.getFontWithExactStyleMatch(style); 2127 if (font == null) { 2128 font = findDeferredFont(name, style); 2129 } 2130 if (font == null) { 2131 font = family.getFont(style); 2132 } 2133 if (font == null) { 2134 font = family.getClosestStyle(style); 2135 } 2136 if (font != null) { 2137 fontNameCache.put(mapName, font); 2138 return font; 2139 } 2140 } 2141 2142 /* If it wasn't a family name, it should be a full name of 2143 * either a composite, or a physical font 2144 */ 2145 font = fullNameToFont.get(lowerCaseName); 2146 if (font != null) { 2147 /* Check that the requested style matches the matched font's style. 2148 * But also match style automatically if the requested style is 2149 * "plain". This because the existing behaviour is that the fonts 2150 * listed via getAllFonts etc always list their style as PLAIN. 2151 * This does lead to non-commutative behaviours where you might 2152 * start with "Lucida Sans Regular" and ask for a BOLD version 2153 * and get "Lucida Sans DemiBold" but if you ask for the PLAIN 2154 * style of "Lucida Sans DemiBold" you get "Lucida Sans DemiBold". 2155 * This consistent however with what happens if you have a bold 2156 * version of a font and no plain version exists - alg. styling 2157 * doesn't "unbolden" the font. 2158 */ 2159 if (font.style == style || style == Font.PLAIN) { 2160 fontNameCache.put(mapName, font); 2161 return font; 2162 } else { 2163 /* If it was a full name like "Lucida Sans Regular", but 2164 * the style requested is "bold", then we want to see if 2165 * there's the appropriate match against another font in 2166 * that family before trying to load all fonts, or applying a 2167 * algorithmic styling 2168 */ 2169 family = FontFamily.getFamily(font.getFamilyName(null)); 2170 if (family != null) { 2171 Font2D familyFont = family.getFont(style|font.style); 2172 /* We exactly matched the requested style, use it! */ 2173 if (familyFont != null) { 2174 fontNameCache.put(mapName, familyFont); 2175 return familyFont; 2176 } else { 2177 /* This next call is designed to support the case 2178 * where bold italic is requested, and if we must 2179 * style, then base it on either bold or italic - 2180 * not on plain! 2181 */ 2182 familyFont = family.getClosestStyle(style|font.style); 2183 if (familyFont != null) { 2184 /* The next check is perhaps one 2185 * that shouldn't be done. ie if we get this 2186 * far we have probably as close a match as we 2187 * are going to get. We could load all fonts to 2188 * see if somehow some parts of the family are 2189 * loaded but not all of it. 2190 */ 2191 if (familyFont.canDoStyle(style|font.style)) { 2192 fontNameCache.put(mapName, familyFont); 2193 return familyFont; 2194 } 2195 } 2196 } 2197 } 2198 } 2199 } 2200 2201 if (FontUtilities.isWindows) { 2202 2203 font = findFontFromPlatformMap(lowerCaseName, style); 2204 if (FontUtilities.isLogging()) { 2205 FontUtilities.getLogger() 2206 .info("findFontFromPlatformMap returned " + font); 2207 } 2208 if (font != null) { 2209 fontNameCache.put(mapName, font); 2210 return font; 2211 } 2212 2213 /* Don't want Windows to return a Lucida Sans font from 2214 * C:\Windows\Fonts 2215 */ 2216 if (deferredFontFiles.size() > 0) { 2217 font = findJREDeferredFont(lowerCaseName, style); 2218 if (font != null) { 2219 fontNameCache.put(mapName, font); 2220 return font; 2221 } 2222 } 2223 font = findFontFromPlatform(lowerCaseName, style); 2224 if (font != null) { 2225 if (FontUtilities.isLogging()) { 2226 FontUtilities.getLogger() 2227 .info("Found font via platform API for request:\"" + 2228 name + "\":, style="+style+ 2229 " found font: " + font); 2230 } 2231 fontNameCache.put(mapName, font); 2232 return font; 2233 } 2234 } 2235 2236 /* If reach here and no match has been located, then if there are 2237 * uninitialised deferred fonts, load as many of those as needed 2238 * to find the deferred font. If none is found through that 2239 * search continue on. 2240 * There is possibly a minor issue when more than one 2241 * deferred font implements the same font face. Since deferred 2242 * fonts are only those in font configuration files, this is a 2243 * controlled situation, the known case being Solaris euro_fonts 2244 * versions of Arial, Times New Roman, Courier New. However 2245 * the larger font will transparently replace the smaller one 2246 * - see addToFontList() - when it is needed by the composite font. 2247 */ 2248 if (deferredFontFiles.size() > 0) { 2249 font = findDeferredFont(name, style); 2250 if (font != null) { 2251 fontNameCache.put(mapName, font); 2252 return font; 2253 } 2254 } 2255 2256 /* Some apps use deprecated 1.0 names such as helvetica and courier. On 2257 * Solaris these are Type1 fonts in /usr/openwin/lib/X11/fonts/Type1. 2258 * If running on Solaris will register all the fonts in this 2259 * directory. 2260 * May as well register the whole directory without actually testing 2261 * the font name is one of the deprecated names as the next step would 2262 * load all fonts which are in this directory anyway. 2263 * In the event that this lookup is successful it potentially "hides" 2264 * TrueType versions of such fonts that are elsewhere but since they 2265 * do not exist on Solaris this is not a problem. 2266 * Set a flag to indicate we've done this registration to avoid 2267 * repetition and more seriously, to avoid recursion. 2268 */ 2269 if (FontUtilities.isSolaris &&!loaded1dot0Fonts) { 2270 /* "timesroman" is a special case since that's not the 2271 * name of any known font on Solaris or elsewhere. 2272 */ 2273 if (lowerCaseName.equals("timesroman")) { 2274 font = findFont2D("serif", style, fallback); 2275 fontNameCache.put(mapName, font); 2276 } 2277 register1dot0Fonts(); 2278 loaded1dot0Fonts = true; 2279 Font2D ff = findFont2D(name, style, fallback); 2280 return ff; 2281 } 2282 2283 /* We check for application registered fonts before 2284 * explicitly loading all fonts as if necessary the registration 2285 * code will have done so anyway. And we don't want to needlessly 2286 * load the actual files for all fonts. 2287 * Just as for installed fonts we check for family before fullname. 2288 * We do not add these fonts to fontNameCache for the 2289 * app context case which eliminates the overhead of a per context 2290 * cache for these. 2291 */ 2292 2293 if (fontsAreRegistered || fontsAreRegisteredPerAppContext) { 2294 Hashtable<String, FontFamily> familyTable = null; 2295 Hashtable<String, Font2D> nameTable; 2296 2297 if (fontsAreRegistered) { 2298 familyTable = createdByFamilyName; 2299 nameTable = createdByFullName; 2300 } else { 2301 AppContext appContext = AppContext.getAppContext(); 2302 familyTable = 2303 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); 2304 nameTable = 2305 (Hashtable<String,Font2D>)appContext.get(regFullNameKey); 2306 } 2307 2308 family = familyTable.get(lowerCaseName); 2309 if (family != null) { 2310 font = family.getFontWithExactStyleMatch(style); 2311 if (font == null) { 2312 font = family.getFont(style); 2313 } 2314 if (font == null) { 2315 font = family.getClosestStyle(style); 2316 } 2317 if (font != null) { 2318 if (fontsAreRegistered) { 2319 fontNameCache.put(mapName, font); 2320 } 2321 return font; 2322 } 2323 } 2324 font = nameTable.get(lowerCaseName); 2325 if (font != null) { 2326 if (fontsAreRegistered) { 2327 fontNameCache.put(mapName, font); 2328 } 2329 return font; 2330 } 2331 } 2332 2333 /* If reach here and no match has been located, then if all fonts 2334 * are not yet loaded, do so, and then recurse. 2335 */ 2336 if (!loadedAllFonts) { 2337 if (FontUtilities.isLogging()) { 2338 FontUtilities.getLogger() 2339 .info("Load fonts looking for:" + name); 2340 } 2341 loadFonts(); 2342 loadedAllFonts = true; 2343 return findFont2D(name, style, fallback); 2344 } 2345 2346 if (!loadedAllFontFiles) { 2347 if (FontUtilities.isLogging()) { 2348 FontUtilities.getLogger() 2349 .info("Load font files looking for:" + name); 2350 } 2351 loadFontFiles(); 2352 loadedAllFontFiles = true; 2353 return findFont2D(name, style, fallback); 2354 } 2355 2356 /* The primary name is the locale default - ie not US/English but 2357 * whatever is the default in this locale. This is the way it always 2358 * has been but may be surprising to some developers if "Arial Regular" 2359 * were hard-coded in their app and yet "Arial Regular" was not the 2360 * default name. Fortunately for them, as a consequence of the JDK 2361 * supporting returning names and family names for arbitrary locales, 2362 * we also need to support searching all localised names for a match. 2363 * But because this case of the name used to reference a font is not 2364 * the same as the default for this locale is rare, it makes sense to 2365 * search a much shorter list of default locale names and only go to 2366 * a longer list of names in the event that no match was found. 2367 * So add here code which searches localised names too. 2368 * As in 1.4.x this happens only after loading all fonts, which 2369 * is probably the right order. 2370 */ 2371 if ((font = findFont2DAllLocales(name, style)) != null) { 2372 fontNameCache.put(mapName, font); 2373 return font; 2374 } 2375 2376 /* Perhaps its a "compatibility" name - timesroman, helvetica, 2377 * or courier, which 1.0 apps used for logical fonts. 2378 * We look for these "late" after a loadFonts as we must not 2379 * hide real fonts of these names. 2380 * Map these appropriately: 2381 * On windows this means according to the rules specified by the 2382 * FontConfiguration : do it only for encoding==Cp1252 2383 * 2384 * REMIND: this is something we plan to remove. 2385 */ 2386 if (FontUtilities.isWindows) { 2387 String compatName = 2388 getFontConfiguration().getFallbackFamilyName(name, null); 2389 if (compatName != null) { 2390 font = findFont2D(compatName, style, fallback); 2391 fontNameCache.put(mapName, font); 2392 return font; 2393 } 2394 } else if (lowerCaseName.equals("timesroman")) { 2395 font = findFont2D("serif", style, fallback); 2396 fontNameCache.put(mapName, font); 2397 return font; 2398 } else if (lowerCaseName.equals("helvetica")) { 2399 font = findFont2D("sansserif", style, fallback); 2400 fontNameCache.put(mapName, font); 2401 return font; 2402 } else if (lowerCaseName.equals("courier")) { 2403 font = findFont2D("monospaced", style, fallback); 2404 fontNameCache.put(mapName, font); 2405 return font; 2406 } 2407 2408 if (FontUtilities.isLogging()) { 2409 FontUtilities.getLogger().info("No font found for:" + name); 2410 } 2411 2412 switch (fallback) { 2413 case PHYSICAL_FALLBACK: return getDefaultPhysicalFont(); 2414 case LOGICAL_FALLBACK: return getDefaultLogicalFont(style); 2415 default: return null; 2416 } 2417 } 2418 2419 /* 2420 * Workaround for apps which are dependent on a font metrics bug 2421 * in JDK 1.1. This is an unsupported win32 private setting. 2422 * Left in for a customer - do not remove. 2423 */ 2424 public boolean usePlatformFontMetrics() { 2425 return usePlatformFontMetrics; 2426 } 2427 2428 public int getNumFonts() { 2429 return physicalFonts.size()+maxCompFont; 2430 } 2431 2432 private static boolean fontSupportsEncoding(Font font, String encoding) { 2433 return FontUtilities.getFont2D(font).supportsEncoding(encoding); 2434 } 2435 2436 protected abstract String getFontPath(boolean noType1Fonts); 2437 2438 private Thread fileCloser = null; 2439 Vector<File> tmpFontFiles = null; 2440 2441 public Font2D createFont2D(File fontFile, int fontFormat, 2442 boolean isCopy, CreatedFontTracker tracker) 2443 throws FontFormatException { 2444 2445 String fontFilePath = fontFile.getPath(); 2446 FileFont font2D = null; 2447 final File fFile = fontFile; 2448 final CreatedFontTracker _tracker = tracker; 2449 try { 2450 switch (fontFormat) { 2451 case Font.TRUETYPE_FONT: 2452 font2D = new TrueTypeFont(fontFilePath, null, 0, true); 2453 break; 2454 case Font.TYPE1_FONT: 2455 font2D = new Type1Font(fontFilePath, null, isCopy); 2456 break; 2457 default: 2458 throw new FontFormatException("Unrecognised Font Format"); 2459 } 2460 } catch (FontFormatException e) { 2461 if (isCopy) { 2462 java.security.AccessController.doPrivileged( 2463 new java.security.PrivilegedAction() { 2464 public Object run() { 2465 if (_tracker != null) { 2466 _tracker.subBytes((int)fFile.length()); 2467 } 2468 fFile.delete(); 2469 return null; 2470 } 2471 }); 2472 } 2473 throw(e); 2474 } 2475 if (isCopy) { 2476 font2D.setFileToRemove(fontFile, tracker); 2477 synchronized (FontManager.class) { 2478 2479 if (tmpFontFiles == null) { 2480 tmpFontFiles = new Vector<File>(); 2481 } 2482 tmpFontFiles.add(fontFile); 2483 2484 if (fileCloser == null) { 2485 final Runnable fileCloserRunnable = new Runnable() { 2486 public void run() { 2487 java.security.AccessController.doPrivileged( 2488 new java.security.PrivilegedAction() { 2489 public Object run() { 2490 2491 for (int i=0;i<CHANNELPOOLSIZE;i++) { 2492 if (fontFileCache[i] != null) { 2493 try { 2494 fontFileCache[i].close(); 2495 } catch (Exception e) { 2496 } 2497 } 2498 } 2499 if (tmpFontFiles != null) { 2500 File[] files = new File[tmpFontFiles.size()]; 2501 files = tmpFontFiles.toArray(files); 2502 for (int f=0; f<files.length;f++) { 2503 try { 2504 files[f].delete(); 2505 } catch (Exception e) { 2506 } 2507 } 2508 } 2509 2510 return null; 2511 } 2512 2513 }); 2514 } 2515 }; 2516 java.security.AccessController.doPrivileged( 2517 new java.security.PrivilegedAction() { 2518 public Object run() { 2519 /* The thread must be a member of a thread group 2520 * which will not get GCed before VM exit. 2521 * Make its parent the top-level thread group. 2522 */ 2523 ThreadGroup tg = 2524 Thread.currentThread().getThreadGroup(); 2525 for (ThreadGroup tgn = tg; 2526 tgn != null; 2527 tg = tgn, tgn = tg.getParent()); 2528 fileCloser = new Thread(tg, fileCloserRunnable); 2529 fileCloser.setContextClassLoader(null); 2530 Runtime.getRuntime().addShutdownHook(fileCloser); 2531 return null; 2532 } 2533 }); 2534 } 2535 } 2536 } 2537 return font2D; 2538 } 2539 2540 /* remind: used in X11GraphicsEnvironment and called often enough 2541 * that we ought to obsolete this code 2542 */ 2543 public synchronized String getFullNameByFileName(String fileName) { 2544 PhysicalFont[] physFonts = getPhysicalFonts(); 2545 for (int i=0;i<physFonts.length;i++) { 2546 if (physFonts[i].platName.equals(fileName)) { 2547 return (physFonts[i].getFontName(null)); 2548 } 2549 } 2550 return null; 2551 } 2552 2553 /* 2554 * This is called when font is determined to be invalid/bad. 2555 * It designed to be called (for example) by the font scaler 2556 * when in processing a font file it is discovered to be incorrect. 2557 * This is different than the case where fonts are discovered to 2558 * be incorrect during initial verification, as such fonts are 2559 * never registered. 2560 * Handles to this font held are re-directed to a default font. 2561 * This default may not be an ideal substitute buts it better than 2562 * crashing This code assumes a PhysicalFont parameter as it doesn't 2563 * make sense for a Composite to be "bad". 2564 */ 2565 public synchronized void deRegisterBadFont(Font2D font2D) { 2566 if (!(font2D instanceof PhysicalFont)) { 2567 /* We should never reach here, but just in case */ 2568 return; 2569 } else { 2570 if (FontUtilities.isLogging()) { 2571 FontUtilities.getLogger() 2572 .severe("Deregister bad font: " + font2D); 2573 } 2574 replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont()); 2575 } 2576 } 2577 2578 /* 2579 * This encapsulates all the work that needs to be done when a 2580 * Font2D is replaced by a different Font2D. 2581 */ 2582 public synchronized void replaceFont(PhysicalFont oldFont, 2583 PhysicalFont newFont) { 2584 2585 if (oldFont.handle.font2D != oldFont) { 2586 /* already done */ 2587 return; 2588 } 2589 2590 /* If we try to replace the font with itself, that won't work, 2591 * so pick any alternative physical font 2592 */ 2593 if (oldFont == newFont) { 2594 if (FontUtilities.isLogging()) { 2595 FontUtilities.getLogger() 2596 .severe("Can't replace bad font with itself " + oldFont); 2597 } 2598 PhysicalFont[] physFonts = getPhysicalFonts(); 2599 for (int i=0; i<physFonts.length;i++) { 2600 if (physFonts[i] != newFont) { 2601 newFont = physFonts[i]; 2602 break; 2603 } 2604 } 2605 if (oldFont == newFont) { 2606 if (FontUtilities.isLogging()) { 2607 FontUtilities.getLogger() 2608 .severe("This is bad. No good physicalFonts found."); 2609 } 2610 return; 2611 } 2612 } 2613 2614 /* eliminate references to this font, so it won't be located 2615 * by future callers, and will be eligible for GC when all 2616 * references are removed 2617 */ 2618 oldFont.handle.font2D = newFont; 2619 physicalFonts.remove(oldFont.fullName); 2620 fullNameToFont.remove(oldFont.fullName.toLowerCase(Locale.ENGLISH)); 2621 FontFamily.remove(oldFont); 2622 2623 if (localeFullNamesToFont != null) { 2624 Map.Entry[] mapEntries = 2625 (Map.Entry[])localeFullNamesToFont.entrySet(). 2626 toArray(new Map.Entry[0]); 2627 /* Should I be replacing these, or just I just remove 2628 * the names from the map? 2629 */ 2630 for (int i=0; i<mapEntries.length;i++) { 2631 if (mapEntries[i].getValue() == oldFont) { 2632 try { 2633 mapEntries[i].setValue(newFont); 2634 } catch (Exception e) { 2635 /* some maps don't support this operation. 2636 * In this case just give up and remove the entry. 2637 */ 2638 localeFullNamesToFont.remove(mapEntries[i].getKey()); 2639 } 2640 } 2641 } 2642 } 2643 2644 for (int i=0; i<maxCompFont; i++) { 2645 /* Deferred initialization of composites shouldn't be 2646 * a problem for this case, since a font must have been 2647 * initialised to be discovered to be bad. 2648 * Some JRE composites on Solaris use two versions of the same 2649 * font. The replaced font isn't bad, just "smaller" so there's 2650 * no need to make the slot point to the new font. 2651 * Since composites have a direct reference to the Font2D (not 2652 * via a handle) making this substitution is not safe and could 2653 * cause an additional problem and so this substitution is 2654 * warranted only when a font is truly "bad" and could cause 2655 * a crash. So we now replace it only if its being substituted 2656 * with some font other than a fontconfig rank font 2657 * Since in practice a substitution will have the same rank 2658 * this may never happen, but the code is safer even if its 2659 * also now a no-op. 2660 * The only obvious "glitch" from this stems from the current 2661 * implementation that when asked for the number of glyphs in a 2662 * composite it lies and returns the number in slot 0 because 2663 * composite glyphs aren't contiguous. Since we live with that 2664 * we can live with the glitch that depending on how it was 2665 * initialised a composite may return different values for this. 2666 * Fixing the issues with composite glyph ids is tricky as 2667 * there are exclusion ranges and unlike other fonts even the 2668 * true "numGlyphs" isn't a contiguous range. Likely the only 2669 * solution is an API that returns an array of glyph ranges 2670 * which takes precedence over the existing API. That might 2671 * also need to address excluding ranges which represent a 2672 * code point supported by an earlier component. 2673 */ 2674 if (newFont.getRank() > Font2D.FONT_CONFIG_RANK) { 2675 compFonts[i].replaceComponentFont(oldFont, newFont); 2676 } 2677 } 2678 } 2679 2680 private synchronized void loadLocaleNames() { 2681 if (localeFullNamesToFont != null) { 2682 return; 2683 } 2684 localeFullNamesToFont = new HashMap<String, TrueTypeFont>(); 2685 Font2D[] fonts = getRegisteredFonts(); 2686 for (int i=0; i<fonts.length; i++) { 2687 if (fonts[i] instanceof TrueTypeFont) { 2688 TrueTypeFont ttf = (TrueTypeFont)fonts[i]; 2689 String[] fullNames = ttf.getAllFullNames(); 2690 for (int n=0; n<fullNames.length; n++) { 2691 localeFullNamesToFont.put(fullNames[n], ttf); 2692 } 2693 FontFamily family = FontFamily.getFamily(ttf.familyName); 2694 if (family != null) { 2695 FontFamily.addLocaleNames(family, ttf.getAllFamilyNames()); 2696 } 2697 } 2698 } 2699 } 2700 2701 /* This replicate the core logic of findFont2D but operates on 2702 * all the locale names. This hasn't been merged into findFont2D to 2703 * keep the logic simpler and reduce overhead, since this case is 2704 * almost never used. The main case in which it is called is when 2705 * a bogus font name is used and we need to check all possible names 2706 * before returning the default case. 2707 */ 2708 private Font2D findFont2DAllLocales(String name, int style) { 2709 2710 if (FontUtilities.isLogging()) { 2711 FontUtilities.getLogger() 2712 .info("Searching localised font names for:" + name); 2713 } 2714 2715 /* If reach here and no match has been located, then if we have 2716 * not yet built the map of localeFullNamesToFont for TT fonts, do so 2717 * now. This method must be called after all fonts have been loaded. 2718 */ 2719 if (localeFullNamesToFont == null) { 2720 loadLocaleNames(); 2721 } 2722 String lowerCaseName = name.toLowerCase(); 2723 Font2D font = null; 2724 2725 /* First see if its a family name. */ 2726 FontFamily family = FontFamily.getLocaleFamily(lowerCaseName); 2727 if (family != null) { 2728 font = family.getFont(style); 2729 if (font == null) { 2730 font = family.getClosestStyle(style); 2731 } 2732 if (font != null) { 2733 return font; 2734 } 2735 } 2736 2737 /* If it wasn't a family name, it should be a full name. */ 2738 synchronized (this) { 2739 font = localeFullNamesToFont.get(name); 2740 } 2741 if (font != null) { 2742 if (font.style == style || style == Font.PLAIN) { 2743 return font; 2744 } else { 2745 family = FontFamily.getFamily(font.getFamilyName(null)); 2746 if (family != null) { 2747 Font2D familyFont = family.getFont(style); 2748 /* We exactly matched the requested style, use it! */ 2749 if (familyFont != null) { 2750 return familyFont; 2751 } else { 2752 familyFont = family.getClosestStyle(style); 2753 if (familyFont != null) { 2754 /* The next check is perhaps one 2755 * that shouldn't be done. ie if we get this 2756 * far we have probably as close a match as we 2757 * are going to get. We could load all fonts to 2758 * see if somehow some parts of the family are 2759 * loaded but not all of it. 2760 * This check is commented out for now. 2761 */ 2762 if (!familyFont.canDoStyle(style)) { 2763 familyFont = null; 2764 } 2765 return familyFont; 2766 } 2767 } 2768 } 2769 } 2770 } 2771 return font; 2772 } 2773 2774 /* Supporting "alternate" composite fonts on 2D graphics objects 2775 * is accessed by the application by calling methods on the local 2776 * GraphicsEnvironment. The overall implementation is described 2777 * in one place, here, since otherwise the implementation is spread 2778 * around it may be difficult to track. 2779 * The methods below call into SunGraphicsEnvironment which creates a 2780 * new FontConfiguration instance. The FontConfiguration class, 2781 * and its platform sub-classes are updated to take parameters requesting 2782 * these behaviours. This is then used to create new composite font 2783 * instances. Since this calls the initCompositeFont method in 2784 * SunGraphicsEnvironment it performs the same initialization as is 2785 * performed normally. There may be some duplication of effort, but 2786 * that code is already written to be able to perform properly if called 2787 * to duplicate work. The main difference is that if we detect we are 2788 * running in an applet/browser/Java plugin environment these new fonts 2789 * are not placed in the "default" maps but into an AppContext instance. 2790 * The font lookup mechanism in java.awt.Font.getFont2D() is also updated 2791 * so that look-up for composite fonts will in that case always 2792 * do a lookup rather than returning a cached result. 2793 * This is inefficient but necessary else singleton java.awt.Font 2794 * instances would not retrieve the correct Font2D for the appcontext. 2795 * sun.font.FontManager.findFont2D is also updated to that it uses 2796 * a name map cache specific to that appcontext. 2797 * 2798 * Getting an AppContext is expensive, so there is a global variable 2799 * that records whether these methods have ever been called and can 2800 * avoid the expense for almost all applications. Once the correct 2801 * CompositeFont is associated with the Font, everything should work 2802 * through existing mechanisms. 2803 * A special case is that GraphicsEnvironment.getAllFonts() must 2804 * return an AppContext specific list. 2805 * 2806 * Calling the methods below is "heavyweight" but it is expected that 2807 * these methods will be called very rarely. 2808 * 2809 * If _usingPerAppContextComposites is true, we are in "applet" 2810 * (eg browser) enviroment and at least one context has selected 2811 * an alternate composite font behaviour. 2812 * If _usingAlternateComposites is true, we are not in an "applet" 2813 * environment and the (single) application has selected 2814 * an alternate composite font behaviour. 2815 * 2816 * - Printing: The implementation delegates logical fonts to an AWT 2817 * mechanism which cannot use these alternate configurations. 2818 * We can detect that alternate fonts are in use and back-off to 2D, but 2819 * that uses outlines. Much of this can be fixed with additional work 2820 * but that may have to wait. The results should be correct, just not 2821 * optimal. 2822 */ 2823 private static final Object altJAFontKey = new Object(); 2824 private static final Object localeFontKey = new Object(); 2825 private static final Object proportionalFontKey = new Object(); 2826 private boolean _usingPerAppContextComposites = false; 2827 private boolean _usingAlternateComposites = false; 2828 2829 /* These values are used only if we are running as a standalone 2830 * application, as determined by maybeMultiAppContext(); 2831 */ 2832 private static boolean gAltJAFont = false; 2833 private boolean gLocalePref = false; 2834 private boolean gPropPref = false; 2835 2836 /* This method doesn't check if alternates are selected in this app 2837 * context. Its used by the FontMetrics caching code which in such 2838 * a case cannot retrieve a cached metrics solely on the basis of 2839 * the Font.equals() method since it needs to also check if the Font2D 2840 * is the same. 2841 * We also use non-standard composites for Swing native L&F fonts on 2842 * Windows. In that case the policy is that the metrics reported are 2843 * based solely on the physical font in the first slot which is the 2844 * visible java.awt.Font. So in that case the metrics cache which tests 2845 * the Font does what we want. In the near future when we expand the GTK 2846 * logical font definitions we may need to revisit this if GTK reports 2847 * combined metrics instead. For now though this test can be simple. 2848 */ 2849 public boolean maybeUsingAlternateCompositeFonts() { 2850 return _usingAlternateComposites || _usingPerAppContextComposites; 2851 } 2852 2853 public boolean usingAlternateCompositeFonts() { 2854 return (_usingAlternateComposites || 2855 (_usingPerAppContextComposites && 2856 AppContext.getAppContext().get(CompositeFont.class) != null)); 2857 } 2858 2859 private static boolean maybeMultiAppContext() { 2860 Boolean appletSM = (Boolean) 2861 java.security.AccessController.doPrivileged( 2862 new java.security.PrivilegedAction() { 2863 public Object run() { 2864 SecurityManager sm = System.getSecurityManager(); 2865 return new Boolean 2866 (sm instanceof sun.applet.AppletSecurity); 2867 } 2868 }); 2869 return appletSM.booleanValue(); 2870 } 2871 2872 /* Modifies the behaviour of a subsequent call to preferLocaleFonts() 2873 * to use Mincho instead of Gothic for dialoginput in JA locales 2874 * on windows. Not needed on other platforms. 2875 */ 2876 public synchronized void useAlternateFontforJALocales() { 2877 2878 if (!FontUtilities.isWindows) { 2879 return; 2880 } 2881 2882 if (!maybeMultiAppContext()) { 2883 gAltJAFont = true; 2884 } else { 2885 AppContext appContext = AppContext.getAppContext(); 2886 appContext.put(altJAFontKey, altJAFontKey); 2887 } 2888 } 2889 2890 public boolean usingAlternateFontforJALocales() { 2891 if (!maybeMultiAppContext()) { 2892 return gAltJAFont; 2893 } else { 2894 AppContext appContext = AppContext.getAppContext(); 2895 return appContext.get(altJAFontKey) == altJAFontKey; 2896 } 2897 } 2898 2899 public synchronized void preferLocaleFonts() { 2900 2901 /* Test if re-ordering will have any effect */ 2902 if (!FontConfiguration.willReorderForStartupLocale()) { 2903 return; 2904 } 2905 2906 if (!maybeMultiAppContext()) { 2907 if (gLocalePref == true) { 2908 return; 2909 } 2910 gLocalePref = true; 2911 createCompositeFonts(fontNameCache, gLocalePref, gPropPref); 2912 _usingAlternateComposites = true; 2913 } else { 2914 AppContext appContext = AppContext.getAppContext(); 2915 if (appContext.get(localeFontKey) == localeFontKey) { 2916 return; 2917 } 2918 appContext.put(localeFontKey, localeFontKey); 2919 boolean acPropPref = 2920 appContext.get(proportionalFontKey) == proportionalFontKey; 2921 ConcurrentHashMap<String, Font2D> 2922 altNameCache = new ConcurrentHashMap<String, Font2D> (); 2923 /* If there is an existing hashtable, we can drop it. */ 2924 appContext.put(CompositeFont.class, altNameCache); 2925 _usingPerAppContextComposites = true; 2926 createCompositeFonts(altNameCache, true, acPropPref); 2927 } 2928 } 2929 2930 public synchronized void preferProportionalFonts() { 2931 2932 /* If no proportional fonts are configured, there's no need 2933 * to take any action. 2934 */ 2935 if (!FontConfiguration.hasMonoToPropMap()) { 2936 return; 2937 } 2938 2939 if (!maybeMultiAppContext()) { 2940 if (gPropPref == true) { 2941 return; 2942 } 2943 gPropPref = true; 2944 createCompositeFonts(fontNameCache, gLocalePref, gPropPref); 2945 _usingAlternateComposites = true; 2946 } else { 2947 AppContext appContext = AppContext.getAppContext(); 2948 if (appContext.get(proportionalFontKey) == proportionalFontKey) { 2949 return; 2950 } 2951 appContext.put(proportionalFontKey, proportionalFontKey); 2952 boolean acLocalePref = 2953 appContext.get(localeFontKey) == localeFontKey; 2954 ConcurrentHashMap<String, Font2D> 2955 altNameCache = new ConcurrentHashMap<String, Font2D> (); 2956 /* If there is an existing hashtable, we can drop it. */ 2957 appContext.put(CompositeFont.class, altNameCache); 2958 _usingPerAppContextComposites = true; 2959 createCompositeFonts(altNameCache, acLocalePref, true); 2960 } 2961 } 2962 2963 private static HashSet<String> installedNames = null; 2964 private static HashSet<String> getInstalledNames() { 2965 if (installedNames == null) { 2966 Locale l = getSystemStartupLocale(); 2967 SunFontManager fontManager = SunFontManager.getInstance(); 2968 String[] installedFamilies = 2969 fontManager.getInstalledFontFamilyNames(l); 2970 Font[] installedFonts = fontManager.getAllInstalledFonts(); 2971 HashSet<String> names = new HashSet<String>(); 2972 for (int i=0; i<installedFamilies.length; i++) { 2973 names.add(installedFamilies[i].toLowerCase(l)); 2974 } 2975 for (int i=0; i<installedFonts.length; i++) { 2976 names.add(installedFonts[i].getFontName(l).toLowerCase(l)); 2977 } 2978 installedNames = names; 2979 } 2980 return installedNames; 2981 } 2982 2983 /* Keys are used to lookup per-AppContext Hashtables */ 2984 private static final Object regFamilyKey = new Object(); 2985 private static final Object regFullNameKey = new Object(); 2986 private Hashtable<String,FontFamily> createdByFamilyName; 2987 private Hashtable<String,Font2D> createdByFullName; 2988 private boolean fontsAreRegistered = false; 2989 private boolean fontsAreRegisteredPerAppContext = false; 2990 2991 public boolean registerFont(Font font) { 2992 /* This method should not be called with "null". 2993 * It is the caller's responsibility to ensure that. 2994 */ 2995 if (font == null) { 2996 return false; 2997 } 2998 2999 /* Initialise these objects only once we start to use this API */ 3000 synchronized (regFamilyKey) { 3001 if (createdByFamilyName == null) { 3002 createdByFamilyName = new Hashtable<String,FontFamily>(); 3003 createdByFullName = new Hashtable<String,Font2D>(); 3004 } 3005 } 3006 3007 if (! FontAccess.getFontAccess().isCreatedFont(font)) { 3008 return false; 3009 } 3010 /* We want to ensure that this font cannot override existing 3011 * installed fonts. Check these conditions : 3012 * - family name is not that of an installed font 3013 * - full name is not that of an installed font 3014 * - family name is not the same as the full name of an installed font 3015 * - full name is not the same as the family name of an installed font 3016 * The last two of these may initially look odd but the reason is 3017 * that (unfortunately) Font constructors do not distinuguish these. 3018 * An extreme example of such a problem would be a font which has 3019 * family name "Dialog.Plain" and full name of "Dialog". 3020 * The one arguably overly stringent restriction here is that if an 3021 * application wants to supply a new member of an existing family 3022 * It will get rejected. But since the JRE can perform synthetic 3023 * styling in many cases its not necessary. 3024 * We don't apply the same logic to registered fonts. If apps want 3025 * to do this lets assume they have a reason. It won't cause problems 3026 * except for themselves. 3027 */ 3028 HashSet<String> names = getInstalledNames(); 3029 Locale l = getSystemStartupLocale(); 3030 String familyName = font.getFamily(l).toLowerCase(); 3031 String fullName = font.getFontName(l).toLowerCase(); 3032 if (names.contains(familyName) || names.contains(fullName)) { 3033 return false; 3034 } 3035 3036 /* Checks passed, now register the font */ 3037 Hashtable<String,FontFamily> familyTable; 3038 Hashtable<String,Font2D> fullNameTable; 3039 if (!maybeMultiAppContext()) { 3040 familyTable = createdByFamilyName; 3041 fullNameTable = createdByFullName; 3042 fontsAreRegistered = true; 3043 } else { 3044 AppContext appContext = AppContext.getAppContext(); 3045 familyTable = 3046 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); 3047 fullNameTable = 3048 (Hashtable<String,Font2D>)appContext.get(regFullNameKey); 3049 if (familyTable == null) { 3050 familyTable = new Hashtable<String,FontFamily>(); 3051 fullNameTable = new Hashtable<String,Font2D>(); 3052 appContext.put(regFamilyKey, familyTable); 3053 appContext.put(regFullNameKey, fullNameTable); 3054 } 3055 fontsAreRegisteredPerAppContext = true; 3056 } 3057 /* Create the FontFamily and add font to the tables */ 3058 Font2D font2D = FontUtilities.getFont2D(font); 3059 int style = font2D.getStyle(); 3060 FontFamily family = familyTable.get(familyName); 3061 if (family == null) { 3062 family = new FontFamily(font.getFamily(l)); 3063 familyTable.put(familyName, family); 3064 } 3065 /* Remove name cache entries if not using app contexts. 3066 * To accommodate a case where code may have registered first a plain 3067 * family member and then used it and is now registering a bold family 3068 * member, we need to remove all members of the family, so that the 3069 * new style can get picked up rather than continuing to synthesise. 3070 */ 3071 if (fontsAreRegistered) { 3072 removeFromCache(family.getFont(Font.PLAIN)); 3073 removeFromCache(family.getFont(Font.BOLD)); 3074 removeFromCache(family.getFont(Font.ITALIC)); 3075 removeFromCache(family.getFont(Font.BOLD|Font.ITALIC)); 3076 removeFromCache(fullNameTable.get(fullName)); 3077 } 3078 family.setFont(font2D, style); 3079 fullNameTable.put(fullName, font2D); 3080 return true; 3081 } 3082 3083 /* Remove from the name cache all references to the Font2D */ 3084 private void removeFromCache(Font2D font) { 3085 if (font == null) { 3086 return; 3087 } 3088 String[] keys = (String[])(fontNameCache.keySet().toArray(STR_ARRAY)); 3089 for (int k=0; k<keys.length;k++) { 3090 if (fontNameCache.get(keys[k]) == font) { 3091 fontNameCache.remove(keys[k]); 3092 } 3093 } 3094 } 3095 3096 // It may look odd to use TreeMap but its more convenient to the caller. 3097 public TreeMap<String, String> getCreatedFontFamilyNames() { 3098 3099 Hashtable<String,FontFamily> familyTable; 3100 if (fontsAreRegistered) { 3101 familyTable = createdByFamilyName; 3102 } else if (fontsAreRegisteredPerAppContext) { 3103 AppContext appContext = AppContext.getAppContext(); 3104 familyTable = 3105 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey); 3106 } else { 3107 return null; 3108 } 3109 3110 Locale l = getSystemStartupLocale(); 3111 synchronized (familyTable) { 3112 TreeMap<String, String> map = new TreeMap<String, String>(); 3113 for (FontFamily f : familyTable.values()) { 3114 Font2D font2D = f.getFont(Font.PLAIN); 3115 if (font2D == null) { 3116 font2D = f.getClosestStyle(Font.PLAIN); 3117 } 3118 String name = font2D.getFamilyName(l); 3119 map.put(name.toLowerCase(l), name); 3120 } 3121 return map; 3122 } 3123 } 3124 3125 public Font[] getCreatedFonts() { 3126 3127 Hashtable<String,Font2D> nameTable; 3128 if (fontsAreRegistered) { 3129 nameTable = createdByFullName; 3130 } else if (fontsAreRegisteredPerAppContext) { 3131 AppContext appContext = AppContext.getAppContext(); 3132 nameTable = 3133 (Hashtable<String,Font2D>)appContext.get(regFullNameKey); 3134 } else { 3135 return null; 3136 } 3137 3138 Locale l = getSystemStartupLocale(); 3139 synchronized (nameTable) { 3140 Font[] fonts = new Font[nameTable.size()]; 3141 int i=0; 3142 for (Font2D font2D : nameTable.values()) { 3143 fonts[i++] = new Font(font2D.getFontName(l), Font.PLAIN, 1); 3144 } 3145 return fonts; 3146 } 3147 } 3148 3149 3150 protected String[] getPlatformFontDirs(boolean noType1Fonts) { 3151 3152 /* First check if we already initialised path dirs */ 3153 if (pathDirs != null) { 3154 return pathDirs; 3155 } 3156 3157 String path = getPlatformFontPath(noType1Fonts); 3158 StringTokenizer parser = 3159 new StringTokenizer(path, File.pathSeparator); 3160 ArrayList<String> pathList = new ArrayList<String>(); 3161 try { 3162 while (parser.hasMoreTokens()) { 3163 pathList.add(parser.nextToken()); 3164 } 3165 } catch (NoSuchElementException e) { 3166 } 3167 pathDirs = pathList.toArray(new String[0]); 3168 return pathDirs; 3169 } 3170 3171 /** 3172 * Returns an array of two strings. The first element is the 3173 * name of the font. The second element is the file name. 3174 */ 3175 public abstract String[] getDefaultPlatformFont(); 3176 3177 // Begin: Refactored from SunGraphicsEnviroment. 3178 3179 /* 3180 * helper function for registerFonts 3181 */ 3182 private void addDirFonts(String dirName, File dirFile, 3183 FilenameFilter filter, 3184 int fontFormat, boolean useJavaRasterizer, 3185 int fontRank, 3186 boolean defer, boolean resolveSymLinks) { 3187 String[] ls = dirFile.list(filter); 3188 if (ls == null || ls.length == 0) { 3189 return; 3190 } 3191 String[] fontNames = new String[ls.length]; 3192 String[][] nativeNames = new String[ls.length][]; 3193 int fontCount = 0; 3194 3195 for (int i=0; i < ls.length; i++ ) { 3196 File theFile = new File(dirFile, ls[i]); 3197 String fullName = null; 3198 if (resolveSymLinks) { 3199 try { 3200 fullName = theFile.getCanonicalPath(); 3201 } catch (IOException e) { 3202 } 3203 } 3204 if (fullName == null) { 3205 fullName = dirName + File.separator + ls[i]; 3206 } 3207 3208 // REMIND: case compare depends on platform 3209 if (registeredFontFiles.contains(fullName)) { 3210 continue; 3211 } 3212 3213 if (badFonts != null && badFonts.contains(fullName)) { 3214 if (FontUtilities.debugFonts()) { 3215 FontUtilities.getLogger() 3216 .warning("skip bad font " + fullName); 3217 } 3218 continue; // skip this font file. 3219 } 3220 3221 registeredFontFiles.add(fullName); 3222 3223 if (FontUtilities.debugFonts() 3224 && FontUtilities.getLogger().isLoggable(PlatformLogger.INFO)) { 3225 String message = "Registering font " + fullName; 3226 String[] natNames = getNativeNames(fullName, null); 3227 if (natNames == null) { 3228 message += " with no native name"; 3229 } else { 3230 message += " with native name(s) " + natNames[0]; 3231 for (int nn = 1; nn < natNames.length; nn++) { 3232 message += ", " + natNames[nn]; 3233 } 3234 } 3235 FontUtilities.getLogger().info(message); 3236 } 3237 fontNames[fontCount] = fullName; 3238 nativeNames[fontCount++] = getNativeNames(fullName, null); 3239 } 3240 registerFonts(fontNames, nativeNames, fontCount, fontFormat, 3241 useJavaRasterizer, fontRank, defer); 3242 return; 3243 } 3244 3245 protected String[] getNativeNames(String fontFileName, 3246 String platformName) { 3247 return null; 3248 } 3249 3250 /** 3251 * Returns a file name for the physical font represented by this platform 3252 * font name. The default implementation tries to obtain the file name 3253 * from the font configuration. 3254 * Subclasses may override to provide information from other sources. 3255 */ 3256 protected String getFileNameFromPlatformName(String platformFontName) { 3257 return fontConfig.getFileNameFromPlatformName(platformFontName); 3258 } 3259 3260 /** 3261 * Return the default font configuration. 3262 */ 3263 public FontConfiguration getFontConfiguration() { 3264 return fontConfig; 3265 } 3266 3267 /* A call to this method should be followed by a call to 3268 * registerFontDirs(..) 3269 */ 3270 public String getPlatformFontPath(boolean noType1Font) { 3271 if (fontPath == null) { 3272 fontPath = getFontPath(noType1Font); 3273 } 3274 return fontPath; 3275 } 3276 3277 public static boolean isOpenJDK() { 3278 return FontUtilities.isOpenJDK; 3279 } 3280 3281 protected void loadFonts() { 3282 if (discoveredAllFonts) { 3283 return; 3284 } 3285 /* Use lock specific to the font system */ 3286 synchronized (this) { 3287 if (FontUtilities.debugFonts()) { 3288 Thread.dumpStack(); 3289 FontUtilities.getLogger() 3290 .info("SunGraphicsEnvironment.loadFonts() called"); 3291 } 3292 initialiseDeferredFonts(); 3293 3294 java.security.AccessController.doPrivileged( 3295 new java.security.PrivilegedAction() { 3296 public Object run() { 3297 if (fontPath == null) { 3298 fontPath = getPlatformFontPath(noType1Font); 3299 registerFontDirs(fontPath); 3300 } 3301 if (fontPath != null) { 3302 // this will find all fonts including those already 3303 // registered. But we have checks in place to prevent 3304 // double registration. 3305 if (! gotFontsFromPlatform()) { 3306 registerFontsOnPath(fontPath, false, 3307 Font2D.UNKNOWN_RANK, 3308 false, true); 3309 loadedAllFontFiles = true; 3310 } 3311 } 3312 registerOtherFontFiles(registeredFontFiles); 3313 discoveredAllFonts = true; 3314 return null; 3315 } 3316 }); 3317 } 3318 } 3319 3320 protected void registerFontDirs(String pathName) { 3321 return; 3322 } 3323 3324 private void registerFontsOnPath(String pathName, 3325 boolean useJavaRasterizer, int fontRank, 3326 boolean defer, boolean resolveSymLinks) { 3327 3328 StringTokenizer parser = new StringTokenizer(pathName, 3329 File.pathSeparator); 3330 try { 3331 while (parser.hasMoreTokens()) { 3332 registerFontsInDir(parser.nextToken(), 3333 useJavaRasterizer, fontRank, 3334 defer, resolveSymLinks); 3335 } 3336 } catch (NoSuchElementException e) { 3337 } 3338 } 3339 3340 /* Called to register fall back fonts */ 3341 public void registerFontsInDir(String dirName) { 3342 registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false); 3343 } 3344 3345 private void registerFontsInDir(String dirName, boolean useJavaRasterizer, 3346 int fontRank, 3347 boolean defer, boolean resolveSymLinks) { 3348 File pathFile = new File(dirName); 3349 addDirFonts(dirName, pathFile, ttFilter, 3350 FONTFORMAT_TRUETYPE, useJavaRasterizer, 3351 fontRank==Font2D.UNKNOWN_RANK ? 3352 Font2D.TTF_RANK : fontRank, 3353 defer, resolveSymLinks); 3354 addDirFonts(dirName, pathFile, t1Filter, 3355 FONTFORMAT_TYPE1, useJavaRasterizer, 3356 fontRank==Font2D.UNKNOWN_RANK ? 3357 Font2D.TYPE1_RANK : fontRank, 3358 defer, resolveSymLinks); 3359 } 3360 3361 protected void registerFontDir(String path) { 3362 } 3363 3364 /** 3365 * Returns file name for default font, either absolute 3366 * or relative as needed by registerFontFile. 3367 */ 3368 public synchronized String getDefaultFontFile() { 3369 if (defaultFontFileName == null) { 3370 initDefaultFonts(); 3371 } 3372 return defaultFontFileName; 3373 } 3374 3375 private void initDefaultFonts() { 3376 if (!isOpenJDK()) { 3377 defaultFontName = lucidaFontName; 3378 if (useAbsoluteFontFileNames()) { 3379 defaultFontFileName = 3380 jreFontDirName + File.separator + FontUtilities.LUCIDA_FILE_NAME; 3381 } else { 3382 defaultFontFileName = FontUtilities.LUCIDA_FILE_NAME; 3383 } 3384 } 3385 } 3386 3387 /** 3388 * Whether registerFontFile expects absolute or relative 3389 * font file names. 3390 */ 3391 protected boolean useAbsoluteFontFileNames() { 3392 return true; 3393 } 3394 3395 /** 3396 * Creates this environment's FontConfiguration. 3397 */ 3398 protected abstract FontConfiguration createFontConfiguration(); 3399 3400 public abstract FontConfiguration 3401 createFontConfiguration(boolean preferLocaleFonts, 3402 boolean preferPropFonts); 3403 3404 /** 3405 * Returns face name for default font, or null if 3406 * no face names are used for CompositeFontDescriptors 3407 * for this platform. 3408 */ 3409 public synchronized String getDefaultFontFaceName() { 3410 if (defaultFontName == null) { 3411 initDefaultFonts(); 3412 } 3413 return defaultFontName; 3414 } 3415 3416 public void loadFontFiles() { 3417 loadFonts(); 3418 if (loadedAllFontFiles) { 3419 return; 3420 } 3421 /* Use lock specific to the font system */ 3422 synchronized (this) { 3423 if (FontUtilities.debugFonts()) { 3424 Thread.dumpStack(); 3425 FontUtilities.getLogger().info("loadAllFontFiles() called"); 3426 } 3427 java.security.AccessController.doPrivileged( 3428 new java.security.PrivilegedAction() { 3429 public Object run() { 3430 if (fontPath == null) { 3431 fontPath = getPlatformFontPath(noType1Font); 3432 } 3433 if (fontPath != null) { 3434 // this will find all fonts including those already 3435 // registered. But we have checks in place to prevent 3436 // double registration. 3437 registerFontsOnPath(fontPath, false, 3438 Font2D.UNKNOWN_RANK, 3439 false, true); 3440 } 3441 loadedAllFontFiles = true; 3442 return null; 3443 } 3444 }); 3445 } 3446 } 3447 3448 /* 3449 * This method asks the font configuration API for all platform names 3450 * used as components of composite/logical fonts and iterates over these 3451 * looking up their corresponding file name and registers these fonts. 3452 * It also ensures that the fonts are accessible via platform APIs. 3453 * The composites themselves are then registered. 3454 */ 3455 private void 3456 initCompositeFonts(FontConfiguration fontConfig, 3457 ConcurrentHashMap<String, Font2D> altNameCache) { 3458 3459 int numCoreFonts = fontConfig.getNumberCoreFonts(); 3460 String[] fcFonts = fontConfig.getPlatformFontNames(); 3461 for (int f=0; f<fcFonts.length; f++) { 3462 String platformFontName = fcFonts[f]; 3463 String fontFileName = 3464 getFileNameFromPlatformName(platformFontName); 3465 String[] nativeNames = null; 3466 if (fontFileName == null 3467 || fontFileName.equals(platformFontName)) { 3468 /* No file located, so register using the platform name, 3469 * i.e. as a native font. 3470 */ 3471 fontFileName = platformFontName; 3472 } else { 3473 if (f < numCoreFonts) { 3474 /* If platform APIs also need to access the font, add it 3475 * to a set to be registered with the platform too. 3476 * This may be used to add the parent directory to the X11 3477 * font path if its not already there. See the docs for the 3478 * subclass implementation. 3479 * This is now mainly for the benefit of X11-based AWT 3480 * But for historical reasons, 2D initialisation code 3481 * makes these calls. 3482 * If the fontconfiguration file is properly set up 3483 * so that all fonts are mapped to files and all their 3484 * appropriate directories are specified, then this 3485 * method will be low cost as it will return after 3486 * a test that finds a null lookup map. 3487 */ 3488 addFontToPlatformFontPath(platformFontName); 3489 } 3490 nativeNames = getNativeNames(fontFileName, platformFontName); 3491 } 3492 /* Uncomment these two lines to "generate" the XLFD->filename 3493 * mappings needed to speed start-up on Solaris. 3494 * Augment this with the appendedpathname and the mappings 3495 * for native (F3) fonts 3496 */ 3497 //String platName = platformFontName.replaceAll(" ", "_"); 3498 //System.out.println("filename."+platName+"="+fontFileName); 3499 registerFontFile(fontFileName, nativeNames, 3500 Font2D.FONT_CONFIG_RANK, true); 3501 3502 3503 } 3504 /* This registers accumulated paths from the calls to 3505 * addFontToPlatformFontPath(..) and any specified by 3506 * the font configuration. Rather than registering 3507 * the fonts it puts them in a place and form suitable for 3508 * the Toolkit to pick up and use if a toolkit is initialised, 3509 * and if it uses X11 fonts. 3510 */ 3511 registerPlatformFontsUsedByFontConfiguration(); 3512 3513 CompositeFontDescriptor[] compositeFontInfo 3514 = fontConfig.get2DCompositeFontInfo(); 3515 for (int i = 0; i < compositeFontInfo.length; i++) { 3516 CompositeFontDescriptor descriptor = compositeFontInfo[i]; 3517 String[] componentFileNames = descriptor.getComponentFileNames(); 3518 String[] componentFaceNames = descriptor.getComponentFaceNames(); 3519 3520 /* It would be better eventually to handle this in the 3521 * FontConfiguration code which should also remove duplicate slots 3522 */ 3523 if (missingFontFiles != null) { 3524 for (int ii=0; ii<componentFileNames.length; ii++) { 3525 if (missingFontFiles.contains(componentFileNames[ii])) { 3526 componentFileNames[ii] = getDefaultFontFile(); 3527 componentFaceNames[ii] = getDefaultFontFaceName(); 3528 } 3529 } 3530 } 3531 3532 /* FontConfiguration needs to convey how many fonts it has added 3533 * as fallback component fonts which should not affect metrics. 3534 * The core component count will be the number of metrics slots. 3535 * This does not preclude other mechanisms for adding 3536 * fall back component fonts to the composite. 3537 */ 3538 if (altNameCache != null) { 3539 SunFontManager.registerCompositeFont( 3540 descriptor.getFaceName(), 3541 componentFileNames, componentFaceNames, 3542 descriptor.getCoreComponentCount(), 3543 descriptor.getExclusionRanges(), 3544 descriptor.getExclusionRangeLimits(), 3545 true, 3546 altNameCache); 3547 } else { 3548 registerCompositeFont(descriptor.getFaceName(), 3549 componentFileNames, componentFaceNames, 3550 descriptor.getCoreComponentCount(), 3551 descriptor.getExclusionRanges(), 3552 descriptor.getExclusionRangeLimits(), 3553 true); 3554 } 3555 if (FontUtilities.debugFonts()) { 3556 FontUtilities.getLogger() 3557 .info("registered " + descriptor.getFaceName()); 3558 } 3559 } 3560 } 3561 3562 /** 3563 * Notifies graphics environment that the logical font configuration 3564 * uses the given platform font name. The graphics environment may 3565 * use this for platform specific initialization. 3566 */ 3567 protected void addFontToPlatformFontPath(String platformFontName) { 3568 } 3569 3570 protected void registerFontFile(String fontFileName, String[] nativeNames, 3571 int fontRank, boolean defer) { 3572 // REMIND: case compare depends on platform 3573 if (registeredFontFiles.contains(fontFileName)) { 3574 return; 3575 } 3576 int fontFormat; 3577 if (ttFilter.accept(null, fontFileName)) { 3578 fontFormat = FONTFORMAT_TRUETYPE; 3579 } else if (t1Filter.accept(null, fontFileName)) { 3580 fontFormat = FONTFORMAT_TYPE1; 3581 } else { 3582 fontFormat = FONTFORMAT_NATIVE; 3583 } 3584 registeredFontFiles.add(fontFileName); 3585 if (defer) { 3586 registerDeferredFont(fontFileName, fontFileName, nativeNames, 3587 fontFormat, false, fontRank); 3588 } else { 3589 registerFontFile(fontFileName, nativeNames, fontFormat, false, 3590 fontRank); 3591 } 3592 } 3593 3594 protected void registerPlatformFontsUsedByFontConfiguration() { 3595 } 3596 3597 /* 3598 * A GE may verify whether a font file used in a fontconfiguration 3599 * exists. If it doesn't then either we may substitute the default 3600 * font, or perhaps elide it altogether from the composite font. 3601 * This makes some sense on windows where the font file is only 3602 * likely to be in one place. But on other OSes, eg Linux, the file 3603 * can move around depending. So there we probably don't want to assume 3604 * its missing and so won't add it to this list. 3605 * If this list - missingFontFiles - is non-null then the composite 3606 * font initialisation logic tests to see if a font file is in that 3607 * set. 3608 * Only one thread should be able to add to this set so we don't 3609 * synchronize. 3610 */ 3611 protected void addToMissingFontFileList(String fileName) { 3612 if (missingFontFiles == null) { 3613 missingFontFiles = new HashSet<String>(); 3614 } 3615 missingFontFiles.add(fileName); 3616 } 3617 3618 /* 3619 * This is for use only within getAllFonts(). 3620 * Fonts listed in the fontconfig files for windows were all 3621 * on the "deferred" initialisation list. They were registered 3622 * either in the course of the application, or in the call to 3623 * loadFonts() within getAllFonts(). The fontconfig file specifies 3624 * the names of the fonts using the English names. If there's a 3625 * different name in the execution locale, then the platform will 3626 * report that, and we will construct the font with both names, and 3627 * thereby enumerate it twice. This happens for Japanese fonts listed 3628 * in the windows fontconfig, when run in the JA locale. The solution 3629 * is to rely (in this case) on the platform's font->file mapping to 3630 * determine that this name corresponds to a file we already registered. 3631 * This works because 3632 * - we know when we get here all deferred fonts are already initialised 3633 * - when we register a font file, we register all fonts in it. 3634 * - we know the fontconfig fonts are all in the windows registry 3635 */ 3636 private boolean isNameForRegisteredFile(String fontName) { 3637 String fileName = getFileNameForFontName(fontName); 3638 if (fileName == null) { 3639 return false; 3640 } 3641 return registeredFontFiles.contains(fileName); 3642 } 3643 3644 /* 3645 * This invocation is not in a privileged block because 3646 * all privileged operations (reading files and properties) 3647 * was conducted on the creation of the GE 3648 */ 3649 public void 3650 createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache, 3651 boolean preferLocale, 3652 boolean preferProportional) { 3653 3654 FontConfiguration fontConfig = 3655 createFontConfiguration(preferLocale, preferProportional); 3656 initCompositeFonts(fontConfig, altNameCache); 3657 } 3658 3659 /** 3660 * Returns all fonts installed in this environment. 3661 */ 3662 public Font[] getAllInstalledFonts() { 3663 if (allFonts == null) { 3664 loadFonts(); 3665 TreeMap fontMapNames = new TreeMap(); 3666 /* warning: the number of composite fonts could change dynamically 3667 * if applications are allowed to create them. "allfonts" could 3668 * then be stale. 3669 */ 3670 Font2D[] allfonts = getRegisteredFonts(); 3671 for (int i=0; i < allfonts.length; i++) { 3672 if (!(allfonts[i] instanceof NativeFont)) { 3673 fontMapNames.put(allfonts[i].getFontName(null), 3674 allfonts[i]); 3675 } 3676 } 3677 3678 String[] platformNames = getFontNamesFromPlatform(); 3679 if (platformNames != null) { 3680 for (int i=0; i<platformNames.length; i++) { 3681 if (!isNameForRegisteredFile(platformNames[i])) { 3682 fontMapNames.put(platformNames[i], null); 3683 } 3684 } 3685 } 3686 3687 String[] fontNames = null; 3688 if (fontMapNames.size() > 0) { 3689 fontNames = new String[fontMapNames.size()]; 3690 Object [] keyNames = fontMapNames.keySet().toArray(); 3691 for (int i=0; i < keyNames.length; i++) { 3692 fontNames[i] = (String)keyNames[i]; 3693 } 3694 } 3695 Font[] fonts = new Font[fontNames.length]; 3696 for (int i=0; i < fontNames.length; i++) { 3697 fonts[i] = new Font(fontNames[i], Font.PLAIN, 1); 3698 Font2D f2d = (Font2D)fontMapNames.get(fontNames[i]); 3699 if (f2d != null) { 3700 FontAccess.getFontAccess().setFont2D(fonts[i], f2d.handle); 3701 } 3702 } 3703 allFonts = fonts; 3704 } 3705 3706 Font []copyFonts = new Font[allFonts.length]; 3707 System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length); 3708 return copyFonts; 3709 } 3710 3711 /** 3712 * Get a list of installed fonts in the requested {@link Locale}. 3713 * The list contains the fonts Family Names. 3714 * If Locale is null, the default locale is used. 3715 * 3716 * @param requestedLocale, if null the default locale is used. 3717 * @return list of installed fonts in the system. 3718 */ 3719 public String[] getInstalledFontFamilyNames(Locale requestedLocale) { 3720 if (requestedLocale == null) { 3721 requestedLocale = Locale.getDefault(); 3722 } 3723 if (allFamilies != null && lastDefaultLocale != null && 3724 requestedLocale.equals(lastDefaultLocale)) { 3725 String[] copyFamilies = new String[allFamilies.length]; 3726 System.arraycopy(allFamilies, 0, copyFamilies, 3727 0, allFamilies.length); 3728 return copyFamilies; 3729 } 3730 3731 TreeMap<String,String> familyNames = new TreeMap<String,String>(); 3732 // these names are always there and aren't localised 3733 String str; 3734 str = Font.SERIF; familyNames.put(str.toLowerCase(), str); 3735 str = Font.SANS_SERIF; familyNames.put(str.toLowerCase(), str); 3736 str = Font.MONOSPACED; familyNames.put(str.toLowerCase(), str); 3737 str = Font.DIALOG; familyNames.put(str.toLowerCase(), str); 3738 str = Font.DIALOG_INPUT; familyNames.put(str.toLowerCase(), str); 3739 3740 /* Platform APIs may be used to get the set of available family 3741 * names for the current default locale so long as it is the same 3742 * as the start-up system locale, rather than loading all fonts. 3743 */ 3744 if (requestedLocale.equals(getSystemStartupLocale()) && 3745 getFamilyNamesFromPlatform(familyNames, requestedLocale)) { 3746 /* Augment platform names with JRE font family names */ 3747 getJREFontFamilyNames(familyNames, requestedLocale); 3748 } else { 3749 loadFontFiles(); 3750 Font2D[] physicalfonts = getPhysicalFonts(); 3751 for (int i=0; i < physicalfonts.length; i++) { 3752 if (!(physicalfonts[i] instanceof NativeFont)) { 3753 String name = 3754 physicalfonts[i].getFamilyName(requestedLocale); 3755 familyNames.put(name.toLowerCase(requestedLocale), name); 3756 } 3757 } 3758 } 3759 3760 String[] retval = new String[familyNames.size()]; 3761 Object [] keyNames = familyNames.keySet().toArray(); 3762 for (int i=0; i < keyNames.length; i++) { 3763 retval[i] = (String)familyNames.get(keyNames[i]); 3764 } 3765 if (requestedLocale.equals(Locale.getDefault())) { 3766 lastDefaultLocale = requestedLocale; 3767 allFamilies = new String[retval.length]; 3768 System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length); 3769 } 3770 return retval; 3771 } 3772 3773 public void register1dot0Fonts() { 3774 java.security.AccessController.doPrivileged( 3775 new java.security.PrivilegedAction() { 3776 public Object run() { 3777 String type1Dir = "/usr/openwin/lib/X11/fonts/Type1"; 3778 registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK, 3779 false, false); 3780 return null; 3781 } 3782 }); 3783 } 3784 3785 /* Really we need only the JRE fonts family names, but there's little 3786 * overhead in doing this the easy way by adding all the currently 3787 * known fonts. 3788 */ 3789 protected void getJREFontFamilyNames(TreeMap<String,String> familyNames, 3790 Locale requestedLocale) { 3791 registerDeferredJREFonts(jreFontDirName); 3792 Font2D[] physicalfonts = getPhysicalFonts(); 3793 for (int i=0; i < physicalfonts.length; i++) { 3794 if (!(physicalfonts[i] instanceof NativeFont)) { 3795 String name = 3796 physicalfonts[i].getFamilyName(requestedLocale); 3797 familyNames.put(name.toLowerCase(requestedLocale), name); 3798 } 3799 } 3800 } 3801 3802 /** 3803 * Default locale can be changed but we need to know the initial locale 3804 * as that is what is used by native code. Changing Java default locale 3805 * doesn't affect that. 3806 * Returns the locale in use when using native code to communicate 3807 * with platform APIs. On windows this is known as the "system" locale, 3808 * and it is usually the same as the platform locale, but not always, 3809 * so this method also checks an implementation property used only 3810 * on windows and uses that if set. 3811 */ 3812 private static Locale systemLocale = null; 3813 private static Locale getSystemStartupLocale() { 3814 if (systemLocale == null) { 3815 systemLocale = (Locale) 3816 java.security.AccessController.doPrivileged( 3817 new java.security.PrivilegedAction() { 3818 public Object run() { 3819 /* On windows the system locale may be different than the 3820 * user locale. This is an unsupported configuration, but 3821 * in that case we want to return a dummy locale that will 3822 * never cause a match in the usage of this API. This is 3823 * important because Windows documents that the family 3824 * names of fonts are enumerated using the language of 3825 * the system locale. BY returning a dummy locale in that 3826 * case we do not use the platform API which would not 3827 * return us the names we want. 3828 */ 3829 String fileEncoding = System.getProperty("file.encoding", ""); 3830 String sysEncoding = System.getProperty("sun.jnu.encoding"); 3831 if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) { 3832 return Locale.ROOT; 3833 } 3834 3835 String language = System.getProperty("user.language", "en"); 3836 String country = System.getProperty("user.country",""); 3837 String variant = System.getProperty("user.variant",""); 3838 return new Locale(language, country, variant); 3839 } 3840 }); 3841 } 3842 return systemLocale; 3843 } 3844 3845 void addToPool(FileFont font) { 3846 3847 FileFont fontFileToClose = null; 3848 int freeSlot = -1; 3849 3850 synchronized (fontFileCache) { 3851 /* Avoid duplicate entries in the pool, and don't close() it, 3852 * since this method is called only from within open(). 3853 * Seeing a duplicate is most likely to happen if the thread 3854 * was interrupted during a read, forcing perhaps repeated 3855 * close and open calls and it eventually it ends up pointing 3856 * at the same slot. 3857 */ 3858 for (int i=0;i<CHANNELPOOLSIZE;i++) { 3859 if (fontFileCache[i] == font) { 3860 return; 3861 } 3862 if (fontFileCache[i] == null && freeSlot < 0) { 3863 freeSlot = i; 3864 } 3865 } 3866 if (freeSlot >= 0) { 3867 fontFileCache[freeSlot] = font; 3868 return; 3869 } else { 3870 /* replace with new font. */ 3871 fontFileToClose = fontFileCache[lastPoolIndex]; 3872 fontFileCache[lastPoolIndex] = font; 3873 /* lastPoolIndex is updated so that the least recently opened 3874 * file will be closed next. 3875 */ 3876 lastPoolIndex = (lastPoolIndex+1) % CHANNELPOOLSIZE; 3877 } 3878 } 3879 /* Need to close the font file outside of the synchronized block, 3880 * since its possible some other thread is in an open() call on 3881 * this font file, and could be holding its lock and the pool lock. 3882 * Releasing the pool lock allows that thread to continue, so it can 3883 * then release the lock on this font, allowing the close() call 3884 * below to proceed. 3885 * Also, calling close() is safe because any other thread using 3886 * the font we are closing() synchronizes all reading, so we 3887 * will not close the file while its in use. 3888 */ 3889 if (fontFileToClose != null) { 3890 fontFileToClose.close(); 3891 } 3892 } 3893 3894 protected FontUIResource getFontConfigFUIR(String family, int style, 3895 int size) 3896 { 3897 return new FontUIResource(family, style, size); 3898 } 3899 }