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.awt;
  27 
  28 import java.awt.GraphicsDevice;
  29 import java.awt.Point;
  30 import java.awt.Rectangle;
  31 import java.io.BufferedReader;
  32 import java.io.File;
  33 import java.io.FileReader;
  34 import java.io.FileNotFoundException;
  35 import java.io.InputStream;
  36 import java.io.IOException;
  37 import java.io.StreamTokenizer;
  38 import java.net.InetAddress;
  39 import java.net.NetworkInterface;
  40 import java.net.SocketException;
  41 import java.net.UnknownHostException;
  42 
  43 import java.util.*;
  44 import java.util.logging.*;
  45 
  46 import sun.awt.motif.MFontConfiguration;
  47 import sun.font.FcFontConfiguration;
  48 import sun.font.Font2D;
  49 import sun.font.FontManager;
  50 import sun.font.NativeFont;
  51 import sun.java2d.SunGraphicsEnvironment;
  52 import sun.java2d.SurfaceManagerFactory;
  53 import sun.java2d.UnixSurfaceManagerFactory;
  54 
  55 /**
  56  * This is an implementation of a GraphicsEnvironment object for the
  57  * default local GraphicsEnvironment used by the Java Runtime Environment
  58  * for X11 environments.
  59  *
  60  * @see GraphicsDevice
  61  * @see GraphicsConfiguration
  62  */
  63 public class X11GraphicsEnvironment
  64     extends SunGraphicsEnvironment
  65 {
  66     private static final Logger log = Logger.getLogger("sun.awt.X11GraphicsEnvironment");
  67     private static final Logger screenLog = Logger.getLogger("sun.awt.screen.X11GraphicsEnvironment");
  68 
  69     private static Boolean xinerState;
  70 
  71     /*
  72      * This is the set of font directories needed to be on the X font path
  73      * to enable AWT heavyweights to find all of the font configuration fonts.
  74      * It is populated by :
  75      * - awtfontpath entries in the fontconfig.properties
  76      * - parent directories of "core" fonts used in the fontconfig.properties
  77      * - looking up font dirs in the xFontDirsMap where the key is a fontID
  78      *   (cut down version of the XLFD read from the font configuration file).
  79      * This set is nulled out after use to free heap space.
  80      */
  81     private static HashSet<String> fontConfigDirs = null;
  82 
  83    /*
  84     * fontNameMap is a map from a fontID (which is a substring of an XLFD like
  85     * "-monotype-arial-bold-r-normal-iso8859-7")
  86     * to font file path like
  87     * /usr/openwin/lib/locale/iso_8859_7/X11/fonts/TrueType/ArialBoldItalic.ttf
  88     * It's used in a couple of methods like
  89     * getFileNameFomPlatformName(..) to help locate the font file.
  90     * We use this substring of a full XLFD because the font configuration files
  91     * define the XLFDs in a way that's easier to make into a request.
  92     * E.g., the -0-0-0-0-p-0- reported by X is -*-%d-*-*-p-*- in the font
  93     * configuration files. We need to remove that part for comparisons.
  94     */
  95     private static Map fontNameMap = new HashMap();
  96 
  97     /* xFontDirsMap is also a map from a font ID to a font filepath.
  98      * The difference from fontNameMap is just that it does not have
  99      * resolved symbolic links. Normally this is not interesting except
 100      * that we need to know the directory in which a font was found to
 101      * add it to the X font server path, since although the files may
 102      * be linked, the fonts.dir is different and specific to the encoding
 103      * handled by that directory. This map is nulled out after use to free
 104      * heap space. If the optimal path is taken, such that all fonts in
 105      * font configuration files are referenced by filename, then the font
 106      * dir can be directly derived as its parent directory.
 107      * If a font is used by two XLFDs, each corresponding to a different
 108      * X11 font directory, then precautions must be taken to include both
 109      * directories.
 110      */
 111      private static Map xFontDirsMap;
 112 
 113     /*
 114      * xlfdMap is a map from a platform path like
 115      * /usr/openwin/lib/locale/ja/X11/fonts/TT/HG-GothicB.ttf to an XLFD like
 116      * "-ricoh-hg gothic b-medium-r-normal--0-0-0-0-m-0-jisx0201.1976-0"
 117      * Because there may be multiple native names, because the font is used
 118      * to support multiple X encodings for example, the value of an entry in
 119      * this map is always a vector where we store all the native names.
 120      * For fonts which we don't understand the key isn't a pathname, its
 121      * the full XLFD string like :-
 122      * "-ricoh-hg gothic b-medium-r-normal--0-0-0-0-m-0-jisx0201.1976-0"
 123      */
 124      private static Map xlfdMap = new HashMap();
 125 
 126     /*
 127      * Used to eliminate redundant work. When a font directory is
 128      * registered it added to this list. Subsequent registrations for the
 129      * same directory can then be skipped by checking this Map.
 130      * Access to this map is not synchronised here since creation
 131      * of the singleton GE instance is already synchronised and that is
 132      * the only code path that accesses this map.
 133      */
 134      private static HashMap registeredDirs = new HashMap();
 135 
 136     /* Array of directories to be added to the X11 font path.
 137      * Used by static method called from Toolkits which use X11 fonts.
 138      * Specifically this means MToolkit
 139      */
 140     private static String[] fontdirs = null;
 141 
 142     static {
 143         java.security.AccessController.doPrivileged(
 144                           new java.security.PrivilegedAction() {
 145             public Object run() {
 146                 System.loadLibrary("awt");
 147 
 148                 /*
 149                  * Note: The MToolkit object depends on the static initializer
 150                  * of X11GraphicsEnvironment to initialize the connection to
 151                  * the X11 server.
 152                  */
 153                 if (!isHeadless()) {
 154                     // first check the OGL system property
 155                     boolean glxRequested = false;
 156                     String prop = System.getProperty("sun.java2d.opengl");
 157                     if (prop != null) {
 158                         if (prop.equals("true") || prop.equals("t")) {
 159                             glxRequested = true;
 160                         } else if (prop.equals("True") || prop.equals("T")) {
 161                             glxRequested = true;
 162                             glxVerbose = true;
 163                         }
 164                     }
 165 
 166                     // initialize the X11 display connection
 167                     initDisplay(glxRequested);
 168 
 169                     // only attempt to initialize GLX if it was requested
 170                     if (glxRequested) {
 171                         glxAvailable = initGLX();
 172                         if (glxVerbose && !glxAvailable) {
 173                             System.out.println(
 174                                 "Could not enable OpenGL " +
 175                                 "pipeline (GLX 1.3 not available)");
 176                         }
 177                     }
 178                 }
 179 
 180                 return null;
 181             }
 182          });
 183 
 184         // Install the correct surface manager factory.
 185         SurfaceManagerFactory.setInstance(new UnixSurfaceManagerFactory());
 186 
 187     }
 188 
 189     private static boolean glxAvailable;
 190     private static boolean glxVerbose;
 191 
 192     private static native boolean initGLX();
 193 
 194     public static boolean isGLXAvailable() {
 195         return glxAvailable;
 196     }
 197 
 198     public static boolean isGLXVerbose() {
 199         return glxVerbose;
 200     }
 201 
 202     /**
 203      * Checks if Shared Memory extension can be used.
 204      * Returns:
 205      *   -1 if server doesn't support MITShm
 206      *    1 if server supports it and it can be used
 207      *    0 otherwise
 208      */
 209     private static native int checkShmExt();
 210 
 211     private static  native String getDisplayString();
 212     private Boolean isDisplayLocal;
 213 
 214     /**
 215      * This should only be called from the static initializer, so no need for
 216      * the synchronized keyword.
 217      */
 218     private static native void initDisplay(boolean glxRequested);
 219 
 220     public X11GraphicsEnvironment() {
 221     }
 222 
 223     protected native int getNumScreens();
 224 
 225     protected GraphicsDevice makeScreenDevice(int screennum) {
 226         return new X11GraphicsDevice(screennum);
 227     }
 228 
 229     protected native int getDefaultScreenNum();
 230     /**
 231      * Returns the default screen graphics device.
 232      */
 233     public GraphicsDevice getDefaultScreenDevice() {
 234         return getScreenDevices()[getDefaultScreenNum()];
 235     }
 236 
 237     @Override
 238     public boolean isDisplayLocal() {
 239         if (isDisplayLocal == null) {
 240             SunToolkit.awtLock();
 241             try {
 242                 if (isDisplayLocal == null) {
 243                     isDisplayLocal = Boolean.valueOf(_isDisplayLocal());
 244                 }
 245             } finally {
 246                 SunToolkit.awtUnlock();
 247             }
 248         }
 249         return isDisplayLocal.booleanValue();
 250     }
 251 
 252     private static boolean _isDisplayLocal() {
 253         if (isHeadless()) {
 254             return true;
 255         }
 256 
 257         String isRemote = (String)java.security.AccessController.doPrivileged(
 258             new sun.security.action.GetPropertyAction("sun.java2d.remote"));
 259         if (isRemote != null) {
 260             return isRemote.equals("false");
 261         }
 262 
 263         int shm = checkShmExt();
 264         if (shm != -1) {
 265             return (shm == 1);
 266         }
 267 
 268         // If XServer doesn't support ShMem extension,
 269         // try the other way
 270 
 271         String display = getDisplayString();
 272         int ind = display.indexOf(':');
 273         final String hostName = display.substring(0, ind);
 274         if (ind <= 0) {
 275             // ':0' case
 276             return true;
 277         }
 278 
 279         Boolean result = (Boolean)java.security.AccessController.doPrivileged(
 280             new java.security.PrivilegedAction() {
 281             public Object run() {
 282                 InetAddress remAddr[] = null;
 283                 Enumeration locals = null;
 284                 Enumeration interfaces = null;
 285                 try {
 286                     interfaces = NetworkInterface.getNetworkInterfaces();
 287                     remAddr = InetAddress.getAllByName(hostName);
 288                     if (remAddr == null) {
 289                         return Boolean.FALSE;
 290                     }
 291                 } catch (UnknownHostException e) {
 292                     System.err.println("Unknown host: " + hostName);
 293                     return Boolean.FALSE;
 294                 } catch (SocketException e1) {
 295                     System.err.println(e1.getMessage());
 296                     return Boolean.FALSE;
 297                 }
 298 
 299                 for (; interfaces.hasMoreElements();) {
 300                     locals = ((NetworkInterface)interfaces.nextElement()).getInetAddresses();
 301                     for (; locals.hasMoreElements();) {
 302                         for (int i = 0; i < remAddr.length; i++) {
 303                             if (locals.nextElement().equals(remAddr[i])) {
 304                                 return Boolean.TRUE;
 305                             }
 306                         }
 307                     }
 308                 }
 309                 return Boolean.FALSE;
 310             }});
 311         return result.booleanValue();
 312     }
 313 
 314     /* These maps are used on Linux where we reference the Lucida oblique
 315      * fonts in fontconfig files even though they aren't in the standard
 316      * font directory. This explicitly remaps the XLFDs for these to the
 317      * correct base font. This is needed to prevent composite fonts from
 318      * defaulting to the Lucida Sans which is a bad substitute for the
 319      * monospaced Lucida Sans Typewriter. Also these maps prevent the
 320      * JRE from doing wasted work at start up.
 321      */
 322     HashMap<String, String> oblmap = null;
 323 
 324     private String getObliqueLucidaFontID(String fontID) {
 325         if (fontID.startsWith("-lucidasans-medium-i-normal") ||
 326             fontID.startsWith("-lucidasans-bold-i-normal") ||
 327             fontID.startsWith("-lucidatypewriter-medium-i-normal") ||
 328             fontID.startsWith("-lucidatypewriter-bold-i-normal")) {
 329             return fontID.substring(0, fontID.indexOf("-i-"));
 330         } else {
 331             return null;
 332         }
 333     }
 334 
 335    private void initObliqueLucidaFontMap() {
 336        oblmap = new HashMap<String, String>();
 337        oblmap.put("-lucidasans-medium",
 338                   jreLibDirName+"/fonts/LucidaSansRegular.ttf");
 339        oblmap.put("-lucidasans-bold",
 340                   jreLibDirName+"/fonts/LucidaSansDemiBold.ttf");
 341        oblmap.put("-lucidatypewriter-medium",
 342                   jreLibDirName+"/fonts/LucidaTypewriterRegular.ttf");
 343        oblmap.put("-lucidatypewriter-bold",
 344                   jreLibDirName+"/fonts/LucidaTypewriterBold.ttf");
 345    }
 346 
 347     /**
 348      * Takes family name property in the following format:
 349      * "-linotype-helvetica-medium-r-normal-sans-*-%d-*-*-p-*-iso8859-1"
 350      * and returns the name of the corresponding physical font.
 351      * This code is used to resolve font configuration fonts, and expects
 352      * only to get called for these fonts.
 353      */
 354     public String getFileNameFromPlatformName(String platName) {
 355 
 356         /* If the FontConfig file doesn't use xlfds, or its
 357          * FcFontConfiguration, this may be already a file name.
 358          */
 359         if (platName.startsWith("/")) {
 360             return platName;
 361         }
 362 
 363         String fileName = null;
 364         String fontID = specificFontIDForName(platName);
 365 
 366         /* If the font filename has been explicitly assigned in the
 367          * font configuration file, use it. This avoids accessing
 368          * the wrong fonts on Linux, where different fonts (some
 369          * of which may not be usable by 2D) may share the same
 370          * specific font ID. It may also speed up the lookup.
 371          */
 372         fileName = super.getFileNameFromPlatformName(platName);
 373         if (fileName != null) {
 374             if (isHeadless() && fileName.startsWith("-")) {
 375                 /* if it's headless, no xlfd should be used */
 376                     return null;
 377             }
 378             if (fileName.startsWith("/")) {
 379                 /* If a path is assigned in the font configuration file,
 380                  * it is required that the config file also specify using the
 381                  * new awtfontpath key the X11 font directories
 382                  * which must be added to the X11 font path to support
 383                  * AWT access to that font. For that reason we no longer
 384                  * have code here to add the parent directory to the list
 385                  * of font config dirs, since the parent directory may not
 386                  * be sufficient if fonts are symbolically linked to a
 387                  * different directory.
 388                  *
 389                  * Add this XLFD (platform name) to the list of known
 390                  * ones for this file.
 391                  */
 392                 Vector xVal = (Vector) xlfdMap.get(fileName);
 393                 if (xVal == null) {
 394                     /* Try to be robust on Linux distros which move fonts
 395                      * around by verifying that the fileName represents a
 396                      * file that exists.  If it doesn't, set it to null
 397                      * to trigger a search.
 398                      */
 399                     if (getFontConfiguration().needToSearchForFile(fileName)) {
 400                         fileName = null;
 401                     }
 402                     if (fileName != null) {
 403                         xVal = new Vector();
 404                         xVal.add(platName);
 405                         xlfdMap.put(fileName, xVal);
 406                     }
 407                 } else {
 408                     if (!xVal.contains(platName)) {
 409                         xVal.add(platName);
 410                     }
 411                 }
 412             }
 413             if (fileName != null) {
 414                 fontNameMap.put(fontID, fileName);
 415                 return fileName;
 416             }
 417         }
 418 
 419         if (fontID != null) {
 420             fileName = (String)fontNameMap.get(fontID);
 421             /* On Linux check for the Lucida Oblique fonts */
 422             if (fileName == null && isLinux && !isOpenJDK()) {
 423                 if (oblmap == null) {
 424                     initObliqueLucidaFontMap();
 425                 }
 426                 String oblkey = getObliqueLucidaFontID(fontID);
 427                 if (oblkey != null) {
 428                     fileName = oblmap.get(oblkey);
 429                 }
 430             }
 431             if (fontPath == null &&
 432                 (fileName == null || !fileName.startsWith("/"))) {
 433                 if (debugFonts) {
 434                     logger.warning("** Registering all font paths because " +
 435                                    "can't find file for " + platName);
 436                 }
 437                 fontPath = getPlatformFontPath(noType1Font);
 438                 registerFontDirs(fontPath);
 439                 if (debugFonts) {
 440                     logger.warning("** Finished registering all font paths");
 441                 }
 442                 fileName = (String)fontNameMap.get(fontID);
 443             }
 444             if (fileName == null && !isHeadless()) {
 445                 /* Query X11 directly to see if this font is available
 446                  * as a native font.
 447                  */
 448                 fileName = getX11FontName(platName);
 449             }
 450             if (fileName == null) {
 451                 fontID = switchFontIDForName(platName);
 452                 fileName = (String)fontNameMap.get(fontID);
 453             }
 454             if (fileName != null) {
 455                 fontNameMap.put(fontID, fileName);
 456             }
 457         }
 458         return fileName;
 459     }
 460 
 461     private static String getX11FontName(String platName) {
 462         String xlfd = platName.replaceAll("%d", "*");
 463         if (NativeFont.fontExists(xlfd)) {
 464             return xlfd;
 465         } else {
 466             return null;
 467         }
 468     }
 469 
 470     /**
 471      * Returns the face name for the given XLFD.
 472      */
 473     public String getFileNameFromXLFD(String name) {
 474         String fileName = null;
 475         String fontID = specificFontIDForName(name);
 476         if (fontID != null) {
 477             fileName = (String)fontNameMap.get(fontID);
 478             if (fileName == null) {
 479                 fontID = switchFontIDForName(name);
 480                 fileName = (String)fontNameMap.get(fontID);
 481             }
 482             if (fileName == null) {
 483                 fileName = getDefaultFontFile();
 484             }
 485         }
 486         return fileName;
 487     }
 488 
 489     // constants identifying XLFD and font ID fields
 490     private static final int FOUNDRY_FIELD = 1;
 491     private static final int FAMILY_NAME_FIELD = 2;
 492     private static final int WEIGHT_NAME_FIELD = 3;
 493     private static final int SLANT_FIELD = 4;
 494     private static final int SETWIDTH_NAME_FIELD = 5;
 495     private static final int ADD_STYLE_NAME_FIELD = 6;
 496     private static final int PIXEL_SIZE_FIELD = 7;
 497     private static final int POINT_SIZE_FIELD = 8;
 498     private static final int RESOLUTION_X_FIELD = 9;
 499     private static final int RESOLUTION_Y_FIELD = 10;
 500     private static final int SPACING_FIELD = 11;
 501     private static final int AVERAGE_WIDTH_FIELD = 12;
 502     private static final int CHARSET_REGISTRY_FIELD = 13;
 503     private static final int CHARSET_ENCODING_FIELD = 14;
 504 
 505     private String switchFontIDForName(String name) {
 506 
 507         int[] hPos = new int[14];
 508         int hyphenCnt = 1;
 509         int pos = 1;
 510 
 511         while (pos != -1 && hyphenCnt < 14) {
 512             pos = name.indexOf('-', pos);
 513             if (pos != -1) {
 514                 hPos[hyphenCnt++] = pos;
 515                     pos++;
 516             }
 517         }
 518 
 519         if (hyphenCnt != 14) {
 520             if (debugFonts) {
 521                 logger.severe("Font Configuration Font ID is malformed:" + name);
 522             }
 523             return name; // what else can we do?
 524         }
 525 
 526         String slant = name.substring(hPos[SLANT_FIELD-1]+1,
 527                                            hPos[SLANT_FIELD]);
 528         String family = name.substring(hPos[FAMILY_NAME_FIELD-1]+1,
 529                                            hPos[FAMILY_NAME_FIELD]);
 530         String registry = name.substring(hPos[CHARSET_REGISTRY_FIELD-1]+1,
 531                                            hPos[CHARSET_REGISTRY_FIELD]);
 532         String encoding = name.substring(hPos[CHARSET_ENCODING_FIELD-1]+1);
 533 
 534         if (slant.equals("i")) {
 535             slant = "o";
 536         } else if (slant.equals("o")) {
 537             slant = "i";
 538         }
 539         // workaround for #4471000
 540         if (family.equals("itc zapfdingbats")
 541             && registry.equals("sun")
 542             && encoding.equals("fontspecific")){
 543             registry = "adobe";
 544         }
 545         StringBuffer sb =
 546             new StringBuffer(name.substring(hPos[FAMILY_NAME_FIELD-1],
 547                                             hPos[SLANT_FIELD-1]+1));
 548         sb.append(slant);
 549         sb.append(name.substring(hPos[SLANT_FIELD],
 550                                  hPos[SETWIDTH_NAME_FIELD]+1));
 551         sb.append(registry);
 552         sb.append(name.substring(hPos[CHARSET_ENCODING_FIELD-1]));
 553         String retval = sb.toString().toLowerCase (Locale.ENGLISH);
 554         return retval;
 555     }
 556 
 557 
 558     private String specificFontIDForName(String name) {
 559 
 560         int[] hPos = new int[14];
 561         int hyphenCnt = 1;
 562         int pos = 1;
 563 
 564         while (pos != -1 && hyphenCnt < 14) {
 565             pos = name.indexOf('-', pos);
 566             if (pos != -1) {
 567                 hPos[hyphenCnt++] = pos;
 568                     pos++;
 569             }
 570         }
 571 
 572         if (hyphenCnt != 14) {
 573             if (debugFonts) {
 574                 logger.severe("Font Configuration Font ID is malformed:" + name);
 575             }
 576             return name; // what else can we do?
 577         }
 578 
 579         StringBuffer sb =
 580             new StringBuffer(name.substring(hPos[FAMILY_NAME_FIELD-1],
 581                                             hPos[SETWIDTH_NAME_FIELD]));
 582         sb.append(name.substring(hPos[CHARSET_REGISTRY_FIELD-1]));
 583         String retval = sb.toString().toLowerCase (Locale.ENGLISH);
 584         return retval;
 585     }
 586 
 587     protected String[] getNativeNames(String fontFileName,
 588                                       String platformName) {
 589         Vector nativeNames;
 590         if ((nativeNames=(Vector)xlfdMap.get(fontFileName))==null) {
 591             if (platformName == null) {
 592                 return null;
 593             } else {
 594                 /* back-stop so that at least the name used in the
 595                  * font configuration file is known as a native name
 596                  */
 597                 String []natNames = new String[1];
 598                 natNames[0] = platformName;
 599                 return natNames;
 600             }
 601         } else {
 602             int len = nativeNames.size();
 603             return (String[])nativeNames.toArray(new String[len]);
 604         }
 605     }
 606 
 607 
 608     // An X font spec (xlfd) includes an encoding. The same TrueType font file
 609     // may be referenced from different X font directories in font.dir files
 610     // to support use in multiple encodings by X apps.
 611     // So for the purposes of font configuration logical fonts where AWT
 612     // heavyweights need to access the font via X APIs we need to ensure that
 613     // the directory for precisely the encodings needed by this are added to
 614     // the x font path. This requires that we note the platform names
 615     // specified in font configuration files and use that to identify the
 616     // X font directory that contains a font.dir file for that platform name
 617     // and add it to the X font path (if display is local)
 618     // Here we make use of an already built map of xlfds to font locations
 619     // to add the font location to the set of those required to build the
 620     // x font path needed by AWT.
 621     // These are added to the x font path later.
 622     // All this is necessary because on Solaris the font.dir directories
 623     // may contain not real font files, but symbolic links to the actual
 624     // location but that location is not suitable for the x font path, since
 625     // it probably doesn't have a font.dir at all and certainly not one
 626     // with the required encodings
 627     // If the fontconfiguration file is properly set up so that all fonts
 628     // are mapped to files then we will never trigger initialising
 629     // xFontDirsMap (it will be null). In this case the awtfontpath entries
 630     // must specify all the X11 directories needed by AWT.
 631     protected void addFontToPlatformFontPath(String platformName) {
 632         if (xFontDirsMap != null) {
 633             String fontID = specificFontIDForName(platformName);
 634             String dirName = (String)xFontDirsMap.get(fontID);
 635             if (dirName != null) {
 636                 fontConfigDirs.add(dirName);
 637             }
 638         }
 639         return;
 640     }
 641 
 642     protected void getPlatformFontPathFromFontConfig() {
 643         if (fontConfigDirs == null) {
 644             fontConfigDirs = getFontConfiguration().getAWTFontPathSet();
 645             if (debugFonts && fontConfigDirs != null) {
 646                 String[] names = fontConfigDirs.toArray(new String[0]);
 647                 for (int i=0;i<names.length;i++) {
 648                     logger.info("awtfontpath : " + names[i]);
 649                 }
 650             }
 651         }
 652     }
 653 
 654     protected void registerPlatformFontsUsedByFontConfiguration() {
 655         if (fontConfigDirs == null) {
 656             return;
 657         }
 658         if (isLinux) {
 659             fontConfigDirs.add(jreLibDirName+File.separator+"oblique-fonts");
 660         }
 661         fontdirs = (String[])fontConfigDirs.toArray(new String[0]);
 662     }
 663 
 664     /* Called by MToolkit to set the X11 font path */
 665     public static void setNativeFontPath() {
 666         if (fontdirs == null) {
 667             return;
 668         }
 669 
 670         // need to register these individually rather than by one call
 671         // to ensure that one bad directory doesn't cause all to be rejected
 672         for (int i=0; i<fontdirs.length; i++) {
 673             if (debugFonts) {
 674                 logger.info("Add " + fontdirs[i] + " to X11 fontpath");
 675             }
 676             FontManager.setNativeFontPath(fontdirs[i]);
 677         }
 678     }
 679 
 680     /* Register just the paths, (it doesn't register the fonts).
 681      * If a font configuration file has specified a baseFontPath
 682      * fontPath is just those directories, unless on usage we
 683      * find it doesn't contain what we need for the logical fonts.
 684      * Otherwise, we register all the paths on Solaris, because
 685      * the fontPath we have here is the complete one from
 686      * parsing /var/sadm/install/contents, not just
 687      * what's on the X font path (may be this should be
 688      * changed).
 689      * But for now what it means is that if we didn't do
 690      * this then if the font weren't listed anywhere on the
 691      * less complete font path we'd trigger loadFonts which
 692      * actually registers the fonts. This may actually be
 693      * the right thing tho' since that would also set up
 694      * the X font path without which we wouldn't be able to
 695      * display some "native" fonts.
 696      * So something to revisit is that probably fontPath
 697      * here ought to be only the X font path + jre font dir.
 698      * loadFonts should have a separate native call to
 699      * get the rest of the platform font path.
 700      *
 701      * Registering the directories can now be avoided in the
 702      * font configuration initialisation when filename entries
 703      * exist in the font configuration file for all fonts.
 704      * (Perhaps a little confusingly a filename entry is
 705      * actually keyed using the XLFD used in the font entries,
 706      * and it maps *to* a real filename).
 707      * In the event any are missing, registration of all
 708      * directories will be invoked to find the real files.
 709      *
 710      * But registering the directory performed other
 711      * functions such as filling in the map of all native names
 712      * for the font. So when this method isn't invoked, they still
 713      * must be found. This is mitigated by getNativeNames now
 714      * being able to return at least the platform name, but mostly
 715      * by ensuring that when a filename key is found, that
 716      * xlfd key is stored as one of the set of platform names
 717      * for the font. Its a set because typical font configuration
 718      * files reference the same CJK font files using multiple
 719      * X11 encodings. For the code that adds this to the map
 720      * see X11GE.getFileNameFromPlatformName(..)
 721      * If you don't get all of these then some code points may
 722      * not use the Xserver, and will not get the PCF bitmaps
 723      * that are available for some point sizes.
 724      * So, in the event that there is such a problem,
 725      * unconditionally making this call may be necessary, at
 726      * some cost to JRE start-up
 727      */
 728     protected void registerFontDirs(String pathName) {
 729 
 730         StringTokenizer parser = new StringTokenizer(pathName,
 731                                                      File.pathSeparator);
 732         try {
 733             while (parser.hasMoreTokens()) {
 734                 String dirPath = parser.nextToken();
 735                 if (dirPath != null && !registeredDirs.containsKey(dirPath)) {
 736                     registeredDirs.put(dirPath, null);
 737                     registerFontDir(dirPath);
 738                 }
 739             }
 740         } catch (NoSuchElementException e) {
 741         }
 742     }
 743 
 744     /* NOTE: this method needs to be executed in a privileged context.
 745      * The superclass constructor which is the primary caller of
 746      * this method executes entirely in such a context. Additionally
 747      * the loadFonts() method does too. So all should be well.
 748 
 749      */
 750     protected void registerFontDir(String path) {
 751         /* fonts.dir file format looks like :-
 752          * 47
 753          * Arial.ttf -monotype-arial-regular-r-normal--0-0-0-0-p-0-iso8859-1
 754          * Arial-Bold.ttf -monotype-arial-bold-r-normal--0-0-0-0-p-0-iso8859-1
 755          * ...
 756          */
 757         if (debugFonts) {
 758             logger.info("ParseFontDir " + path);
 759         }
 760         File fontsDotDir = new File(path + File.separator + "fonts.dir");
 761         FileReader fr = null;
 762         try {
 763             if (fontsDotDir.canRead()) {
 764                 fr = new FileReader(fontsDotDir);
 765                 BufferedReader br = new BufferedReader(fr, 8192);
 766                 StreamTokenizer st = new StreamTokenizer(br);
 767                 st.eolIsSignificant(true);
 768                 int ttype = st.nextToken();
 769                 if (ttype == StreamTokenizer.TT_NUMBER) {
 770                     int numEntries = (int)st.nval;
 771                     ttype = st.nextToken();
 772                     if (ttype == StreamTokenizer.TT_EOL) {
 773                         st.resetSyntax();
 774                         st.wordChars(32, 127);
 775                         st.wordChars(128 + 32, 255);
 776                         st.whitespaceChars(0, 31);
 777 
 778                         for (int i=0; i < numEntries; i++) {
 779                             ttype = st.nextToken();
 780                             if (ttype == StreamTokenizer.TT_EOF) {
 781                                 break;
 782                             }
 783                             if (ttype != StreamTokenizer.TT_WORD) {
 784                                 break;
 785                             }
 786                             int breakPos = st.sval.indexOf(' ');
 787                             if (breakPos <= 0) {
 788                                 /* On TurboLinux 8.0 a fonts.dir file had
 789                                  * a line with integer value "24" which
 790                                  * appeared to be the number of remaining
 791                                  * entries in the file. This didn't add to
 792                                  * the value on the first line of the file.
 793                                  * Seemed like XFree86 didn't like this line
 794                                  * much either. It failed to parse the file.
 795                                  * Ignore lines like this completely, and
 796                                  * don't let them count as an entry.
 797                                  */
 798                                 numEntries++;
 799                                 ttype = st.nextToken();
 800                                 if (ttype != StreamTokenizer.TT_EOL) {
 801                                     break;
 802                                 }
 803 
 804                                 continue;
 805                             }
 806                             if (st.sval.charAt(0) == '!') {
 807                                 /* TurboLinux 8.0 comment line: ignore.
 808                                  * can't use st.commentChar('!') to just
 809                                  * skip because this line mustn't count
 810                                  * against numEntries.
 811                                  */
 812                                 numEntries++;
 813                                 ttype = st.nextToken();
 814                                 if (ttype != StreamTokenizer.TT_EOL) {
 815                                     break;
 816                                 }
 817                                 continue;
 818                             }
 819                             String fileName = st.sval.substring(0, breakPos);
 820                             /* TurboLinux 8.0 uses some additional syntax to
 821                              * indicate algorithmic styling values.
 822                              * Ignore ':' separated files at the beginning
 823                              * of the fileName
 824                              */
 825                             int lastColon = fileName.lastIndexOf(':');
 826                             if (lastColon > 0) {
 827                                 if (lastColon+1 >= fileName.length()) {
 828                                     continue;
 829                                 }
 830                                 fileName = fileName.substring(lastColon+1);
 831                             }
 832                             String fontPart = st.sval.substring(breakPos+1);
 833                             String fontID = specificFontIDForName(fontPart);
 834                             String sVal = (String) fontNameMap.get(fontID);
 835 
 836                             if (debugFonts) {
 837                                 logger.info("file=" + fileName +
 838                                             " xlfd=" + fontPart);
 839                                 logger.info("fontID=" + fontID +
 840                                             " sVal=" + sVal);
 841                             }
 842                             String fullPath = null;
 843                             try {
 844                                 File file = new File(path,fileName);
 845                                 /* we may have a resolved symbolic link
 846                                  * this becomes important for an xlfd we
 847                                  * still need to know the location it was
 848                                  * found to update the X server font path
 849                                  * for use by AWT heavyweights - and when 2D
 850                                  * wants to use the native rasteriser.
 851                                  */
 852                                 if (xFontDirsMap == null) {
 853                                     xFontDirsMap = new HashMap();
 854                                 }
 855                                 xFontDirsMap.put(fontID, path);
 856                                 fullPath = file.getCanonicalPath();
 857                             } catch (IOException e) {
 858                                 fullPath = path + File.separator + fileName;
 859                             }
 860                             Vector xVal = (Vector) xlfdMap.get(fullPath);
 861                             if (debugFonts) {
 862                                 logger.info("fullPath=" + fullPath +
 863                                             " xVal=" + xVal);
 864                             }
 865                             if ((xVal == null || !xVal.contains(fontPart)) &&
 866                                 (sVal == null) || !sVal.startsWith("/")) {
 867                                 if (debugFonts) {
 868                                     logger.info("Map fontID:"+fontID +
 869                                                 "to file:" + fullPath);
 870                                 }
 871                                 fontNameMap.put(fontID, fullPath);
 872                                 if (xVal == null) {
 873                                     xVal = new Vector();
 874                                     xlfdMap.put (fullPath, xVal);
 875                                 }
 876                                 xVal.add(fontPart);
 877                             }
 878 
 879                             ttype = st.nextToken();
 880                             if (ttype != StreamTokenizer.TT_EOL) {
 881                                 break;
 882                             }
 883                         }
 884                     }
 885                 }
 886                 fr.close();
 887             }
 888         } catch (IOException ioe1) {
 889         } finally {
 890             if (fr != null) {
 891                 try {
 892                     fr.close();
 893                 }  catch (IOException ioe2) {
 894                 }
 895             }
 896         }
 897     }
 898 
 899     @Override
 900     public void loadFonts() {
 901         super.loadFonts();
 902         /* These maps are greatly expanded during a loadFonts but
 903          * can be reset to their initial state afterwards.
 904          * Since preferLocaleFonts() and preferProportionalFonts() will
 905          * trigger a partial repopulating from the FontConfiguration
 906          * it has to be the inital (empty) state for the latter two, not
 907          * simply nulling out.
 908          * xFontDirsMap is a special case in that the implementation
 909          * will typically not ever need to initialise it so it can be null.
 910          */
 911         xFontDirsMap = null;
 912         xlfdMap = new HashMap(1);
 913         fontNameMap = new HashMap(1);
 914     }
 915 
 916     // Implements SunGraphicsEnvironment.createFontConfiguration.
 917     protected FontConfiguration createFontConfiguration() {
 918 
 919         /* The logic here decides whether to use a preconfigured
 920          * fontconfig.properties file, or synthesise one using platform APIs.
 921          * On Solaris (as opposed to OpenSolaris) we try to use the
 922          * pre-configured ones, but if the files it specifies are missing
 923          * we fail-safe to synthesising one. This might happen if Solaris
 924          * changes its fonts.
 925          * For OpenSolaris I don't expect us to ever create fontconfig files,
 926          * so it will always synthesise. Note that if we misidentify
 927          * OpenSolaris as Solaris, then the test for the presence of
 928          * Solaris-only font files will correct this.
 929          * For Linux we require an exact match of distro and version to
 930          * use the preconfigured file, and also that it points to
 931          * existent fonts.
 932          * If synthesising fails, we fall back to any preconfigured file
 933          * and do the best we can. For the commercial JDK this will be
 934          * fine as it includes the Lucida fonts. OpenJDK should not hit
 935          * this as the synthesis should always work on its platforms.
 936          */
 937         FontConfiguration mFontConfig = new MFontConfiguration(this);
 938         if (isOpenSolaris ||
 939             (isLinux &&
 940              (!mFontConfig.foundOsSpecificFile() ||
 941               !mFontConfig.fontFilesArePresent()) ||
 942              (isSolaris && !mFontConfig.fontFilesArePresent()))) {
 943             FcFontConfiguration fcFontConfig =
 944                 new FcFontConfiguration(this);
 945             if (fcFontConfig.init()) {
 946                 return fcFontConfig;
 947             }
 948         }
 949         mFontConfig.init();
 950         return mFontConfig;
 951     }
 952     public FontConfiguration
 953         createFontConfiguration(boolean preferLocaleFonts,
 954                                 boolean preferPropFonts) {
 955 
 956         FontConfiguration config = getFontConfiguration();
 957         if (config instanceof FcFontConfiguration) {
 958             // Doesn't need to implement the alternate support.
 959             return config;
 960         }
 961 
 962         return new MFontConfiguration(this,
 963                                       preferLocaleFonts, preferPropFonts);
 964     }
 965 
 966     /**
 967      * Returns face name for default font, or null if
 968      * no face names are used for CompositeFontDescriptors
 969      * for this platform.
 970      */
 971     public String getDefaultFontFaceName() {
 972 
 973         return null;
 974     }
 975 
 976     private static native boolean pRunningXinerama();
 977     private static native Point getXineramaCenterPoint();
 978 
 979     /**
 980      * Override for Xinerama case: call new Solaris API for getting the correct
 981      * centering point from the windowing system.
 982      */
 983     public Point getCenterPoint() {
 984         if (runningXinerama()) {
 985             Point p = getXineramaCenterPoint();
 986             if (p != null) {
 987                 return p;
 988             }
 989         }
 990         return super.getCenterPoint();
 991     }
 992 
 993     /**
 994      * Override for Xinerama case
 995      */
 996     public Rectangle getMaximumWindowBounds() {
 997         if (runningXinerama()) {
 998             return getXineramaWindowBounds();
 999         } else {
1000             return super.getMaximumWindowBounds();
1001         }
1002     }
1003 
1004     public boolean runningXinerama() {
1005         if (xinerState == null) {
1006             // pRunningXinerama() simply returns a global boolean variable,
1007             // so there is no need to synchronize here
1008             xinerState = Boolean.valueOf(pRunningXinerama());
1009             if (screenLog.isLoggable(Level.FINER)) {
1010                 screenLog.log(Level.FINER, "Running Xinerama: " + xinerState);
1011             }
1012         }
1013         return xinerState.booleanValue();
1014     }
1015 
1016     /**
1017      * Return the bounds for a centered Window on a system running in Xinerama
1018      * mode.
1019      *
1020      * Calculations are based on the assumption of a perfectly rectangular
1021      * display area (display edges line up with one another, and displays
1022      * have consistent width and/or height).
1023      *
1024      * The bounds to return depend on the arrangement of displays and on where
1025      * Windows are to be centered.  There are two common situations:
1026      *
1027      * 1) The center point lies at the center of the combined area of all the
1028      *    displays.  In this case, the combined area of all displays is
1029      *    returned.
1030      *
1031      * 2) The center point lies at the center of a single display.  In this case
1032      *    the user most likely wants centered Windows to be constrained to that
1033      *    single display.  The boundaries of the one display are returned.
1034      *
1035      * It is possible for the center point to be at both the center of the
1036      * entire display space AND at the center of a single monitor (a square of
1037      * 9 monitors, for instance).  In this case, the entire display area is
1038      * returned.
1039      *
1040      * Because the center point is arbitrarily settable by the user, it could
1041      * fit neither of the cases above.  The fallback case is to simply return
1042      * the combined area for all screens.
1043      */
1044     protected Rectangle getXineramaWindowBounds() {
1045         Point center = getCenterPoint();
1046         Rectangle unionRect, tempRect;
1047         GraphicsDevice[] gds = getScreenDevices();
1048         Rectangle centerMonitorRect = null;
1049         int i;
1050 
1051         // if center point is at the center of all monitors
1052         // return union of all bounds
1053         //
1054         //  MM*MM     MMM       M
1055         //            M*M       *
1056         //            MMM       M
1057 
1058         // if center point is at center of a single monitor (but not of all
1059         // monitors)
1060         // return bounds of single monitor
1061         //
1062         // MMM         MM
1063         // MM*         *M
1064 
1065         // else, center is in some strange spot (such as on the border between
1066         // monitors), and we should just return the union of all monitors
1067         //
1068         // MM          MMM
1069         // MM          MMM
1070 
1071         unionRect = getUsableBounds(gds[0]);
1072 
1073         for (i = 0; i < gds.length; i++) {
1074             tempRect = getUsableBounds(gds[i]);
1075             if (centerMonitorRect == null &&
1076                 // add a pixel or two for fudge-factor
1077                 (tempRect.width / 2) + tempRect.x > center.x - 1 &&
1078                 (tempRect.height / 2) + tempRect.y > center.y - 1 &&
1079                 (tempRect.width / 2) + tempRect.x < center.x + 1 &&
1080                 (tempRect.height / 2) + tempRect.y < center.y + 1) {
1081                 centerMonitorRect = tempRect;
1082             }
1083             unionRect = unionRect.union(tempRect);
1084         }
1085 
1086         // first: check for center of all monitors (video wall)
1087         // add a pixel or two for fudge-factor
1088         if ((unionRect.width / 2) + unionRect.x > center.x - 1 &&
1089             (unionRect.height / 2) + unionRect.y > center.y - 1 &&
1090             (unionRect.width / 2) + unionRect.x < center.x + 1 &&
1091             (unionRect.height / 2) + unionRect.y < center.y + 1) {
1092 
1093             if (screenLog.isLoggable(Level.FINER)) {
1094                 screenLog.log(Level.FINER, "Video Wall: center point is at center of all displays.");
1095             }
1096             return unionRect;
1097         }
1098 
1099         // next, check if at center of one monitor
1100         if (centerMonitorRect != null) {
1101             if (screenLog.isLoggable(Level.FINER)) {
1102                 screenLog.log(Level.FINER, "Center point at center of a particular " +
1103                                            "monitor, but not of the entire virtual display.");
1104             }
1105             return centerMonitorRect;
1106         }
1107 
1108         // otherwise, the center is at some weird spot: return unionRect
1109         if (screenLog.isLoggable(Level.FINER)) {
1110             screenLog.log(Level.FINER, "Center point is somewhere strange - return union of all bounds.");
1111         }
1112         return unionRect;
1113     }
1114 
1115     /**
1116      * From the DisplayChangedListener interface; devices do not need
1117      * to react to this event.
1118      */
1119     @Override
1120     public void paletteChanged() {
1121     }
1122 }