1 /* 2 * Copyright 1997-2008 Sun Microsystems, Inc. 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. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 26 package sun.java2d; 27 28 import java.awt.Color; 29 import java.awt.Font; 30 import java.awt.Graphics2D; 31 import java.awt.GraphicsConfiguration; 32 import java.awt.GraphicsDevice; 33 import java.awt.GraphicsEnvironment; 34 import java.awt.Insets; 35 import java.awt.Rectangle; 36 import java.awt.Toolkit; 37 import java.awt.font.TextAttribute; 38 import java.awt.image.BufferedImage; 39 import java.awt.peer.ComponentPeer; 40 import java.io.BufferedReader; 41 import java.io.File; 42 import java.io.FileInputStream; 43 import java.io.FilenameFilter; 44 import java.io.InputStreamReader; 45 import java.io.IOException; 46 import java.text.AttributedCharacterIterator; 47 import java.util.ArrayList; 48 import java.util.HashSet; 49 import java.util.Iterator; 50 import java.util.Locale; 51 import java.util.Map; 52 import java.util.NoSuchElementException; 53 import java.util.Set; 54 import java.util.StringTokenizer; 55 import java.util.TreeMap; 56 import java.util.Vector; 57 import java.util.concurrent.ConcurrentHashMap; 58 import java.util.logging.Level; 59 import java.util.logging.Logger; 60 import sun.awt.AppContext; 61 import sun.awt.DisplayChangedListener; 62 import sun.awt.FontConfiguration; 63 import sun.awt.SunDisplayChanger; 64 import sun.font.CompositeFontDescriptor; 65 import sun.font.Font2D; 66 import sun.font.FontManager; 67 import sun.font.NativeFont; 68 69 /** 70 * This is an implementation of a GraphicsEnvironment object for the 71 * default local GraphicsEnvironment. 72 * 73 * @see GraphicsDevice 74 * @see GraphicsConfiguration 75 */ 76 77 public abstract class SunGraphicsEnvironment extends GraphicsEnvironment 78 implements FontSupport, DisplayChangedListener { 79 80 public static boolean isLinux; 81 public static boolean isSolaris; 82 public static boolean isOpenSolaris; 83 public static boolean isWindows; 84 public static boolean noType1Font; 85 private static Font defaultFont; 86 private static String defaultFontFileName; 87 private static String defaultFontName; 88 public static final String lucidaFontName = "Lucida Sans Regular"; 89 public static final String lucidaFileName = "LucidaSansRegular.ttf"; 90 public static boolean debugFonts = false; 91 protected static Logger logger = null; 92 private static ArrayList badFonts; 93 public static String jreLibDirName; 94 public static String jreFontDirName; 95 private static HashSet<String> missingFontFiles = null; 96 97 private FontConfiguration fontConfig; 98 99 /* fontPath is the location of all fonts on the system, excluding the 100 * JRE's own font directory but including any path specified using the 101 * sun.java2d.fontpath property. Together with that property, it is 102 * initialised by the getPlatformFontPath() method 103 * This call must be followed by a call to registerFontDirs(fontPath) 104 * once any extra debugging path has been appended. 105 */ 106 protected String fontPath; 107 108 /* discoveredAllFonts is set to true when all fonts on the font path are 109 * discovered. This usually also implies opening, validating and 110 * registering, but an implementation may be optimized to avold this. 111 * So see also "loadedAllFontFiles" 112 */ 113 private boolean discoveredAllFonts = false; 114 115 /* loadedAllFontFiles is set to true when all fonts on the font path are 116 * actually opened, validated and registered. This always implies 117 * discoveredAllFonts is true. 118 */ 119 private boolean loadedAllFontFiles = false; 120 121 protected HashSet registeredFontFiles = new HashSet(); 122 public static String eudcFontFileName; /* Initialised only on windows */ 123 124 private static boolean isOpenJDK; 125 /** 126 * A few things in Java 2D code are different in OpenJDK, 127 * so need a way to tell which implementation this is. 128 * The absence of Lucida Sans Regular is the simplest way for now. 129 */ 130 public static boolean isOpenJDK() { 131 return isOpenJDK; 132 } 133 134 static { 135 java.security.AccessController.doPrivileged( 136 new java.security.PrivilegedAction() { 137 public Object run() { 138 139 jreLibDirName = System.getProperty("java.home","") + 140 File.separator + "lib"; 141 jreFontDirName = jreLibDirName + File.separator + "fonts"; 142 File lucidaFile = 143 new File(jreFontDirName + File.separator + lucidaFileName); 144 isOpenJDK = !lucidaFile.exists(); 145 146 String debugLevel = 147 System.getProperty("sun.java2d.debugfonts"); 148 149 if (debugLevel != null && !debugLevel.equals("false")) { 150 debugFonts = true; 151 logger = Logger.getLogger("sun.java2d"); 152 if (debugLevel.equals("warning")) { 153 logger.setLevel(Level.WARNING); 154 } else if (debugLevel.equals("severe")) { 155 logger.setLevel(Level.SEVERE); 156 } 157 } 158 return null; 159 } 160 }); 161 }; 162 163 public SunGraphicsEnvironment() { 164 java.security.AccessController.doPrivileged( 165 new java.security.PrivilegedAction() { 166 public Object run() { 167 String osName = System.getProperty("os.name"); 168 if ("Linux".equals(osName)) { 169 isLinux = true; 170 } else if ("SunOS".equals(osName)) { 171 isSolaris = true; 172 String version = System.getProperty("os.version", "0.0"); 173 try { 174 float ver = Float.parseFloat(version); 175 if (ver > 5.10f) { 176 File f = new File("/etc/release"); 177 FileInputStream fis = new FileInputStream(f); 178 InputStreamReader isr 179 = new InputStreamReader(fis, "ISO-8859-1"); 180 BufferedReader br = new BufferedReader(isr); 181 String line = br.readLine(); 182 if (line.indexOf("OpenSolaris") >= 0) { 183 isOpenSolaris = true; 184 } 185 fis.close(); 186 } 187 } catch (Exception e) { 188 } 189 } else if ("Windows".equals(osName)) { 190 isWindows = true; 191 } 192 193 noType1Font = "true". 194 equals(System.getProperty("sun.java2d.noType1Font")); 195 196 if (!isOpenJDK()) { 197 defaultFontName = lucidaFontName; 198 if (useAbsoluteFontFileNames()) { 199 defaultFontFileName = 200 jreFontDirName + File.separator + lucidaFileName; 201 } else { 202 defaultFontFileName = lucidaFileName; 203 } 204 } 205 206 File badFontFile = 207 new File(jreFontDirName + File.separator + "badfonts.txt"); 208 if (badFontFile.exists()) { 209 FileInputStream fis = null; 210 try { 211 badFonts = new ArrayList(); 212 fis = new FileInputStream(badFontFile); 213 InputStreamReader isr = new InputStreamReader(fis); 214 BufferedReader br = new BufferedReader(isr); 215 while (true) { 216 String name = br.readLine(); 217 if (name == null) { 218 break; 219 } else { 220 if (debugFonts) { 221 logger.warning("read bad font: " + name); 222 } 223 badFonts.add(name); 224 } 225 } 226 } catch (IOException e) { 227 try { 228 if (fis != null) { 229 fis.close(); 230 } 231 } catch (IOException ioe) { 232 } 233 } 234 } 235 236 /* Here we get the fonts in jre/lib/fonts and register them 237 * so they are always available and preferred over other fonts. 238 * This needs to be registered before the composite fonts as 239 * otherwise some native font that corresponds may be found 240 * as we don't have a way to handle two fonts of the same 241 * name, so the JRE one must be the first one registered. 242 * Pass "true" to registerFonts method as on-screen these 243 * JRE fonts always go through the T2K rasteriser. 244 */ 245 if (isLinux) { 246 /* Linux font configuration uses these fonts */ 247 registerFontDir(jreFontDirName); 248 } 249 registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK, 250 true, false); 251 252 /* Register the JRE fonts so that the native platform can 253 * access them. This is used only on Windows so that when 254 * printing the printer driver can access the fonts. 255 */ 256 registerJREFontsWithPlatform(jreFontDirName); 257 258 /* Create the font configuration and get any font path 259 * that might be specified. 260 */ 261 fontConfig = createFontConfiguration(); 262 if (isOpenJDK()) { 263 String[] fontInfo = FontManager.getDefaultPlatformFont(); 264 defaultFontName = fontInfo[0]; 265 defaultFontFileName = fontInfo[1]; 266 } 267 getPlatformFontPathFromFontConfig(); 268 269 String extraFontPath = fontConfig.getExtraFontPath(); 270 271 /* In prior releases the debugging font path replaced 272 * all normally located font directories except for the 273 * JRE fonts dir. This directory is still always located and 274 * placed at the head of the path but as an augmentation 275 * to the previous behaviour the 276 * changes below allow you to additionally append to 277 * the font path by starting with append: or prepend by 278 * starting with a prepend: sign. Eg: to append 279 * -Dsun.java2d.fontpath=append:/usr/local/myfonts 280 * and to prepend 281 * -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp 282 * 283 * If there is an appendedfontpath it in the font configuration 284 * it is used instead of searching the system for dirs. 285 * The behaviour of append and prepend is then similar 286 * to the normal case. ie it goes after what 287 * you prepend and * before what you append. If the 288 * sun.java2d.fontpath property is used, but it 289 * neither the append or prepend syntaxes is used then as 290 * except for the JRE dir the path is replaced and it is 291 * up to you to make sure that all the right directories 292 * are located. This is platform and locale-specific so 293 * its almost impossible to get right, so it should be 294 * used with caution. 295 */ 296 boolean prependToPath = false; 297 boolean appendToPath = false; 298 String dbgFontPath = System.getProperty("sun.java2d.fontpath"); 299 300 if (dbgFontPath != null) { 301 if (dbgFontPath.startsWith("prepend:")) { 302 prependToPath = true; 303 dbgFontPath = 304 dbgFontPath.substring("prepend:".length()); 305 } else if (dbgFontPath.startsWith("append:")) { 306 appendToPath = true; 307 dbgFontPath = 308 dbgFontPath.substring("append:".length()); 309 } 310 } 311 312 if (debugFonts) { 313 logger.info("JRE font directory: " + jreFontDirName); 314 logger.info("Extra font path: " + extraFontPath); 315 logger.info("Debug font path: " + dbgFontPath); 316 } 317 318 if (dbgFontPath != null) { 319 /* In debugging mode we register all the paths 320 * Caution: this is a very expensive call on Solaris:- 321 */ 322 fontPath = getPlatformFontPath(noType1Font); 323 324 if (extraFontPath != null) { 325 fontPath = 326 extraFontPath + File.pathSeparator + fontPath; 327 } 328 if (appendToPath) { 329 fontPath = fontPath + File.pathSeparator + dbgFontPath; 330 } else if (prependToPath) { 331 fontPath = dbgFontPath + File.pathSeparator + fontPath; 332 } else { 333 fontPath = dbgFontPath; 334 } 335 registerFontDirs(fontPath); 336 } else if (extraFontPath != null) { 337 /* If the font configuration contains an "appendedfontpath" 338 * entry, it is interpreted as a set of locations that 339 * should always be registered. 340 * It may be additional to locations normally found for 341 * that place, or it may be locations that need to have 342 * all their paths registered to locate all the needed 343 * platform names. 344 * This is typically when the same .TTF file is referenced 345 * from multiple font.dir files and all of these must be 346 * read to find all the native (XLFD) names for the font, 347 * so that X11 font APIs can be used for as many code 348 * points as possible. 349 */ 350 registerFontDirs(extraFontPath); 351 } 352 353 /* On Solaris, we need to register the Japanese TrueType 354 * directory so that we can find the corresponding bitmap 355 * fonts. This could be done by listing the directory in 356 * the font configuration file, but we don't want to 357 * confuse users with this quirk. There are no bitmap fonts 358 * for other writing systems that correspond to TrueType 359 * fonts and have matching XLFDs. We need to register the 360 * bitmap fonts only in environments where they're on the 361 * X font path, i.e., in the Japanese locale. 362 * Note that if the X Toolkit is in use the font path isn't 363 * set up by JDK, but users of a JA locale should have it 364 * set up already by their login environment. 365 */ 366 if (isSolaris && Locale.JAPAN.equals(Locale.getDefault())) { 367 registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT"); 368 } 369 370 initCompositeFonts(fontConfig, null); 371 372 /* Establish the default font to be used by SG2D etc */ 373 defaultFont = new Font(Font.DIALOG, Font.PLAIN, 12); 374 375 return null; 376 } 377 }); 378 } 379 380 protected GraphicsDevice[] screens; 381 382 /** 383 * Returns an array of all of the screen devices. 384 */ 385 public synchronized GraphicsDevice[] getScreenDevices() { 386 GraphicsDevice[] ret = screens; 387 if (ret == null) { 388 int num = getNumScreens(); 389 ret = new GraphicsDevice[num]; 390 for (int i = 0; i < num; i++) { 391 ret[i] = makeScreenDevice(i); 392 } 393 screens = ret; 394 } 395 return ret; 396 } 397 398 protected abstract int getNumScreens(); 399 protected abstract GraphicsDevice makeScreenDevice(int screennum); 400 401 /** 402 * Returns the default screen graphics device. 403 */ 404 public GraphicsDevice getDefaultScreenDevice() { 405 return getScreenDevices()[0]; 406 } 407 408 /** 409 * Returns a Graphics2D object for rendering into the 410 * given BufferedImage. 411 * @throws NullPointerException if BufferedImage argument is null 412 */ 413 public Graphics2D createGraphics(BufferedImage img) { 414 if (img == null) { 415 throw new NullPointerException("BufferedImage cannot be null"); 416 } 417 SurfaceData sd = SurfaceData.getPrimarySurfaceData(img); 418 return new SunGraphics2D(sd, Color.white, Color.black, defaultFont); 419 } 420 421 /* A call to this method should be followed by a call to 422 * registerFontDirs(..) 423 */ 424 protected String getPlatformFontPath(boolean noType1Font) { 425 if (fontPath == null) { 426 fontPath = FontManager.getFontPath(noType1Font); 427 } 428 return fontPath; 429 } 430 431 private String[] platformFontDirs; 432 /** 433 * Get all directories which contain installed fonts. 434 */ 435 public String[] getPlatformFontDirs() { 436 if (platformFontDirs == null) { 437 String path = getPlatformFontPath(noType1Font); 438 StringTokenizer parser = 439 new StringTokenizer(path, File.pathSeparator);; 440 ArrayList<String> pathList = new ArrayList<String>(); 441 try { 442 while (parser.hasMoreTokens()) { 443 pathList.add(parser.nextToken()); 444 } 445 } catch (NoSuchElementException e) { 446 } 447 platformFontDirs = pathList.toArray(new String[0]); 448 } 449 return platformFontDirs; 450 } 451 452 /** 453 * Whether registerFontFile expects absolute or relative 454 * font file names. 455 */ 456 protected boolean useAbsoluteFontFileNames() { 457 return true; 458 } 459 460 /** 461 * Returns file name for default font, either absolute 462 * or relative as needed by registerFontFile. 463 */ 464 public String getDefaultFontFile() { 465 return defaultFontFileName; 466 } 467 468 /** 469 * Returns face name for default font, or null if 470 * no face names are used for CompositeFontDescriptors 471 * for this platform. 472 */ 473 public String getDefaultFontFaceName() { 474 return defaultFontName; 475 } 476 477 public void loadFonts() { 478 if (discoveredAllFonts) { 479 return; 480 } 481 /* Use lock specific to the font system */ 482 synchronized (lucidaFontName) { 483 if (debugFonts) { 484 Thread.dumpStack(); 485 logger.info("SunGraphicsEnvironment.loadFonts() called"); 486 } 487 FontManager.initialiseDeferredFonts(); 488 489 java.security.AccessController.doPrivileged( 490 new java.security.PrivilegedAction() { 491 public Object run() { 492 if (fontPath == null) { 493 fontPath = getPlatformFontPath(noType1Font); 494 registerFontDirs(fontPath); 495 } 496 if (fontPath != null) { 497 // this will find all fonts including those already 498 // registered. But we have checks in place to prevent 499 // double registration. 500 if (!FontManager.gotFontsFromPlatform()) { 501 registerFontsOnPath(fontPath, false, 502 Font2D.UNKNOWN_RANK, 503 false, true); 504 loadedAllFontFiles = true; 505 } 506 } 507 FontManager.registerOtherFontFiles(registeredFontFiles); 508 discoveredAllFonts = true; 509 return null; 510 } 511 }); 512 } 513 } 514 515 516 public void loadFontFiles() { 517 loadFonts(); 518 if (loadedAllFontFiles) { 519 return; 520 } 521 /* Use lock specific to the font system */ 522 synchronized (lucidaFontName) { 523 if (debugFonts) { 524 Thread.dumpStack(); 525 logger.info("loadAllFontFiles() called"); 526 } 527 java.security.AccessController.doPrivileged( 528 new java.security.PrivilegedAction() { 529 public Object run() { 530 if (fontPath == null) { 531 fontPath = getPlatformFontPath(noType1Font); 532 } 533 if (fontPath != null) { 534 // this will find all fonts including those already 535 // registered. But we have checks in place to prevent 536 // double registration. 537 registerFontsOnPath(fontPath, false, 538 Font2D.UNKNOWN_RANK, 539 false, true); 540 } 541 loadedAllFontFiles = true; 542 return null; 543 } 544 }); 545 } 546 } 547 548 /* 549 * This is for use only within getAllFonts(). 550 * Fonts listed in the fontconfig files for windows were all 551 * on the "deferred" initialisation list. They were registered 552 * either in the course of the application, or in the call to 553 * loadFonts() within getAllFonts(). The fontconfig file specifies 554 * the names of the fonts using the English names. If there's a 555 * different name in the execution locale, then the platform will 556 * report that, and we will construct the font with both names, and 557 * thereby enumerate it twice. This happens for Japanese fonts listed 558 * in the windows fontconfig, when run in the JA locale. The solution 559 * is to rely (in this case) on the platform's font->file mapping to 560 * determine that this name corresponds to a file we already registered. 561 * This works because 562 * - we know when we get here all deferred fonts are already initialised 563 * - when we register a font file, we register all fonts in it. 564 * - we know the fontconfig fonts are all in the windows registry 565 */ 566 private boolean isNameForRegisteredFile(String fontName) { 567 String fileName = FontManager.getFileNameForFontName(fontName); 568 if (fileName == null) { 569 return false; 570 } 571 return registeredFontFiles.contains(fileName); 572 } 573 574 private Font[] allFonts; 575 576 /** 577 * Returns all fonts installed in this environment. 578 */ 579 public Font[] getAllInstalledFonts() { 580 if (allFonts == null) { 581 loadFonts(); 582 TreeMap fontMapNames = new TreeMap(); 583 /* warning: the number of composite fonts could change dynamically 584 * if applications are allowed to create them. "allfonts" could 585 * then be stale. 586 */ 587 588 Font2D[] allfonts = FontManager.getRegisteredFonts(); 589 for (int i=0; i < allfonts.length; i++) { 590 if (!(allfonts[i] instanceof NativeFont)) { 591 fontMapNames.put(allfonts[i].getFontName(null), 592 allfonts[i]); 593 } 594 } 595 596 String[] platformNames = FontManager.getFontNamesFromPlatform(); 597 if (platformNames != null) { 598 for (int i=0; i<platformNames.length; i++) { 599 if (!isNameForRegisteredFile(platformNames[i])) { 600 fontMapNames.put(platformNames[i], null); 601 } 602 } 603 } 604 605 String[] fontNames = null; 606 if (fontMapNames.size() > 0) { 607 fontNames = new String[fontMapNames.size()]; 608 Object [] keyNames = fontMapNames.keySet().toArray(); 609 for (int i=0; i < keyNames.length; i++) { 610 fontNames[i] = (String)keyNames[i]; 611 } 612 } 613 Font[] fonts = new Font[fontNames.length]; 614 for (int i=0; i < fontNames.length; i++) { 615 fonts[i] = new Font(fontNames[i], Font.PLAIN, 1); 616 Font2D f2d = (Font2D)fontMapNames.get(fontNames[i]); 617 if (f2d != null) { 618 FontManager.setFont2D(fonts[i], f2d.handle); 619 } 620 } 621 allFonts = fonts; 622 } 623 624 Font []copyFonts = new Font[allFonts.length]; 625 System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length); 626 return copyFonts; 627 } 628 629 /** 630 * Returns all fonts available in this environment. 631 */ 632 public Font[] getAllFonts() { 633 Font[] installedFonts = getAllInstalledFonts(); 634 Font[] created = FontManager.getCreatedFonts(); 635 if (created == null || created.length == 0) { 636 return installedFonts; 637 } else { 638 int newlen = installedFonts.length + created.length; 639 Font [] fonts = java.util.Arrays.copyOf(installedFonts, newlen); 640 System.arraycopy(created, 0, fonts, 641 installedFonts.length, created.length); 642 return fonts; 643 } 644 } 645 646 /** 647 * Default locale can be changed but we need to know the initial locale 648 * as that is what is used by native code. Changing Java default locale 649 * doesn't affect that. 650 * Returns the locale in use when using native code to communicate 651 * with platform APIs. On windows this is known as the "system" locale, 652 * and it is usually the same as the platform locale, but not always, 653 * so this method also checks an implementation property used only 654 * on windows and uses that if set. 655 */ 656 private static Locale systemLocale = null; 657 public static Locale getSystemStartupLocale() { 658 if (systemLocale == null) { 659 systemLocale = (Locale) 660 java.security.AccessController.doPrivileged( 661 new java.security.PrivilegedAction() { 662 public Object run() { 663 /* On windows the system locale may be different than the 664 * user locale. This is an unsupported configuration, but 665 * in that case we want to return a dummy locale that will 666 * never cause a match in the usage of this API. This is 667 * important because Windows documents that the family 668 * names of fonts are enumerated using the language of 669 * the system locale. BY returning a dummy locale in that 670 * case we do not use the platform API which would not 671 * return us the names we want. 672 */ 673 String fileEncoding = System.getProperty("file.encoding", ""); 674 String sysEncoding = System.getProperty("sun.jnu.encoding"); 675 if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) { 676 return Locale.ROOT; 677 } 678 679 String language = System.getProperty("user.language", "en"); 680 String country = System.getProperty("user.country",""); 681 String variant = System.getProperty("user.variant",""); 682 return new Locale(language, country, variant); 683 } 684 }); 685 } 686 return systemLocale; 687 } 688 689 /* Really we need only the JRE fonts family names, but there's little 690 * overhead in doing this the easy way by adding all the currently 691 * known fonts. 692 */ 693 protected void getJREFontFamilyNames(TreeMap<String,String> familyNames, 694 Locale requestedLocale) { 695 FontManager.registerDeferredJREFonts(jreFontDirName); 696 Font2D[] physicalfonts = FontManager.getPhysicalFonts(); 697 for (int i=0; i < physicalfonts.length; i++) { 698 if (!(physicalfonts[i] instanceof NativeFont)) { 699 String name = 700 physicalfonts[i].getFamilyName(requestedLocale); 701 familyNames.put(name.toLowerCase(requestedLocale), name); 702 } 703 } 704 } 705 706 private String[] allFamilies; // cache for default locale only 707 private Locale lastDefaultLocale; 708 709 public String[] getInstalledFontFamilyNames(Locale requestedLocale) { 710 if (requestedLocale == null) { 711 requestedLocale = Locale.getDefault(); 712 } 713 if (allFamilies != null && lastDefaultLocale != null && 714 requestedLocale.equals(lastDefaultLocale)) { 715 String[] copyFamilies = new String[allFamilies.length]; 716 System.arraycopy(allFamilies, 0, copyFamilies, 717 0, allFamilies.length); 718 return copyFamilies; 719 } 720 721 TreeMap<String,String> familyNames = new TreeMap<String,String>(); 722 // these names are always there and aren't localised 723 String str; 724 str = Font.SERIF; familyNames.put(str.toLowerCase(), str); 725 str = Font.SANS_SERIF; familyNames.put(str.toLowerCase(), str); 726 str = Font.MONOSPACED; familyNames.put(str.toLowerCase(), str); 727 str = Font.DIALOG; familyNames.put(str.toLowerCase(), str); 728 str = Font.DIALOG_INPUT; familyNames.put(str.toLowerCase(), str); 729 730 /* Platform APIs may be used to get the set of available family 731 * names for the current default locale so long as it is the same 732 * as the start-up system locale, rather than loading all fonts. 733 */ 734 if (requestedLocale.equals(getSystemStartupLocale()) && 735 FontManager.getFamilyNamesFromPlatform(familyNames, 736 requestedLocale)) { 737 /* Augment platform names with JRE font family names */ 738 getJREFontFamilyNames(familyNames, requestedLocale); 739 } else { 740 loadFontFiles(); 741 Font2D[] physicalfonts = FontManager.getPhysicalFonts(); 742 for (int i=0; i < physicalfonts.length; i++) { 743 if (!(physicalfonts[i] instanceof NativeFont)) { 744 String name = 745 physicalfonts[i].getFamilyName(requestedLocale); 746 familyNames.put(name.toLowerCase(requestedLocale), name); 747 } 748 } 749 } 750 751 String[] retval = new String[familyNames.size()]; 752 Object [] keyNames = familyNames.keySet().toArray(); 753 for (int i=0; i < keyNames.length; i++) { 754 retval[i] = (String)familyNames.get(keyNames[i]); 755 } 756 if (requestedLocale.equals(Locale.getDefault())) { 757 lastDefaultLocale = requestedLocale; 758 allFamilies = new String[retval.length]; 759 System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length); 760 } 761 return retval; 762 } 763 764 public String[] getAvailableFontFamilyNames(Locale requestedLocale) { 765 String[] installed = getInstalledFontFamilyNames(requestedLocale); 766 /* Use a new TreeMap as used in getInstalledFontFamilyNames 767 * and insert all the keys in lower case, so that the sort order 768 * is the same as the installed families. This preserves historical 769 * behaviour and inserts new families in the right place. 770 * It would have been marginally more efficient to directly obtain 771 * the tree map and just insert new entries, but not so much as 772 * to justify the extra internal interface. 773 */ 774 TreeMap<String, String> map = FontManager.getCreatedFontFamilyNames(); 775 if (map == null || map.size() == 0) { 776 return installed; 777 } else { 778 for (int i=0; i<installed.length; i++) { 779 map.put(installed[i].toLowerCase(requestedLocale), 780 installed[i]); 781 } 782 String[] retval = new String[map.size()]; 783 Object [] keyNames = map.keySet().toArray(); 784 for (int i=0; i < keyNames.length; i++) { 785 retval[i] = (String)map.get(keyNames[i]); 786 } 787 return retval; 788 } 789 } 790 791 public String[] getAvailableFontFamilyNames() { 792 return getAvailableFontFamilyNames(Locale.getDefault()); 793 } 794 795 /** 796 * Returns a file name for the physical font represented by this platform 797 * font name. The default implementation tries to obtain the file name 798 * from the font configuration. 799 * Subclasses may override to provide information from other sources. 800 */ 801 protected String getFileNameFromPlatformName(String platformFontName) { 802 return fontConfig.getFileNameFromPlatformName(platformFontName); 803 } 804 805 public static class TTFilter implements FilenameFilter { 806 public boolean accept(File dir,String name) { 807 /* all conveniently have the same suffix length */ 808 int offset = name.length()-4; 809 if (offset <= 0) { /* must be at least A.ttf */ 810 return false; 811 } else { 812 return(name.startsWith(".ttf", offset) || 813 name.startsWith(".TTF", offset) || 814 name.startsWith(".ttc", offset) || 815 name.startsWith(".TTC", offset) || 816 name.startsWith(".otf", offset) || 817 name.startsWith(".OTF", offset)); 818 } 819 } 820 } 821 822 public static class T1Filter implements FilenameFilter { 823 public boolean accept(File dir,String name) { 824 if (noType1Font) { 825 return false; 826 } 827 /* all conveniently have the same suffix length */ 828 int offset = name.length()-4; 829 if (offset <= 0) { /* must be at least A.pfa */ 830 return false; 831 } else { 832 return(name.startsWith(".pfa", offset) || 833 name.startsWith(".pfb", offset) || 834 name.startsWith(".PFA", offset) || 835 name.startsWith(".PFB", offset)); 836 } 837 } 838 } 839 840 public static class TTorT1Filter implements FilenameFilter { 841 public boolean accept(File dir, String name) { 842 return SunGraphicsEnvironment.ttFilter.accept(dir, name) || 843 SunGraphicsEnvironment.t1Filter.accept(dir, name); 844 } 845 } 846 847 /* No need to keep consing up new instances - reuse a singleton. 848 * The trade-off is that these objects don't get GC'd. 849 */ 850 public static final TTFilter ttFilter = new TTFilter(); 851 public static final T1Filter t1Filter = new T1Filter(); 852 853 /* The majority of the register functions in this class are 854 * registering platform fonts in the JRE's font maps. 855 * The next one is opposite in function as it registers the JRE 856 * fonts as platform fonts. If subsequent to calling this 857 * your implementation enumerates platform fonts in a way that 858 * would return these fonts too you may get duplicates. 859 * This function is primarily used to install the JRE fonts 860 * so that the native platform can access them. 861 * It is intended to be overridden by platform subclasses 862 * Currently minimal use is made of this as generally 863 * Java 2D doesn't need the platform to be able to 864 * use its fonts and platforms which already have matching 865 * fonts registered (possibly even from other different JRE 866 * versions) generally can't be guaranteed to use the 867 * one registered by this JRE version in response to 868 * requests from this JRE. 869 */ 870 protected void registerJREFontsWithPlatform(String pathName) { 871 return; 872 } 873 874 /* Called from FontManager - has Solaris specific implementation */ 875 public void register1dot0Fonts() { 876 java.security.AccessController.doPrivileged( 877 new java.security.PrivilegedAction() { 878 public Object run() { 879 String type1Dir = "/usr/openwin/lib/X11/fonts/Type1"; 880 registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK, 881 false, false); 882 return null; 883 } 884 }); 885 } 886 887 protected void registerFontDirs(String pathName) { 888 return; 889 } 890 891 /* Called to register fall back fonts */ 892 public void registerFontsInDir(String dirName) { 893 registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false); 894 } 895 896 private void registerFontsInDir(String dirName, boolean useJavaRasterizer, 897 int fontRank, 898 boolean defer, boolean resolveSymLinks) { 899 File pathFile = new File(dirName); 900 addDirFonts(dirName, pathFile, ttFilter, 901 FontManager.FONTFORMAT_TRUETYPE, useJavaRasterizer, 902 fontRank==Font2D.UNKNOWN_RANK ? 903 Font2D.TTF_RANK : fontRank, 904 defer, resolveSymLinks); 905 addDirFonts(dirName, pathFile, t1Filter, 906 FontManager.FONTFORMAT_TYPE1, useJavaRasterizer, 907 fontRank==Font2D.UNKNOWN_RANK ? 908 Font2D.TYPE1_RANK : fontRank, 909 defer, resolveSymLinks); 910 } 911 912 private void registerFontsOnPath(String pathName, 913 boolean useJavaRasterizer, int fontRank, 914 boolean defer, boolean resolveSymLinks) { 915 916 StringTokenizer parser = new StringTokenizer(pathName, 917 File.pathSeparator); 918 try { 919 while (parser.hasMoreTokens()) { 920 registerFontsInDir(parser.nextToken(), 921 useJavaRasterizer, fontRank, 922 defer, resolveSymLinks); 923 } 924 } catch (NoSuchElementException e) { 925 } 926 } 927 928 protected void registerFontFile(String fontFileName, String[] nativeNames, 929 int fontRank, boolean defer) { 930 // REMIND: case compare depends on platform 931 if (registeredFontFiles.contains(fontFileName)) { 932 return; 933 } 934 int fontFormat; 935 if (ttFilter.accept(null, fontFileName)) { 936 fontFormat = FontManager.FONTFORMAT_TRUETYPE; 937 } else if (t1Filter.accept(null, fontFileName)) { 938 fontFormat = FontManager.FONTFORMAT_TYPE1; 939 } else { 940 fontFormat = FontManager.FONTFORMAT_NATIVE; 941 } 942 registeredFontFiles.add(fontFileName); 943 if (defer) { 944 FontManager.registerDeferredFont(fontFileName, 945 fontFileName, nativeNames, 946 fontFormat, false, fontRank); 947 } else { 948 FontManager.registerFontFile(fontFileName, nativeNames, 949 fontFormat, false, fontRank); 950 } 951 } 952 953 protected void registerFontDir(String path) { 954 } 955 956 protected String[] getNativeNames(String fontFileName, 957 String platformName) { 958 return null; 959 } 960 961 /* 962 * helper function for registerFonts 963 */ 964 private void addDirFonts(String dirName, File dirFile, 965 FilenameFilter filter, 966 int fontFormat, boolean useJavaRasterizer, 967 int fontRank, 968 boolean defer, boolean resolveSymLinks) { 969 String[] ls = dirFile.list(filter); 970 if (ls == null || ls.length == 0) { 971 return; 972 } 973 String[] fontNames = new String[ls.length]; 974 String[][] nativeNames = new String[ls.length][]; 975 int fontCount = 0; 976 977 for (int i=0; i < ls.length; i++ ) { 978 File theFile = new File(dirFile, ls[i]); 979 String fullName = null; 980 if (resolveSymLinks) { 981 try { 982 fullName = theFile.getCanonicalPath(); 983 } catch (IOException e) { 984 } 985 } 986 if (fullName == null) { 987 fullName = dirName + File.separator + ls[i]; 988 } 989 990 // REMIND: case compare depends on platform 991 if (registeredFontFiles.contains(fullName)) { 992 continue; 993 } 994 995 if (badFonts != null && badFonts.contains(fullName)) { 996 if (debugFonts) { 997 logger.warning("skip bad font " + fullName); 998 } 999 continue; // skip this font file. 1000 } 1001 1002 registeredFontFiles.add(fullName); 1003 1004 if (debugFonts && logger.isLoggable(Level.INFO)) { 1005 String message = "Registering font " + fullName; 1006 String[] natNames = getNativeNames(fullName, null); 1007 if (natNames == null) { 1008 message += " with no native name"; 1009 } else { 1010 message += " with native name(s) " + natNames[0]; 1011 for (int nn = 1; nn < natNames.length; nn++) { 1012 message += ", " + natNames[nn]; 1013 } 1014 } 1015 logger.info(message); 1016 } 1017 fontNames[fontCount] = fullName; 1018 nativeNames[fontCount++] = getNativeNames(fullName, null); 1019 } 1020 FontManager.registerFonts(fontNames, nativeNames, fontCount, 1021 fontFormat, useJavaRasterizer, fontRank, 1022 defer); 1023 return; 1024 } 1025 1026 /* 1027 * A GE may verify whether a font file used in a fontconfiguration 1028 * exists. If it doesn't then either we may substitute the default 1029 * font, or perhaps elide it altogether from the composite font. 1030 * This makes some sense on windows where the font file is only 1031 * likely to be in one place. But on other OSes, eg Linux, the file 1032 * can move around depending. So there we probably don't want to assume 1033 * its missing and so won't add it to this list. 1034 * If this list - missingFontFiles - is non-null then the composite 1035 * font initialisation logic tests to see if a font file is in that 1036 * set. 1037 * Only one thread should be able to add to this set so we don't 1038 * synchronize. 1039 */ 1040 protected void addToMissingFontFileList(String fileName) { 1041 if (missingFontFiles == null) { 1042 missingFontFiles = new HashSet<String>(); 1043 } 1044 missingFontFiles.add(fileName); 1045 } 1046 1047 /** 1048 * Creates this environment's FontConfiguration. 1049 */ 1050 protected abstract FontConfiguration createFontConfiguration(); 1051 1052 public abstract FontConfiguration 1053 createFontConfiguration(boolean preferLocaleFonts, 1054 boolean preferPropFonts); 1055 1056 /* 1057 * This method asks the font configuration API for all platform names 1058 * used as components of composite/logical fonts and iterates over these 1059 * looking up their corresponding file name and registers these fonts. 1060 * It also ensures that the fonts are accessible via platform APIs. 1061 * The composites themselves are then registered. 1062 */ 1063 private void 1064 initCompositeFonts(FontConfiguration fontConfig, 1065 ConcurrentHashMap<String, Font2D> altNameCache) { 1066 1067 int numCoreFonts = fontConfig.getNumberCoreFonts(); 1068 String[] fcFonts = fontConfig.getPlatformFontNames(); 1069 for (int f=0; f<fcFonts.length; f++) { 1070 String platformFontName = fcFonts[f]; 1071 String fontFileName = 1072 getFileNameFromPlatformName(platformFontName); 1073 String[] nativeNames = null; 1074 if (fontFileName == null || fontFileName.equals(platformFontName)){ 1075 /* No file located, so register using the platform name, 1076 * i.e. as a native font. 1077 */ 1078 fontFileName = platformFontName; 1079 } else { 1080 if (f < numCoreFonts) { 1081 /* If platform APIs also need to access the font, add it 1082 * to a set to be registered with the platform too. 1083 * This may be used to add the parent directory to the X11 1084 * font path if its not already there. See the docs for the 1085 * subclass implementation. 1086 * This is now mainly for the benefit of X11-based AWT 1087 * But for historical reasons, 2D initialisation code 1088 * makes these calls. 1089 * If the fontconfiguration file is properly set up 1090 * so that all fonts are mapped to files and all their 1091 * appropriate directories are specified, then this 1092 * method will be low cost as it will return after 1093 * a test that finds a null lookup map. 1094 */ 1095 addFontToPlatformFontPath(platformFontName); 1096 } 1097 nativeNames = getNativeNames(fontFileName, platformFontName); 1098 } 1099 /* Uncomment these two lines to "generate" the XLFD->filename 1100 * mappings needed to speed start-up on Solaris. 1101 * Augment this with the appendedpathname and the mappings 1102 * for native (F3) fonts 1103 */ 1104 //String platName = platformFontName.replaceAll(" ", "_"); 1105 //System.out.println("filename."+platName+"="+fontFileName); 1106 registerFontFile(fontFileName, nativeNames, 1107 Font2D.FONT_CONFIG_RANK, true); 1108 1109 1110 } 1111 /* This registers accumulated paths from the calls to 1112 * addFontToPlatformFontPath(..) and any specified by 1113 * the font configuration. Rather than registering 1114 * the fonts it puts them in a place and form suitable for 1115 * the Toolkit to pick up and use if a toolkit is initialised, 1116 * and if it uses X11 fonts. 1117 */ 1118 registerPlatformFontsUsedByFontConfiguration(); 1119 1120 CompositeFontDescriptor[] compositeFontInfo 1121 = fontConfig.get2DCompositeFontInfo(); 1122 for (int i = 0; i < compositeFontInfo.length; i++) { 1123 CompositeFontDescriptor descriptor = compositeFontInfo[i]; 1124 String[] componentFileNames = descriptor.getComponentFileNames(); 1125 String[] componentFaceNames = descriptor.getComponentFaceNames(); 1126 1127 /* It would be better eventually to handle this in the 1128 * FontConfiguration code which should also remove duplicate slots 1129 */ 1130 if (missingFontFiles != null) { 1131 for (int ii=0; ii<componentFileNames.length; ii++) { 1132 if (missingFontFiles.contains(componentFileNames[ii])) { 1133 componentFileNames[ii] = getDefaultFontFile(); 1134 componentFaceNames[ii] = getDefaultFontFaceName(); 1135 } 1136 } 1137 } 1138 1139 /* FontConfiguration needs to convey how many fonts it has added 1140 * as fallback component fonts which should not affect metrics. 1141 * The core component count will be the number of metrics slots. 1142 * This does not preclude other mechanisms for adding 1143 * fall back component fonts to the composite. 1144 */ 1145 if (altNameCache != null) { 1146 FontManager.registerCompositeFont( 1147 descriptor.getFaceName(), 1148 componentFileNames, componentFaceNames, 1149 descriptor.getCoreComponentCount(), 1150 descriptor.getExclusionRanges(), 1151 descriptor.getExclusionRangeLimits(), 1152 true, 1153 altNameCache); 1154 } else { 1155 FontManager.registerCompositeFont( 1156 descriptor.getFaceName(), 1157 componentFileNames, componentFaceNames, 1158 descriptor.getCoreComponentCount(), 1159 descriptor.getExclusionRanges(), 1160 descriptor.getExclusionRangeLimits(), 1161 true); 1162 } 1163 if (debugFonts) { 1164 logger.info("registered " + descriptor.getFaceName()); 1165 } 1166 } 1167 } 1168 1169 /** 1170 * Notifies graphics environment that the logical font configuration 1171 * uses the given platform font name. The graphics environment may 1172 * use this for platform specific initialization. 1173 */ 1174 protected void addFontToPlatformFontPath(String platformFontName) { 1175 } 1176 1177 protected void registerPlatformFontsUsedByFontConfiguration() { 1178 } 1179 1180 /** 1181 * Determines whether the given font is a logical font. 1182 */ 1183 public static boolean isLogicalFont(Font f) { 1184 return FontConfiguration.isLogicalFontFamilyName(f.getFamily()); 1185 } 1186 1187 /** 1188 * Return the default font configuration. 1189 */ 1190 public FontConfiguration getFontConfiguration() { 1191 return fontConfig; 1192 } 1193 1194 /** 1195 * Return the bounds of a GraphicsDevice, less its screen insets. 1196 * See also java.awt.GraphicsEnvironment.getUsableBounds(); 1197 */ 1198 public static Rectangle getUsableBounds(GraphicsDevice gd) { 1199 GraphicsConfiguration gc = gd.getDefaultConfiguration(); 1200 Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc); 1201 Rectangle usableBounds = gc.getBounds(); 1202 1203 usableBounds.x += insets.left; 1204 usableBounds.y += insets.top; 1205 usableBounds.width -= (insets.left + insets.right); 1206 usableBounds.height -= (insets.top + insets.bottom); 1207 1208 return usableBounds; 1209 } 1210 1211 /** 1212 * This method is provided for internal and exclusive use by Swing. 1213 * This method should no longer be called, instead directly call 1214 * FontManager.fontSupportsDefaultEncoding(Font). 1215 * This method will be removed once Swing is updated to no longer 1216 * call it. 1217 */ 1218 public static boolean fontSupportsDefaultEncoding(Font font) { 1219 return FontManager.fontSupportsDefaultEncoding(font); 1220 } 1221 1222 public static void useAlternateFontforJALocales() { 1223 FontManager.useAlternateFontforJALocales(); 1224 } 1225 1226 /* 1227 * This invocation is not in a privileged block because 1228 * all privileged operations (reading files and properties) 1229 * was conducted on the creation of the GE 1230 */ 1231 public void 1232 createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache, 1233 boolean preferLocale, 1234 boolean preferProportional) { 1235 1236 FontConfiguration fontConfig = 1237 createFontConfiguration(preferLocale, preferProportional); 1238 initCompositeFonts(fontConfig, altNameCache); 1239 } 1240 1241 /* If (as we do on X11) need to set a platform font path, 1242 * then the needed path may be specified by the platform 1243 * specific FontConfiguration class & data file. Such platforms 1244 * (ie X11) need to override this method to retrieve this information 1245 * into suitable data structures. 1246 */ 1247 protected void getPlatformFontPathFromFontConfig() { 1248 } 1249 1250 /** 1251 * From the DisplayChangedListener interface; called 1252 * when the display mode has been changed. 1253 */ 1254 public void displayChanged() { 1255 // notify screens in device array to do display update stuff 1256 for (GraphicsDevice gd : getScreenDevices()) { 1257 if (gd instanceof DisplayChangedListener) { 1258 ((DisplayChangedListener) gd).displayChanged(); 1259 } 1260 } 1261 1262 // notify SunDisplayChanger list (e.g. VolatileSurfaceManagers and 1263 // SurfaceDataProxies) about the display change event 1264 displayChanger.notifyListeners(); 1265 } 1266 1267 /** 1268 * Part of the DisplayChangedListener interface: 1269 * propagate this event to listeners 1270 */ 1271 public void paletteChanged() { 1272 displayChanger.notifyPaletteChanged(); 1273 } 1274 1275 /** 1276 * Returns true when the display is local, false for remote displays. 1277 * 1278 * @return true when the display is local, false for remote displays 1279 */ 1280 public abstract boolean isDisplayLocal(); 1281 1282 /* 1283 * ----DISPLAY CHANGE SUPPORT---- 1284 */ 1285 1286 protected SunDisplayChanger displayChanger = new SunDisplayChanger(); 1287 1288 /** 1289 * Add a DisplayChangeListener to be notified when the display settings 1290 * are changed. 1291 */ 1292 public void addDisplayChangedListener(DisplayChangedListener client) { 1293 displayChanger.add(client); 1294 } 1295 1296 /** 1297 * Remove a DisplayChangeListener from Win32GraphicsEnvironment 1298 */ 1299 public void removeDisplayChangedListener(DisplayChangedListener client) { 1300 displayChanger.remove(client); 1301 } 1302 1303 /* 1304 * ----END DISPLAY CHANGE SUPPORT---- 1305 */ 1306 1307 /** 1308 * Returns true if FlipBufferStrategy with COPIED buffer contents 1309 * is preferred for this peer's GraphicsConfiguration over 1310 * BlitBufferStrategy, false otherwise. 1311 * 1312 * The reason FlipBS could be preferred is that in some configurations 1313 * an accelerated copy to the screen is supported (like Direct3D 9) 1314 * 1315 * @return true if flip strategy should be used, false otherwise 1316 */ 1317 public boolean isFlipStrategyPreferred(ComponentPeer peer) { 1318 return false; 1319 } 1320 }