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