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 }