jdk/src/share/classes/sun/java2d/SunGraphicsEnvironment.java
changeset 2 90ce3da70b43
child 883 c3e81f0acd3d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/java2d/SunGraphicsEnvironment.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1297 @@
+/*
+ * Copyright 1997-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.java2d;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.font.TextAttribute;
+import java.awt.image.BufferedImage;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.text.AttributedCharacterIterator;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import sun.awt.AppContext;
+import sun.awt.DisplayChangedListener;
+import sun.awt.FontConfiguration;
+import sun.awt.SunDisplayChanger;
+import sun.font.CompositeFontDescriptor;
+import sun.font.Font2D;
+import sun.font.FontManager;
+import sun.font.NativeFont;
+
+/**
+ * This is an implementation of a GraphicsEnvironment object for the
+ * default local GraphicsEnvironment.
+ *
+ * @see GraphicsDevice
+ * @see GraphicsConfiguration
+ */
+
+public abstract class SunGraphicsEnvironment extends GraphicsEnvironment
+    implements FontSupport, DisplayChangedListener {
+
+    public static boolean isLinux;
+    public static boolean isSolaris;
+    public static boolean isWindows;
+    public static boolean noType1Font;
+    private static Font defaultFont;
+    private static String defaultFontFileName;
+    private static String defaultFontName;
+    public static final String lucidaFontName = "Lucida Sans Regular";
+    public static final String lucidaFileName = "LucidaSansRegular.ttf";
+    public static boolean debugFonts = false;
+    protected static Logger logger = null;
+    private static ArrayList badFonts;
+    public static String jreLibDirName;
+    public static String jreFontDirName;
+    private static HashSet<String> missingFontFiles = null;
+
+    private FontConfiguration fontConfig;
+
+    /* fontPath is the location of all fonts on the system, excluding the
+     * JRE's own font directory but including any path specified using the
+     * sun.java2d.fontpath property. Together with that property,  it is
+     * initialised by the getPlatformFontPath() method
+     * This call must be followed by a call to registerFontDirs(fontPath)
+     * once any extra debugging path has been appended.
+     */
+    protected String fontPath;
+
+    /* discoveredAllFonts is set to true when all fonts on the font path are
+     * discovered. This usually also implies opening, validating and
+     * registering, but an implementation may be optimized to avold this.
+     * So see also "loadedAllFontFiles"
+     */
+    private boolean discoveredAllFonts = false;
+
+    /* loadedAllFontFiles is set to true when all fonts on the font path are
+     * actually opened, validated and registered. This always implies
+     * discoveredAllFonts is true.
+     */
+    private boolean loadedAllFontFiles = false;
+
+    protected HashSet registeredFontFiles = new HashSet();
+    public static String eudcFontFileName; /* Initialised only on windows */
+
+    private static boolean isOpenJDK;
+    /**
+     * A few things in Java 2D code are different in OpenJDK,
+     * so need a way to tell which implementation this is.
+     * The absence of Lucida Sans Regular is the simplest way for now.
+     */
+    public static boolean isOpenJDK() {
+        return isOpenJDK;
+    }
+
+    static {
+        java.security.AccessController.doPrivileged(
+                                    new java.security.PrivilegedAction() {
+            public Object run() {
+
+                jreLibDirName = System.getProperty("java.home","") +
+                    File.separator + "lib";
+                jreFontDirName = jreLibDirName + File.separator + "fonts";
+                File lucidaFile =
+                    new File(jreFontDirName + File.separator + lucidaFileName);
+                isOpenJDK = !lucidaFile.exists();
+
+                String debugLevel =
+                    System.getProperty("sun.java2d.debugfonts");
+
+                if (debugLevel != null && !debugLevel.equals("false")) {
+                    debugFonts = true;
+                    logger = Logger.getLogger("sun.java2d");
+                    if (debugLevel.equals("warning")) {
+                        logger.setLevel(Level.WARNING);
+                    } else if (debugLevel.equals("severe")) {
+                        logger.setLevel(Level.SEVERE);
+                    }
+                }
+                return null;
+            }
+        });
+    };
+
+    public SunGraphicsEnvironment() {
+        java.security.AccessController.doPrivileged(
+                                    new java.security.PrivilegedAction() {
+            public Object run() {
+                String osName = System.getProperty("os.name");
+                if ("Linux".equals(osName)) {
+                    isLinux = true;
+                } else if ("SunOS".equals(osName)) {
+                    isSolaris = true;
+                } else if ("Windows".equals(osName)) {
+                    isWindows = true;
+                }
+
+                noType1Font = "true".
+                    equals(System.getProperty("sun.java2d.noType1Font"));
+
+                if (isOpenJDK()) {
+                    String[] fontInfo = FontManager.getDefaultPlatformFont();
+                    defaultFontName = fontInfo[0];
+                    defaultFontFileName = fontInfo[1];
+                } else {
+                    defaultFontName = lucidaFontName;
+                    if (useAbsoluteFontFileNames()) {
+                        defaultFontFileName =
+                            jreFontDirName + File.separator + lucidaFileName;
+                    } else {
+                        defaultFontFileName = lucidaFileName;
+                    }
+                }
+
+                File badFontFile =
+                    new File(jreFontDirName + File.separator + "badfonts.txt");
+                if (badFontFile.exists()) {
+                    FileInputStream fis = null;
+                    try {
+                        badFonts = new ArrayList();
+                        fis = new FileInputStream(badFontFile);
+                        InputStreamReader isr = new InputStreamReader(fis);
+                        BufferedReader br = new BufferedReader(isr);
+                        while (true) {
+                            String name = br.readLine();
+                            if (name == null) {
+                                break;
+                            } else {
+                                if (debugFonts) {
+                                    logger.warning("read bad font: " + name);
+                                }
+                                badFonts.add(name);
+                            }
+                        }
+                    } catch (IOException e) {
+                        try {
+                            if (fis != null) {
+                                fis.close();
+                            }
+                        } catch (IOException ioe) {
+                        }
+                    }
+                }
+
+                /* Here we get the fonts in jre/lib/fonts and register them
+                 * so they are always available and preferred over other fonts.
+                 * This needs to be registered before the composite fonts as
+                 * otherwise some native font that corresponds may be found
+                 * as we don't have a way to handle two fonts of the same
+                 * name, so the JRE one must be the first one registered.
+                 * Pass "true" to registerFonts method as on-screen these
+                 * JRE fonts always go through the T2K rasteriser.
+                 */
+                if (isLinux) {
+                    /* Linux font configuration uses these fonts */
+                    registerFontDir(jreFontDirName);
+                }
+                registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK,
+                                   true, false);
+
+                /* Register the JRE fonts so that the native platform can
+                 * access them. This is used only on Windows so that when
+                 * printing the printer driver can access the fonts.
+                 */
+                registerJREFontsWithPlatform(jreFontDirName);
+
+                /* Create the font configuration and get any font path
+                 * that might be specified.
+                 */
+                fontConfig = createFontConfiguration();
+                getPlatformFontPathFromFontConfig();
+
+                String extraFontPath = fontConfig.getExtraFontPath();
+
+                /* In prior releases the debugging font path replaced
+                 * all normally located font directories except for the
+                 * JRE fonts dir. This directory is still always located and
+                 * placed at the head of the path but as an augmentation
+                 * to the previous behaviour the
+                 * changes below allow you to additionally append to
+                 * the font path by starting with append: or prepend by
+                 * starting with a prepend: sign. Eg: to append
+                 * -Dsun.java2d.fontpath=append:/usr/local/myfonts
+                 * and to prepend
+                 * -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp
+                 *
+                 * If there is an appendedfontpath it in the font configuration
+                 * it is used instead of searching the system for dirs.
+                 * The behaviour of append and prepend is then similar
+                 * to the normal case. ie it goes after what
+                 * you prepend and * before what you append. If the
+                 * sun.java2d.fontpath property is used, but it
+                 * neither the append or prepend syntaxes is used then as
+                 * except for the JRE dir the path is replaced and it is
+                 * up to you to make sure that all the right directories
+                 * are located. This is platform and locale-specific so
+                 * its almost impossible to get right, so it should be
+                 * used with caution.
+                 */
+                boolean prependToPath = false;
+                boolean appendToPath = false;
+                String dbgFontPath = System.getProperty("sun.java2d.fontpath");
+
+                if (dbgFontPath != null) {
+                    if (dbgFontPath.startsWith("prepend:")) {
+                        prependToPath = true;
+                        dbgFontPath =
+                            dbgFontPath.substring("prepend:".length());
+                    } else if (dbgFontPath.startsWith("append:")) {
+                        appendToPath = true;
+                        dbgFontPath =
+                            dbgFontPath.substring("append:".length());
+                    }
+                }
+
+                if (debugFonts) {
+                    logger.info("JRE font directory: " + jreFontDirName);
+                    logger.info("Extra font path: " + extraFontPath);
+                    logger.info("Debug font path: " + dbgFontPath);
+                }
+
+                if (dbgFontPath != null) {
+                    /* In debugging mode we register all the paths
+                     * Caution: this is a very expensive call on Solaris:-
+                     */
+                    fontPath = getPlatformFontPath(noType1Font);
+
+                    if (extraFontPath != null) {
+                        fontPath =
+                            extraFontPath + File.pathSeparator + fontPath;
+                    }
+                    if (appendToPath) {
+                        fontPath = fontPath + File.pathSeparator + dbgFontPath;
+                    } else if (prependToPath) {
+                        fontPath = dbgFontPath + File.pathSeparator + fontPath;
+                    } else {
+                        fontPath = dbgFontPath;
+                    }
+                    registerFontDirs(fontPath);
+                } else if (extraFontPath != null) {
+                    /* If the font configuration contains an "appendedfontpath"
+                     * entry, it is interpreted as a set of locations that
+                     * should always be registered.
+                     * It may be additional to locations normally found for
+                     * that place, or it may be locations that need to have
+                     * all their paths registered to locate all the needed
+                     * platform names.
+                     * This is typically when the same .TTF file is referenced
+                     * from multiple font.dir files and all of these must be
+                     * read to find all the native (XLFD) names for the font,
+                     * so that X11 font APIs can be used for as many code
+                     * points as possible.
+                     */
+                    registerFontDirs(extraFontPath);
+                }
+
+                /* On Solaris, we need to register the Japanese TrueType
+                 * directory so that we can find the corresponding bitmap
+                 * fonts. This could be done by listing the directory in
+                 * the font configuration file, but we don't want to
+                 * confuse users with this quirk. There are no bitmap fonts
+                 * for other writing systems that correspond to TrueType
+                 * fonts and have matching XLFDs. We need to register the
+                 * bitmap fonts only in environments where they're on the
+                 * X font path, i.e., in the Japanese locale.
+                 * Note that if the X Toolkit is in use the font path isn't
+                 * set up by JDK, but users of a JA locale should have it
+                 * set up already by their login environment.
+                 */
+                if (isSolaris && Locale.JAPAN.equals(Locale.getDefault())) {
+                    registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT");
+                }
+
+                initCompositeFonts(fontConfig, null);
+
+                /* Establish the default font to be used by SG2D etc */
+                defaultFont = new Font(Font.DIALOG, Font.PLAIN, 12);
+
+                return null;
+            }
+        });
+    }
+
+    protected GraphicsDevice[] screens;
+
+    /**
+     * Returns an array of all of the screen devices.
+     */
+    public synchronized GraphicsDevice[] getScreenDevices() {
+        GraphicsDevice[] ret = screens;
+        if (ret == null) {
+            int num = getNumScreens();
+            ret = new GraphicsDevice[num];
+            for (int i = 0; i < num; i++) {
+                ret[i] = makeScreenDevice(i);
+            }
+            screens = ret;
+        }
+        return ret;
+    }
+
+    protected abstract int getNumScreens();
+    protected abstract GraphicsDevice makeScreenDevice(int screennum);
+
+    /**
+     * Returns the default screen graphics device.
+     */
+    public GraphicsDevice getDefaultScreenDevice() {
+        return getScreenDevices()[0];
+    }
+
+    /**
+     * Returns a Graphics2D object for rendering into the
+     * given BufferedImage.
+     * @throws NullPointerException if BufferedImage argument is null
+     */
+    public Graphics2D createGraphics(BufferedImage img) {
+        if (img == null) {
+            throw new NullPointerException("BufferedImage cannot be null");
+        }
+        SurfaceData sd = SurfaceData.getPrimarySurfaceData(img);
+        return new SunGraphics2D(sd, Color.white, Color.black, defaultFont);
+    }
+
+    /* A call to this method should be followed by a call to
+     * registerFontDirs(..)
+     */
+    protected String getPlatformFontPath(boolean noType1Font) {
+        if (fontPath == null) {
+            fontPath = FontManager.getFontPath(noType1Font);
+        }
+        return fontPath;
+    }
+
+    private String[] platformFontDirs;
+    /**
+     * Get all directories which contain installed fonts.
+     */
+    public String[] getPlatformFontDirs() {
+        if (platformFontDirs == null) {
+            String path = getPlatformFontPath(noType1Font);
+            StringTokenizer parser =
+                new StringTokenizer(path, File.pathSeparator);;
+            ArrayList<String> pathList = new ArrayList<String>();
+            try {
+                while (parser.hasMoreTokens()) {
+                    pathList.add(parser.nextToken());
+                }
+            } catch (NoSuchElementException e) {
+            }
+            platformFontDirs = pathList.toArray(new String[0]);
+        }
+        return platformFontDirs;
+    }
+
+    /**
+     * Whether registerFontFile expects absolute or relative
+     * font file names.
+     */
+    protected boolean useAbsoluteFontFileNames() {
+        return true;
+    }
+
+    /**
+     * Returns file name for default font, either absolute
+     * or relative as needed by registerFontFile.
+     */
+    public String getDefaultFontFile() {
+        return defaultFontFileName;
+    }
+
+    /**
+     * Returns face name for default font, or null if
+     * no face names are used for CompositeFontDescriptors
+     * for this platform.
+     */
+    public String getDefaultFontFaceName() {
+        return defaultFontName;
+    }
+
+    public void loadFonts() {
+        if (discoveredAllFonts) {
+            return;
+        }
+        /* Use lock specific to the font system */
+        synchronized (lucidaFontName) {
+            if (debugFonts) {
+                Thread.dumpStack();
+                logger.info("SunGraphicsEnvironment.loadFonts() called");
+            }
+            FontManager.initialiseDeferredFonts();
+
+            java.security.AccessController.doPrivileged(
+                                    new java.security.PrivilegedAction() {
+                public Object run() {
+                    if (fontPath == null) {
+                        fontPath = getPlatformFontPath(noType1Font);
+                        registerFontDirs(fontPath);
+                    }
+                    if (fontPath != null) {
+                        // this will find all fonts including those already
+                        // registered. But we have checks in place to prevent
+                        // double registration.
+                        if (!FontManager.gotFontsFromPlatform()) {
+                            registerFontsOnPath(fontPath, false,
+                                                Font2D.UNKNOWN_RANK,
+                                                false, true);
+                            loadedAllFontFiles = true;
+                        }
+                    }
+                    FontManager.registerOtherFontFiles(registeredFontFiles);
+                    discoveredAllFonts = true;
+                    return null;
+                }
+            });
+        }
+    }
+
+
+    public void loadFontFiles() {
+        loadFonts();
+        if (loadedAllFontFiles) {
+            return;
+        }
+        /* Use lock specific to the font system */
+        synchronized (lucidaFontName) {
+            if (debugFonts) {
+                Thread.dumpStack();
+                logger.info("loadAllFontFiles() called");
+            }
+            java.security.AccessController.doPrivileged(
+                                    new java.security.PrivilegedAction() {
+                public Object run() {
+                    if (fontPath == null) {
+                        fontPath = getPlatformFontPath(noType1Font);
+                    }
+                    if (fontPath != null) {
+                        // this will find all fonts including those already
+                        // registered. But we have checks in place to prevent
+                        // double registration.
+                        registerFontsOnPath(fontPath, false,
+                                            Font2D.UNKNOWN_RANK,
+                                            false, true);
+                    }
+                    loadedAllFontFiles = true;
+                    return null;
+                }
+            });
+        }
+    }
+
+    /*
+     * This is for use only within getAllFonts().
+     * Fonts listed in the fontconfig files for windows were all
+     * on the "deferred" initialisation list. They were registered
+     * either in the course of the application, or in the call to
+     * loadFonts() within getAllFonts(). The fontconfig file specifies
+     * the names of the fonts using the English names. If there's a
+     * different name in the execution locale, then the platform will
+     * report that, and we will construct the font with both names, and
+     * thereby enumerate it twice. This happens for Japanese fonts listed
+     * in the windows fontconfig, when run in the JA locale. The solution
+     * is to rely (in this case) on the platform's font->file mapping to
+     * determine that this name corresponds to a file we already registered.
+     * This works because
+     * - we know when we get here all deferred fonts are already initialised
+     * - when we register a font file, we register all fonts in it.
+     * - we know the fontconfig fonts are all in the windows registry
+     */
+    private boolean isNameForRegisteredFile(String fontName) {
+        String fileName = FontManager.getFileNameForFontName(fontName);
+        if (fileName == null) {
+            return false;
+        }
+        return registeredFontFiles.contains(fileName);
+    }
+
+    private Font[] allFonts;
+
+    /**
+     * Returns all fonts installed in this environment.
+     */
+    public Font[] getAllInstalledFonts() {
+        if (allFonts == null) {
+            loadFonts();
+            TreeMap fontMapNames = new TreeMap();
+            /* warning: the number of composite fonts could change dynamically
+             * if applications are allowed to create them. "allfonts" could
+             * then be stale.
+             */
+
+            Font2D[] allfonts = FontManager.getRegisteredFonts();
+            for (int i=0; i < allfonts.length; i++) {
+                if (!(allfonts[i] instanceof NativeFont)) {
+                    fontMapNames.put(allfonts[i].getFontName(null),
+                                     allfonts[i]);
+                }
+            }
+
+            String[] platformNames =  FontManager.getFontNamesFromPlatform();
+            if (platformNames != null) {
+                for (int i=0; i<platformNames.length; i++) {
+                    if (!isNameForRegisteredFile(platformNames[i])) {
+                        fontMapNames.put(platformNames[i], null);
+                    }
+                }
+            }
+
+            String[] fontNames = null;
+            if (fontMapNames.size() > 0) {
+                fontNames = new String[fontMapNames.size()];
+                Object [] keyNames = fontMapNames.keySet().toArray();
+                for (int i=0; i < keyNames.length; i++) {
+                    fontNames[i] = (String)keyNames[i];
+                }
+            }
+            Font[] fonts = new Font[fontNames.length];
+            for (int i=0; i < fontNames.length; i++) {
+                fonts[i] = new Font(fontNames[i], Font.PLAIN, 1);
+                Font2D f2d = (Font2D)fontMapNames.get(fontNames[i]);
+                if (f2d  != null) {
+                    FontManager.setFont2D(fonts[i], f2d.handle);
+                }
+            }
+            allFonts = fonts;
+        }
+
+        Font []copyFonts = new Font[allFonts.length];
+        System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length);
+        return copyFonts;
+    }
+
+     /**
+     * Returns all fonts available in this environment.
+     */
+    public Font[] getAllFonts() {
+        Font[] installedFonts = getAllInstalledFonts();
+        Font[] created = FontManager.getCreatedFonts();
+        if (created == null || created.length == 0) {
+            return installedFonts;
+        } else {
+            int newlen = installedFonts.length + created.length;
+            Font [] fonts = java.util.Arrays.copyOf(installedFonts, newlen);
+            System.arraycopy(created, 0, fonts,
+                             installedFonts.length, created.length);
+            return fonts;
+        }
+    }
+
+    /**
+     * Default locale can be changed but we need to know the initial locale
+     * as that is what is used by native code. Changing Java default locale
+     * doesn't affect that.
+     * Returns the locale in use when using native code to communicate
+     * with platform APIs. On windows this is known as the "system" locale,
+     * and it is usually the same as the platform locale, but not always,
+     * so this method also checks an implementation property used only
+     * on windows and uses that if set.
+     */
+    private static Locale systemLocale = null;
+    public static Locale getSystemStartupLocale() {
+        if (systemLocale == null) {
+            systemLocale = (Locale)
+                java.security.AccessController.doPrivileged(
+                                    new java.security.PrivilegedAction() {
+            public Object run() {
+                /* On windows the system locale may be different than the
+                 * user locale. This is an unsupported configuration, but
+                 * in that case we want to return a dummy locale that will
+                 * never cause a match in the usage of this API. This is
+                 * important because Windows documents that the family
+                 * names of fonts are enumerated using the language of
+                 * the system locale. BY returning a dummy locale in that
+                 * case we do not use the platform API which would not
+                 * return us the names we want.
+                 */
+                String fileEncoding = System.getProperty("file.encoding", "");
+                String sysEncoding = System.getProperty("sun.jnu.encoding");
+                if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) {
+                    return Locale.ROOT;
+                }
+
+                String language = System.getProperty("user.language", "en");
+                String country  = System.getProperty("user.country","");
+                String variant  = System.getProperty("user.variant","");
+                return new Locale(language, country, variant);
+            }
+        });
+        }
+        return systemLocale;
+    }
+
+    /* Really we need only the JRE fonts family names, but there's little
+     * overhead in doing this the easy way by adding all the currently
+     * known fonts.
+     */
+    protected void getJREFontFamilyNames(TreeMap<String,String> familyNames,
+                                         Locale requestedLocale) {
+        FontManager.registerDeferredJREFonts(jreFontDirName);
+        Font2D[] physicalfonts = FontManager.getPhysicalFonts();
+        for (int i=0; i < physicalfonts.length; i++) {
+            if (!(physicalfonts[i] instanceof NativeFont)) {
+                String name =
+                    physicalfonts[i].getFamilyName(requestedLocale);
+                familyNames.put(name.toLowerCase(requestedLocale), name);
+            }
+        }
+    }
+
+    private String[] allFamilies; // cache for default locale only
+    private Locale lastDefaultLocale;
+
+    public String[] getInstalledFontFamilyNames(Locale requestedLocale) {
+        if (requestedLocale == null) {
+            requestedLocale = Locale.getDefault();
+        }
+        if (allFamilies != null && lastDefaultLocale != null &&
+            requestedLocale.equals(lastDefaultLocale)) {
+                String[] copyFamilies = new String[allFamilies.length];
+                System.arraycopy(allFamilies, 0, copyFamilies,
+                                 0, allFamilies.length);
+                return copyFamilies;
+        }
+
+        TreeMap<String,String> familyNames = new TreeMap<String,String>();
+        //  these names are always there and aren't localised
+        String str;
+        str = Font.SERIF;         familyNames.put(str.toLowerCase(), str);
+        str = Font.SANS_SERIF;    familyNames.put(str.toLowerCase(), str);
+        str = Font.MONOSPACED;    familyNames.put(str.toLowerCase(), str);
+        str = Font.DIALOG;        familyNames.put(str.toLowerCase(), str);
+        str = Font.DIALOG_INPUT;  familyNames.put(str.toLowerCase(), str);
+
+        /* Platform APIs may be used to get the set of available family
+         * names for the current default locale so long as it is the same
+         * as the start-up system locale, rather than loading all fonts.
+         */
+        if (requestedLocale.equals(getSystemStartupLocale()) &&
+            FontManager.getFamilyNamesFromPlatform(familyNames,
+                                                    requestedLocale)) {
+            /* Augment platform names with JRE font family names */
+            getJREFontFamilyNames(familyNames, requestedLocale);
+        } else {
+            loadFontFiles();
+            Font2D[] physicalfonts = FontManager.getPhysicalFonts();
+            for (int i=0; i < physicalfonts.length; i++) {
+                if (!(physicalfonts[i] instanceof NativeFont)) {
+                    String name =
+                        physicalfonts[i].getFamilyName(requestedLocale);
+                    familyNames.put(name.toLowerCase(requestedLocale), name);
+                }
+            }
+        }
+
+        String[] retval =  new String[familyNames.size()];
+        Object [] keyNames = familyNames.keySet().toArray();
+        for (int i=0; i < keyNames.length; i++) {
+            retval[i] = (String)familyNames.get(keyNames[i]);
+        }
+        if (requestedLocale.equals(Locale.getDefault())) {
+            lastDefaultLocale = requestedLocale;
+            allFamilies = new String[retval.length];
+            System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length);
+        }
+        return retval;
+    }
+
+    public String[] getAvailableFontFamilyNames(Locale requestedLocale) {
+        String[] installed = getInstalledFontFamilyNames(requestedLocale);
+        /* Use a new TreeMap as used in getInstalledFontFamilyNames
+         * and insert all the keys in lower case, so that the sort order
+         * is the same as the installed families. This preserves historical
+         * behaviour and inserts new families in the right place.
+         * It would have been marginally more efficient to directly obtain
+         * the tree map and just insert new entries, but not so much as
+         * to justify the extra internal interface.
+         */
+        TreeMap<String, String> map = FontManager.getCreatedFontFamilyNames();
+        if (map == null || map.size() == 0) {
+            return installed;
+        } else {
+            for (int i=0; i<installed.length; i++) {
+                map.put(installed[i].toLowerCase(requestedLocale),
+                        installed[i]);
+            }
+            String[] retval =  new String[map.size()];
+            Object [] keyNames = map.keySet().toArray();
+            for (int i=0; i < keyNames.length; i++) {
+                retval[i] = (String)map.get(keyNames[i]);
+            }
+            return retval;
+        }
+    }
+
+    public String[] getAvailableFontFamilyNames() {
+        return getAvailableFontFamilyNames(Locale.getDefault());
+    }
+
+    /**
+     * Returns a file name for the physical font represented by this platform
+     * font name. The default implementation tries to obtain the file name
+     * from the font configuration.
+     * Subclasses may override to provide information from other sources.
+     */
+    protected String getFileNameFromPlatformName(String platformFontName) {
+        return fontConfig.getFileNameFromPlatformName(platformFontName);
+    }
+
+    public static class TTFilter implements FilenameFilter {
+        public boolean accept(File dir,String name) {
+            /* all conveniently have the same suffix length */
+            int offset = name.length()-4;
+            if (offset <= 0) { /* must be at least A.ttf */
+                return false;
+            } else {
+                return(name.startsWith(".ttf", offset) ||
+                       name.startsWith(".TTF", offset) ||
+                       name.startsWith(".ttc", offset) ||
+                       name.startsWith(".TTC", offset));
+            }
+        }
+    }
+
+    public static class T1Filter implements FilenameFilter {
+        public boolean accept(File dir,String name) {
+            if (noType1Font) {
+                return false;
+            }
+            /* all conveniently have the same suffix length */
+            int offset = name.length()-4;
+            if (offset <= 0) { /* must be at least A.pfa */
+                return false;
+            } else {
+                return(name.startsWith(".pfa", offset) ||
+                       name.startsWith(".pfb", offset) ||
+                       name.startsWith(".PFA", offset) ||
+                       name.startsWith(".PFB", offset));
+            }
+        }
+    }
+
+     public static class TTorT1Filter implements FilenameFilter {
+        public boolean accept(File dir, String name) {
+
+            /* all conveniently have the same suffix length */
+            int offset = name.length()-4;
+            if (offset <= 0) { /* must be at least A.ttf or A.pfa */
+                return false;
+            } else {
+                boolean isTT =
+                    name.startsWith(".ttf", offset) ||
+                    name.startsWith(".TTF", offset) ||
+                    name.startsWith(".ttc", offset) ||
+                    name.startsWith(".TTC", offset);
+                if (isTT) {
+                    return true;
+                } else if (noType1Font) {
+                    return false;
+                } else {
+                    return(name.startsWith(".pfa", offset) ||
+                           name.startsWith(".pfb", offset) ||
+                           name.startsWith(".PFA", offset) ||
+                           name.startsWith(".PFB", offset));
+                }
+            }
+        }
+    }
+
+    /* No need to keep consing up new instances - reuse a singleton.
+     * The trade-off is that these objects don't get GC'd.
+     */
+    public static final TTFilter ttFilter = new TTFilter();
+    public static final T1Filter t1Filter = new T1Filter();
+
+    /* The majority of the register functions in this class are
+     * registering platform fonts in the JRE's font maps.
+     * The next one is opposite in function as it registers the JRE
+     * fonts as platform fonts. If subsequent to calling this
+     * your implementation enumerates platform fonts in a way that
+     * would return these fonts too you may get duplicates.
+     * This function is primarily used to install the JRE fonts
+     * so that the native platform can access them.
+     * It is intended to be overridden by platform subclasses
+     * Currently minimal use is made of this as generally
+     * Java 2D doesn't need the platform to be able to
+     * use its fonts and platforms which already have matching
+     * fonts registered (possibly even from other different JRE
+     * versions) generally can't be guaranteed to use the
+     * one registered by this JRE version in response to
+     * requests from this JRE.
+     */
+    protected void registerJREFontsWithPlatform(String pathName) {
+        return;
+    }
+
+    /* Called from FontManager - has Solaris specific implementation */
+    public void register1dot0Fonts() {
+        java.security.AccessController.doPrivileged(
+                            new java.security.PrivilegedAction() {
+            public Object run() {
+                String type1Dir = "/usr/openwin/lib/X11/fonts/Type1";
+                registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK,
+                                   false, false);
+                return null;
+            }
+        });
+    }
+
+    protected void registerFontDirs(String pathName) {
+        return;
+    }
+
+    /* Called to register fall back fonts */
+    public void registerFontsInDir(String dirName) {
+        registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false);
+    }
+
+    private void registerFontsInDir(String dirName, boolean useJavaRasterizer,
+                                    int fontRank,
+                                    boolean defer, boolean resolveSymLinks) {
+        File pathFile = new File(dirName);
+        addDirFonts(dirName, pathFile, ttFilter,
+                    FontManager.FONTFORMAT_TRUETYPE, useJavaRasterizer,
+                    fontRank==Font2D.UNKNOWN_RANK ?
+                    Font2D.TTF_RANK : fontRank,
+                    defer, resolveSymLinks);
+        addDirFonts(dirName, pathFile, t1Filter,
+                    FontManager.FONTFORMAT_TYPE1, useJavaRasterizer,
+                    fontRank==Font2D.UNKNOWN_RANK ?
+                    Font2D.TYPE1_RANK : fontRank,
+                    defer, resolveSymLinks);
+    }
+
+    private void registerFontsOnPath(String pathName,
+                                     boolean useJavaRasterizer, int fontRank,
+                                     boolean defer, boolean resolveSymLinks) {
+
+        StringTokenizer parser = new StringTokenizer(pathName,
+                                                     File.pathSeparator);
+        try {
+            while (parser.hasMoreTokens()) {
+                registerFontsInDir(parser.nextToken(),
+                                   useJavaRasterizer, fontRank,
+                                   defer, resolveSymLinks);
+            }
+        } catch (NoSuchElementException e) {
+        }
+    }
+
+    protected void registerFontFile(String fontFileName, String[] nativeNames,
+                                    int fontRank, boolean defer) {
+        // REMIND: case compare depends on platform
+        if (registeredFontFiles.contains(fontFileName)) {
+            return;
+        }
+        int fontFormat;
+        if (ttFilter.accept(null, fontFileName)) {
+            fontFormat = FontManager.FONTFORMAT_TRUETYPE;
+        } else if (t1Filter.accept(null, fontFileName)) {
+            fontFormat = FontManager.FONTFORMAT_TYPE1;
+        } else {
+            fontFormat = FontManager.FONTFORMAT_NATIVE;
+        }
+        registeredFontFiles.add(fontFileName);
+        if (defer) {
+            FontManager.registerDeferredFont(fontFileName,
+                                             fontFileName, nativeNames,
+                                             fontFormat, false, fontRank);
+        } else {
+            FontManager.registerFontFile(fontFileName, nativeNames,
+                                         fontFormat, false, fontRank);
+        }
+    }
+
+    protected void registerFontDir(String path) {
+    }
+
+    protected String[] getNativeNames(String fontFileName,
+                                      String platformName) {
+        return null;
+    }
+
+    /*
+     * helper function for registerFonts
+     */
+    private void addDirFonts(String dirName, File dirFile,
+                             FilenameFilter filter,
+                             int fontFormat, boolean useJavaRasterizer,
+                             int fontRank,
+                             boolean defer, boolean resolveSymLinks) {
+        String[] ls = dirFile.list(filter);
+        if (ls == null || ls.length == 0) {
+            return;
+        }
+        String[] fontNames = new String[ls.length];
+        String[][] nativeNames = new String[ls.length][];
+        int fontCount = 0;
+
+        for (int i=0; i < ls.length; i++ ) {
+            File theFile = new File(dirFile, ls[i]);
+            String fullName = null;
+            if (resolveSymLinks) {
+                try {
+                    fullName = theFile.getCanonicalPath();
+                } catch (IOException e) {
+                }
+            }
+            if (fullName == null) {
+                fullName = dirName + File.separator + ls[i];
+            }
+
+            // REMIND: case compare depends on platform
+            if (registeredFontFiles.contains(fullName)) {
+                continue;
+            }
+
+            if (badFonts != null && badFonts.contains(fullName)) {
+                if (debugFonts) {
+                    logger.warning("skip bad font " + fullName);
+                }
+                continue; // skip this font file.
+            }
+
+            registeredFontFiles.add(fullName);
+
+            if (debugFonts && logger.isLoggable(Level.INFO)) {
+                String message = "Registering font " + fullName;
+                String[] natNames = getNativeNames(fullName, null);
+                if (natNames == null) {
+                    message += " with no native name";
+                } else {
+                    message += " with native name(s) " + natNames[0];
+                    for (int nn = 1; nn < natNames.length; nn++) {
+                        message += ", " + natNames[nn];
+                    }
+                }
+                logger.info(message);
+            }
+            fontNames[fontCount] = fullName;
+            nativeNames[fontCount++] = getNativeNames(fullName, null);
+        }
+        FontManager.registerFonts(fontNames, nativeNames, fontCount,
+                                  fontFormat, useJavaRasterizer, fontRank,
+                                  defer);
+        return;
+    }
+
+    /*
+     * A GE may verify whether a font file used in a fontconfiguration
+     * exists. If it doesn't then either we may substitute the default
+     * font, or perhaps elide it altogether from the composite font.
+     * This makes some sense on windows where the font file is only
+     * likely to be in one place. But on other OSes, eg Linux, the file
+     * can move around depending. So there we probably don't want to assume
+     * its missing and so won't add it to this list.
+     * If this list - missingFontFiles - is non-null then the composite
+     * font initialisation logic tests to see if a font file is in that
+     * set.
+     * Only one thread should be able to add to this set so we don't
+     * synchronize.
+     */
+    protected void addToMissingFontFileList(String fileName) {
+        if (missingFontFiles == null) {
+            missingFontFiles = new HashSet<String>();
+        }
+        missingFontFiles.add(fileName);
+    }
+
+    /**
+     * Creates this environment's FontConfiguration.
+     */
+    protected abstract FontConfiguration createFontConfiguration();
+
+    public abstract FontConfiguration
+        createFontConfiguration(boolean preferLocaleFonts,
+                                boolean preferPropFonts);
+
+    /*
+     * This method asks the font configuration API for all platform names
+     * used as components of composite/logical fonts and iterates over these
+     * looking up their corresponding file name and registers these fonts.
+     * It also ensures that the fonts are accessible via platform APIs.
+     * The composites themselves are then registered.
+     */
+    private void
+        initCompositeFonts(FontConfiguration fontConfig,
+                           ConcurrentHashMap<String, Font2D>  altNameCache) {
+
+        int numCoreFonts = fontConfig.getNumberCoreFonts();
+        String[] fcFonts = fontConfig.getPlatformFontNames();
+        for (int f=0; f<fcFonts.length; f++) {
+            String platformFontName = fcFonts[f];
+            String fontFileName =
+                getFileNameFromPlatformName(platformFontName);
+            String[] nativeNames = null;
+            if (fontFileName == null) {
+                /* No file located, so register using the platform name,
+                 * i.e. as a native font.
+                 */
+                fontFileName = platformFontName;
+            } else {
+                if (f < numCoreFonts) {
+                    /* If platform APIs also need to access the font, add it
+                     * to a set to be registered with the platform too.
+                     * This may be used to add the parent directory to the X11
+                     * font path if its not already there. See the docs for the
+                     * subclass implementation.
+                     * This is now mainly for the benefit of X11-based AWT
+                     * But for historical reasons, 2D initialisation code
+                     * makes these calls.
+                     * If the fontconfiguration file is properly set up
+                     * so that all fonts are mapped to files and all their
+                     * appropriate directories are specified, then this
+                     * method will be low cost as it will return after
+                     * a test that finds a null lookup map.
+                     */
+                    addFontToPlatformFontPath(platformFontName);
+                }
+                nativeNames = getNativeNames(fontFileName, platformFontName);
+            }
+            /* Uncomment these two lines to "generate" the XLFD->filename
+             * mappings needed to speed start-up on Solaris.
+             * Augment this with the appendedpathname and the mappings
+             * for native (F3) fonts
+             */
+            //String platName = platformFontName.replaceAll(" ", "_");
+            //System.out.println("filename."+platName+"="+fontFileName);
+            registerFontFile(fontFileName, nativeNames,
+                             Font2D.FONT_CONFIG_RANK, true);
+
+
+        }
+        /* This registers accumulated paths from the calls to
+         * addFontToPlatformFontPath(..) and any specified by
+         * the font configuration. Rather than registering
+         * the fonts it puts them in a place and form suitable for
+         * the Toolkit to pick up and use if a toolkit is initialised,
+         * and if it uses X11 fonts.
+         */
+        registerPlatformFontsUsedByFontConfiguration();
+
+        CompositeFontDescriptor[] compositeFontInfo
+                = fontConfig.get2DCompositeFontInfo();
+        for (int i = 0; i < compositeFontInfo.length; i++) {
+            CompositeFontDescriptor descriptor = compositeFontInfo[i];
+            String[] componentFileNames = descriptor.getComponentFileNames();
+            String[] componentFaceNames = descriptor.getComponentFaceNames();
+
+            /* It would be better eventually to handle this in the
+             * FontConfiguration code which should also remove duplicate slots
+             */
+            if (missingFontFiles != null) {
+                for (int ii=0; ii<componentFileNames.length; ii++) {
+                    if (missingFontFiles.contains(componentFileNames[ii])) {
+                        componentFileNames[ii] = getDefaultFontFile();
+                        componentFaceNames[ii] = getDefaultFontFaceName();
+                    }
+                }
+            }
+
+            /* FontConfiguration needs to convey how many fonts it has added
+             * as fallback component fonts which should not affect metrics.
+             * The core component count will be the number of metrics slots.
+             * This does not preclude other mechanisms for adding
+             * fall back component fonts to the composite.
+             */
+            if (altNameCache != null) {
+                FontManager.registerCompositeFont(
+                    descriptor.getFaceName(),
+                    componentFileNames, componentFaceNames,
+                    descriptor.getCoreComponentCount(),
+                    descriptor.getExclusionRanges(),
+                    descriptor.getExclusionRangeLimits(),
+                    true,
+                    altNameCache);
+            } else {
+                FontManager.registerCompositeFont(
+                    descriptor.getFaceName(),
+                    componentFileNames, componentFaceNames,
+                    descriptor.getCoreComponentCount(),
+                    descriptor.getExclusionRanges(),
+                    descriptor.getExclusionRangeLimits(),
+                    true);
+            }
+            if (debugFonts) {
+                logger.info("registered " + descriptor.getFaceName());
+            }
+        }
+    }
+
+    /**
+     * Notifies graphics environment that the logical font configuration
+     * uses the given platform font name. The graphics environment may
+     * use this for platform specific initialization.
+     */
+    protected void addFontToPlatformFontPath(String platformFontName) {
+    }
+
+    protected void registerPlatformFontsUsedByFontConfiguration() {
+    }
+
+    /**
+     * Determines whether the given font is a logical font.
+     */
+    public static boolean isLogicalFont(Font f) {
+        return FontConfiguration.isLogicalFontFamilyName(f.getFamily());
+    }
+
+    /**
+     * Return the default font configuration.
+     */
+    public FontConfiguration getFontConfiguration() {
+        return fontConfig;
+    }
+
+    /**
+     * Return the bounds of a GraphicsDevice, less its screen insets.
+     * See also java.awt.GraphicsEnvironment.getUsableBounds();
+     */
+    public static Rectangle getUsableBounds(GraphicsDevice gd) {
+        GraphicsConfiguration gc = gd.getDefaultConfiguration();
+        Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
+        Rectangle usableBounds = gc.getBounds();
+
+        usableBounds.x += insets.left;
+        usableBounds.y += insets.top;
+        usableBounds.width -= (insets.left + insets.right);
+        usableBounds.height -= (insets.top + insets.bottom);
+
+        return usableBounds;
+    }
+
+    /**
+     * This method is provided for internal and exclusive use by Swing.
+     * This method should no longer be called, instead directly call
+     * FontManager.fontSupportsDefaultEncoding(Font).
+     * This method will be removed once Swing is updated to no longer
+     * call it.
+     */
+    public static boolean fontSupportsDefaultEncoding(Font font) {
+        return FontManager.fontSupportsDefaultEncoding(font);
+    }
+
+    public static void useAlternateFontforJALocales() {
+        FontManager.useAlternateFontforJALocales();
+    }
+
+    /*
+     * This invocation is not in a privileged block because
+     * all privileged operations (reading files and properties)
+     * was conducted on the creation of the GE
+     */
+    public void
+        createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache,
+                             boolean preferLocale,
+                             boolean preferProportional) {
+
+        FontConfiguration fontConfig =
+            createFontConfiguration(preferLocale, preferProportional);
+        initCompositeFonts(fontConfig, altNameCache);
+    }
+
+    /* If (as we do on X11) need to set a platform font path,
+     * then the needed path may be specified by the platform
+     * specific FontConfiguration class & data file. Such platforms
+     * (ie X11) need to override this method to retrieve this information
+     * into suitable data structures.
+     */
+    protected void getPlatformFontPathFromFontConfig() {
+    }
+
+    /**
+     * From the DisplayChangedListener interface; called
+     * when the display mode has been changed.
+     */
+    public void displayChanged() {
+        // notify screens in device array to do display update stuff
+        for (GraphicsDevice gd : getScreenDevices()) {
+            if (gd instanceof DisplayChangedListener) {
+                ((DisplayChangedListener) gd).displayChanged();
+            }
+        }
+
+        // notify SunDisplayChanger list (e.g. VolatileSurfaceManagers and
+        // SurfaceDataProxies) about the display change event
+        displayChanger.notifyListeners();
+    }
+
+    /**
+     * Part of the DisplayChangedListener interface:
+     * propagate this event to listeners
+     */
+    public void paletteChanged() {
+        displayChanger.notifyPaletteChanged();
+    }
+
+    /*
+     * ----DISPLAY CHANGE SUPPORT----
+     */
+
+    protected SunDisplayChanger displayChanger = new SunDisplayChanger();
+
+    /**
+     * Add a DisplayChangeListener to be notified when the display settings
+     * are changed.
+     */
+    public void addDisplayChangedListener(DisplayChangedListener client) {
+        displayChanger.add(client);
+    }
+
+    /**
+     * Remove a DisplayChangeListener from Win32GraphicsEnvironment
+     */
+    public void removeDisplayChangedListener(DisplayChangedListener client) {
+        displayChanger.remove(client);
+    }
+
+    /*
+     * ----END DISPLAY CHANGE SUPPORT----
+     */
+}