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