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