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