1 /*
   2  * Copyright 2000-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.Font;
  29 import java.io.DataInputStream;
  30 import java.io.DataOutputStream;
  31 import java.io.File;
  32 import java.io.FileInputStream;
  33 import java.io.InputStream;
  34 import java.io.IOException;
  35 import java.io.OutputStream;
  36 import java.nio.charset.Charset;
  37 import java.nio.charset.CharsetEncoder;
  38 import java.security.AccessController;
  39 import java.security.PrivilegedAction;
  40 import java.util.logging.Logger;
  41 import java.util.HashMap;
  42 import java.util.HashSet;
  43 import java.util.Hashtable;
  44 import java.util.Iterator;
  45 import java.util.Locale;
  46 import java.util.Map.Entry;
  47 import java.util.Properties;
  48 import java.util.Set;
  49 import java.util.Vector;
  50 import sun.font.CompositeFontDescriptor;
  51 import sun.font.SunFontManager;
  52 import sun.font.FontManagerFactory;
  53 import sun.font.FontUtilities;
  54 
  55 /**
  56  * Provides the definitions of the five logical fonts: Serif, SansSerif,
  57  * Monospaced, Dialog, and DialogInput. The necessary information
  58  * is obtained from fontconfig files.
  59  */
  60 public abstract class FontConfiguration {
  61 
  62     //static global runtime env
  63     protected static String osVersion;
  64     protected static String osName;
  65     protected static String encoding; // canonical name of default nio charset
  66     protected static Locale startupLocale = null;
  67     protected static Hashtable localeMap = null;
  68     private static FontConfiguration fontConfig;
  69     private static Logger logger;
  70     protected static boolean isProperties = true;
  71 
  72     protected SunFontManager fontManager;
  73     protected boolean preferLocaleFonts;
  74     protected boolean preferPropFonts;
  75 
  76     private File fontConfigFile;
  77     private boolean foundOsSpecificFile;
  78     private boolean inited;
  79     private String javaLib;
  80 
  81     /* A default FontConfiguration must be created before an alternate
  82      * one to ensure proper static initialisation takes place.
  83      */
  84     public FontConfiguration(SunFontManager fm) {
  85         if (FontUtilities.debugFonts() && logger == null) {
  86             logger = Logger.getLogger("sun.awt.FontConfiguration");
  87         }
  88         fontManager = fm;
  89         setOsNameAndVersion();  /* static initialization */
  90         setEncoding();          /* static initialization */
  91         /* Separating out the file location from the rest of the
  92          * initialisation, so the caller has the option of doing
  93          * something else if a suitable file isn't found.
  94          */
  95         findFontConfigFile();
  96     }
  97 
  98     public synchronized boolean init() {
  99         if (!inited) {
 100             this.preferLocaleFonts = false;
 101             this.preferPropFonts = false;
 102             setFontConfiguration();
 103             readFontConfigFile(fontConfigFile);
 104             initFontConfig();
 105             inited = true;
 106         }
 107         return true;
 108     }
 109 
 110     public FontConfiguration(SunFontManager fm,
 111                              boolean preferLocaleFonts,
 112                              boolean preferPropFonts) {
 113         fontManager = fm;
 114         this.preferLocaleFonts = preferLocaleFonts;
 115         this.preferPropFonts = preferPropFonts;
 116         /* fontConfig should be initialised by default constructor, and
 117          * its data tables can be shared, since readFontConfigFile doesn't
 118          * update any other state. Also avoid a doPrivileged block.
 119          */
 120         initFontConfig();
 121     }
 122 
 123     /**
 124      * Fills in this instance's osVersion and osName members. By
 125      * default uses the system properties os.name and os.version;
 126      * subclasses may override.
 127      */
 128     protected void setOsNameAndVersion() {
 129         osName = System.getProperty("os.name");
 130         osVersion = System.getProperty("os.version");
 131     }
 132 
 133     private void setEncoding() {
 134         encoding = Charset.defaultCharset().name();
 135         startupLocale = SunToolkit.getStartupLocale();
 136     }
 137 
 138     /////////////////////////////////////////////////////////////////////
 139     // methods for loading the FontConfig file                         //
 140     /////////////////////////////////////////////////////////////////////
 141 
 142     public boolean foundOsSpecificFile() {
 143         return foundOsSpecificFile;
 144     }
 145 
 146     /* Smoke test to see if we can trust this configuration by testing if
 147      * the first slot of a composite font maps to an installed file.
 148      */
 149     public boolean fontFilesArePresent() {
 150         init();
 151         short fontNameID = compFontNameIDs[0][0][0];
 152         short fileNameID = getComponentFileID(fontNameID);
 153         final String fileName = mapFileName(getComponentFileName(fileNameID));
 154         Boolean exists = (Boolean)java.security.AccessController.doPrivileged(
 155             new java.security.PrivilegedAction() {
 156                  public Object run() {
 157                      try {
 158                          File f = new File(fileName);
 159                          return Boolean.valueOf(f.exists());
 160                      }
 161                      catch (Exception e) {
 162                          return false;
 163                      }
 164                  }
 165                 });
 166         return exists.booleanValue();
 167     }
 168 
 169     private void findFontConfigFile() {
 170 
 171         foundOsSpecificFile = true; // default assumption.
 172         String javaHome = System.getProperty("java.home");
 173         if (javaHome == null) {
 174             throw new Error("java.home property not set");
 175         }
 176         javaLib = javaHome + File.separator + "lib";
 177         String userConfigFile = System.getProperty("sun.awt.fontconfig");
 178         if (userConfigFile != null) {
 179             fontConfigFile = new File(userConfigFile);
 180         } else {
 181             fontConfigFile = findFontConfigFile(javaLib);
 182         }
 183     }
 184 
 185     private void readFontConfigFile(File f) {
 186         /* This is invoked here as readFontConfigFile is only invoked
 187          * once per VM, and always in a privileged context, thus the
 188          * directory containing installed fall back fonts is accessed
 189          * from this context
 190          */
 191         getInstalledFallbackFonts(javaLib);
 192 
 193         if (f != null) {
 194             try {
 195                 FileInputStream in = new FileInputStream(f.getPath());
 196                 if (isProperties) {
 197                     loadProperties(in);
 198                 } else {
 199                     loadBinary(in);
 200                 }
 201                 in.close();
 202                 if (FontUtilities.debugFonts()) {
 203                     logger.config("Read logical font configuration from " + f);
 204                 }
 205             } catch (IOException e) {
 206                 if (FontUtilities.debugFonts()) {
 207                     logger.config("Failed to read logical font configuration from " + f);
 208                 }
 209             }
 210         }
 211         String version = getVersion();
 212         if (!"1".equals(version) && FontUtilities.debugFonts()) {
 213             logger.config("Unsupported fontconfig version: " + version);
 214         }
 215     }
 216 
 217     protected void getInstalledFallbackFonts(String javaLib) {
 218         String fallbackDirName = javaLib + File.separator +
 219             "fonts" + File.separator + "fallback";
 220 
 221         File fallbackDir = new File(fallbackDirName);
 222         if (fallbackDir.exists() && fallbackDir.isDirectory()) {
 223             String[] ttfs = fallbackDir.list(fontManager.getTrueTypeFilter());
 224             String[] t1s = fallbackDir.list(fontManager.getType1Filter());
 225             int numTTFs = (ttfs == null) ? 0 : ttfs.length;
 226             int numT1s = (t1s == null) ? 0 : t1s.length;
 227             int len = numTTFs + numT1s;
 228             if (numTTFs + numT1s == 0) {
 229                 return;
 230             }
 231             installedFallbackFontFiles = new String[len];
 232             for (int i=0; i<numTTFs; i++) {
 233                 installedFallbackFontFiles[i] =
 234                     fallbackDir + File.separator + ttfs[i];
 235             }
 236             for (int i=0; i<numT1s; i++) {
 237                 installedFallbackFontFiles[i+numTTFs] =
 238                     fallbackDir + File.separator + t1s[i];
 239             }
 240             fontManager.registerFontsInDir(fallbackDirName);
 241         }
 242     }
 243 
 244     private File findImpl(String fname) {
 245         File f = new File(fname + ".properties");
 246         if (f.canRead()) {
 247             isProperties = true;
 248             return f;
 249         }
 250         f = new File(fname + ".bfc");
 251         if (f.canRead()) {
 252             isProperties = false;
 253             return f;
 254         }
 255         return null;
 256     }
 257 
 258     private File findFontConfigFile(String javaLib) {
 259         String baseName = javaLib + File.separator + "fontconfig";
 260         File configFile;
 261         if (osVersion != null && osName != null) {
 262             configFile = findImpl(baseName + "." + osName + "." + osVersion);
 263             if (configFile != null) {
 264                 return configFile;
 265             }
 266         }
 267         if (osName != null) {
 268             configFile = findImpl(baseName + "." + osName);
 269             if (configFile != null) {
 270                 return configFile;
 271             }
 272         }
 273         if (osVersion != null) {
 274             configFile = findImpl(baseName + "." + osVersion);
 275             if (configFile != null) {
 276                 return configFile;
 277             }
 278         }
 279         foundOsSpecificFile = false;
 280 
 281         configFile = findImpl(baseName);
 282         if (configFile != null) {
 283             return configFile;
 284         }
 285         return null;
 286     }
 287 
 288     /* Initialize the internal data tables from binary format font
 289      * configuration file.
 290      */
 291     public static void loadBinary(InputStream inStream) throws IOException {
 292         DataInputStream in = new DataInputStream(inStream);
 293         head = readShortTable(in, HEAD_LENGTH);
 294         int[] tableSizes = new int[INDEX_TABLEEND];
 295         for (int i = 0; i < INDEX_TABLEEND; i++) {
 296             tableSizes[i] = head[i + 1] - head[i];
 297         }
 298         table_scriptIDs       = readShortTable(in, tableSizes[INDEX_scriptIDs]);
 299         table_scriptFonts     = readShortTable(in, tableSizes[INDEX_scriptFonts]);
 300         table_elcIDs          = readShortTable(in, tableSizes[INDEX_elcIDs]);
 301         table_sequences        = readShortTable(in, tableSizes[INDEX_sequences]);
 302         table_fontfileNameIDs = readShortTable(in, tableSizes[INDEX_fontfileNameIDs]);
 303         table_componentFontNameIDs = readShortTable(in, tableSizes[INDEX_componentFontNameIDs]);
 304         table_filenames       = readShortTable(in, tableSizes[INDEX_filenames]);
 305         table_awtfontpaths    = readShortTable(in, tableSizes[INDEX_awtfontpaths]);
 306         table_exclusions      = readShortTable(in, tableSizes[INDEX_exclusions]);
 307         table_proportionals   = readShortTable(in, tableSizes[INDEX_proportionals]);
 308         table_scriptFontsMotif   = readShortTable(in, tableSizes[INDEX_scriptFontsMotif]);
 309         table_alphabeticSuffix   = readShortTable(in, tableSizes[INDEX_alphabeticSuffix]);
 310         table_stringIDs       = readShortTable(in, tableSizes[INDEX_stringIDs]);
 311 
 312         //StringTable cache
 313         stringCache = new String[table_stringIDs.length + 1];
 314 
 315         int len = tableSizes[INDEX_stringTable];
 316         byte[] bb = new byte[len * 2];
 317         table_stringTable = new char[len];
 318         in.read(bb);
 319         int i = 0, j = 0;
 320         while (i < len) {
 321            table_stringTable[i++] = (char)(bb[j++] << 8 | (bb[j++] & 0xff));
 322         }
 323         if (verbose) {
 324             dump();
 325         }
 326     }
 327 
 328     /* Generate a binary format font configuration from internal data
 329      * tables.
 330      */
 331     public static void saveBinary(OutputStream out) throws IOException {
 332         DataOutputStream dataOut = new DataOutputStream(out);
 333         writeShortTable(dataOut, head);
 334         writeShortTable(dataOut, table_scriptIDs);
 335         writeShortTable(dataOut, table_scriptFonts);
 336         writeShortTable(dataOut, table_elcIDs);
 337         writeShortTable(dataOut, table_sequences);
 338         writeShortTable(dataOut, table_fontfileNameIDs);
 339         writeShortTable(dataOut, table_componentFontNameIDs);
 340         writeShortTable(dataOut, table_filenames);
 341         writeShortTable(dataOut, table_awtfontpaths);
 342         writeShortTable(dataOut, table_exclusions);
 343         writeShortTable(dataOut, table_proportionals);
 344         writeShortTable(dataOut, table_scriptFontsMotif);
 345         writeShortTable(dataOut, table_alphabeticSuffix);
 346         writeShortTable(dataOut, table_stringIDs);
 347         //stringTable
 348         dataOut.writeChars(new String(table_stringTable));
 349         out.close();
 350         if (verbose) {
 351             dump();
 352         }
 353         sanityCheck();
 354     }
 355 
 356     //private static boolean loadingProperties;
 357     private static short stringIDNum;
 358     private static short[] stringIDs;
 359     private static StringBuilder stringTable;
 360 
 361     public static void loadProperties(InputStream in) throws IOException {
 362         //loadingProperties = true;
 363         //StringID starts from "1", "0" is reserved for "not defined"
 364         stringIDNum = 1;
 365         stringIDs = new short[1000];
 366         stringTable = new StringBuilder(4096);
 367 
 368         if (verbose && logger == null) {
 369             logger = Logger.getLogger("sun.awt.FontConfiguration");
 370         }
 371         new PropertiesHandler().load(in);
 372 
 373         //loadingProperties = false;
 374         stringIDs = null;
 375         stringTable = null;
 376     }
 377 
 378 
 379     /////////////////////////////////////////////////////////////////////
 380     // methods for initializing the FontConfig                         //
 381     /////////////////////////////////////////////////////////////////////
 382 
 383     /**
 384      *  set initLocale, initEncoding and initELC for this FontConfig object
 385      *  currently we just simply use the startup locale and encoding
 386      */
 387     private void initFontConfig() {
 388         initLocale = startupLocale;
 389         initEncoding = encoding;
 390         if (preferLocaleFonts && !willReorderForStartupLocale()) {
 391             preferLocaleFonts = false;
 392         }
 393         initELC = getInitELC();
 394         initAllComponentFonts();
 395     }
 396 
 397     //"ELC" stands for "Encoding.Language.Country". This method returns
 398     //the ID of the matched elc setting of "initLocale" in elcIDs table.
 399     //If no match is found, it returns the default ID, which is
 400     //"NULL.NULL.NULL" in elcIDs table.
 401     private short getInitELC() {
 402         if (initELC != -1) {
 403             return initELC;
 404         }
 405         HashMap <String, Integer> elcIDs = new HashMap<String, Integer>();
 406         for (int i = 0; i < table_elcIDs.length; i++) {
 407             elcIDs.put(getString(table_elcIDs[i]), i);
 408         }
 409         String language = initLocale.getLanguage();
 410         String country = initLocale.getCountry();
 411         String elc;
 412         if (elcIDs.containsKey(elc=initEncoding + "." + language + "." + country)
 413             || elcIDs.containsKey(elc=initEncoding + "." + language)
 414             || elcIDs.containsKey(elc=initEncoding)) {
 415             initELC = elcIDs.get(elc).shortValue();
 416         } else {
 417             initELC = elcIDs.get("NULL.NULL.NULL").shortValue();
 418         }
 419         int i = 0;
 420         while (i < table_alphabeticSuffix.length) {
 421             if (initELC == table_alphabeticSuffix[i]) {
 422                 alphabeticSuffix = getString(table_alphabeticSuffix[i + 1]);
 423                 return initELC;
 424             }
 425             i += 2;
 426         }
 427         return initELC;
 428     }
 429 
 430     public static boolean verbose;
 431     private short    initELC = -1;
 432     private Locale   initLocale;
 433     private String   initEncoding;
 434     private String   alphabeticSuffix;
 435 
 436     private short[][][] compFontNameIDs = new short[NUM_FONTS][NUM_STYLES][];
 437     private int[][][] compExclusions = new int[NUM_FONTS][][];
 438     private int[] compCoreNum = new int[NUM_FONTS];
 439 
 440     private Set<Short> coreFontNameIDs = new HashSet<Short>();
 441     private Set<Short> fallbackFontNameIDs = new HashSet<Short>();
 442 
 443     private void initAllComponentFonts() {
 444         short[] fallbackScripts = getFallbackScripts();
 445         for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) {
 446             short[] coreScripts = getCoreScripts(fontIndex);
 447             compCoreNum[fontIndex] = coreScripts.length;
 448             /*
 449             System.out.println("coreScriptID=" + table_sequences[initELC * 5 + fontIndex]);
 450             for (int i = 0; i < coreScripts.length; i++) {
 451             System.out.println("  " + i + " :" + getString(table_scriptIDs[coreScripts[i]]));
 452             }
 453             */
 454             //init exclusionRanges
 455             int[][] exclusions = new int[coreScripts.length][];
 456             for (int i = 0; i < coreScripts.length; i++) {
 457                 exclusions[i] = getExclusionRanges(coreScripts[i]);
 458             }
 459             compExclusions[fontIndex] = exclusions;
 460             //init componentFontNames
 461             for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) {
 462                 int index;
 463                 short[] nameIDs = new short[coreScripts.length + fallbackScripts.length];
 464                 //core
 465                 for (index = 0; index < coreScripts.length; index++) {
 466                     nameIDs[index] = getComponentFontID(coreScripts[index],
 467                                                fontIndex, styleIndex);
 468                     if (preferLocaleFonts && localeMap != null &&
 469                             fontManager.usingAlternateFontforJALocales()) {
 470                         nameIDs[index] = remapLocaleMap(fontIndex, styleIndex,
 471                                                         coreScripts[index], nameIDs[index]);
 472                     }
 473                     if (preferPropFonts) {
 474                         nameIDs[index] = remapProportional(fontIndex, nameIDs[index]);
 475                     }
 476                     //System.out.println("nameid=" + nameIDs[index]);
 477                     coreFontNameIDs.add(nameIDs[index]);
 478                 }
 479                 //fallback
 480                 for (int i = 0; i < fallbackScripts.length; i++) {
 481                     short id = getComponentFontID(fallbackScripts[i],
 482                                                fontIndex, styleIndex);
 483                     if (preferLocaleFonts && localeMap != null &&
 484                             fontManager.usingAlternateFontforJALocales()) {
 485                         id = remapLocaleMap(fontIndex, styleIndex, fallbackScripts[i], id);
 486                     }
 487                     if (preferPropFonts) {
 488                         id = remapProportional(fontIndex, id);
 489                     }
 490                     if (contains(nameIDs, id, index)) {
 491                         continue;
 492                     }
 493                     /*
 494                       System.out.println("fontIndex=" + fontIndex + ", styleIndex=" + styleIndex
 495                            + ", fbIndex=" + i + ",fbS=" + fallbackScripts[i] + ", id=" + id);
 496                     */
 497                     fallbackFontNameIDs.add(id);
 498                     nameIDs[index++] = id;
 499                 }
 500                 if (index < nameIDs.length) {
 501                     short[] newNameIDs = new short[index];
 502                     System.arraycopy(nameIDs, 0, newNameIDs, 0, index);
 503                     nameIDs = newNameIDs;
 504                 }
 505                 compFontNameIDs[fontIndex][styleIndex] = nameIDs;
 506             }
 507         }
 508    }
 509 
 510    private short remapLocaleMap(int fontIndex, int styleIndex, short scriptID, short fontID) {
 511         String scriptName = getString(table_scriptIDs[scriptID]);
 512 
 513         String value = (String)localeMap.get(scriptName);
 514         if (value == null) {
 515             String fontName = fontNames[fontIndex];
 516             String styleName = styleNames[styleIndex];
 517             value = (String)localeMap.get(fontName + "." + styleName + "." + scriptName);
 518         }
 519         if (value == null) {
 520             return fontID;
 521         }
 522 
 523         for (int i = 0; i < table_componentFontNameIDs.length; i++) {
 524             String name = getString(table_componentFontNameIDs[i]);
 525             if (value.equalsIgnoreCase(name)) {
 526                 fontID = (short)i;
 527                 break;
 528             }
 529         }
 530         return fontID;
 531     }
 532 
 533     public static boolean hasMonoToPropMap() {
 534         return table_proportionals != null && table_proportionals.length != 0;
 535     }
 536 
 537     private short remapProportional(int fontIndex, short id) {
 538     if (preferPropFonts &&
 539         table_proportionals.length != 0 &&
 540         fontIndex != 2 &&         //"monospaced"
 541         fontIndex != 4) {         //"dialoginput"
 542             int i = 0;
 543             while (i < table_proportionals.length) {
 544                 if (table_proportionals[i] == id) {
 545                     return table_proportionals[i + 1];
 546                 }
 547                 i += 2;
 548             }
 549         }
 550         return id;
 551     }
 552 
 553     /////////////////////////////////////////////////////////////////////
 554     // Methods for handling font and style names                       //
 555     /////////////////////////////////////////////////////////////////////
 556     protected static final int NUM_FONTS = 5;
 557     protected static final int NUM_STYLES = 4;
 558     protected static final String[] fontNames
 559             = {"serif", "sansserif", "monospaced", "dialog", "dialoginput"};
 560     protected static final String[] publicFontNames
 561             = {Font.SERIF, Font.SANS_SERIF, Font.MONOSPACED, Font.DIALOG,
 562                Font.DIALOG_INPUT};
 563     protected static final String[] styleNames
 564             = {"plain", "bold", "italic", "bolditalic"};
 565 
 566     /**
 567      * Checks whether the given font family name is a valid logical font name.
 568      * The check is case insensitive.
 569      */
 570     public static boolean isLogicalFontFamilyName(String fontName) {
 571         return isLogicalFontFamilyNameLC(fontName.toLowerCase(Locale.ENGLISH));
 572     }
 573 
 574     /**
 575      * Checks whether the given font family name is a valid logical font name.
 576      * The check is case sensitive.
 577      */
 578     public static boolean isLogicalFontFamilyNameLC(String fontName) {
 579         for (int i = 0; i < fontNames.length; i++) {
 580             if (fontName.equals(fontNames[i])) {
 581                 return true;
 582             }
 583         }
 584         return false;
 585     }
 586 
 587     /**
 588      * Checks whether the given style name is a valid logical font style name.
 589      */
 590     private static boolean isLogicalFontStyleName(String styleName) {
 591         for (int i = 0; i < styleNames.length; i++) {
 592             if (styleName.equals(styleNames[i])) {
 593                 return true;
 594             }
 595         }
 596         return false;
 597     }
 598 
 599     /**
 600      * Checks whether the given font face name is a valid logical font name.
 601      * The check is case insensitive.
 602      */
 603     public static boolean isLogicalFontFaceName(String fontName) {
 604         return isLogicalFontFaceNameLC(fontName.toLowerCase(Locale.ENGLISH));
 605     }
 606 
 607    /**
 608     * Checks whether the given font face name is a valid logical font name.
 609     * The check is case sensitive.
 610     */
 611     public static boolean isLogicalFontFaceNameLC(String fontName) {
 612         int period = fontName.indexOf('.');
 613         if (period >= 0) {
 614             String familyName = fontName.substring(0, period);
 615             String styleName = fontName.substring(period + 1);
 616             return isLogicalFontFamilyName(familyName) &&
 617                     isLogicalFontStyleName(styleName);
 618         } else {
 619             return isLogicalFontFamilyName(fontName);
 620         }
 621     }
 622 
 623     protected static int getFontIndex(String fontName) {
 624         return getArrayIndex(fontNames, fontName);
 625     }
 626 
 627     protected static int getStyleIndex(String styleName) {
 628         return getArrayIndex(styleNames, styleName);
 629     }
 630 
 631     private static int getArrayIndex(String[] names, String name) {
 632         for (int i = 0; i < names.length; i++) {
 633             if (name.equals(names[i])) {
 634                 return i;
 635             }
 636         }
 637         assert false;
 638         return 0;
 639     }
 640 
 641     protected static int getStyleIndex(int style) {
 642         switch (style) {
 643             case Font.PLAIN:
 644                 return 0;
 645             case Font.BOLD:
 646                 return 1;
 647             case Font.ITALIC:
 648                 return 2;
 649             case Font.BOLD | Font.ITALIC:
 650                 return 3;
 651             default:
 652                 return 0;
 653         }
 654     }
 655 
 656     protected static String getFontName(int fontIndex) {
 657         return fontNames[fontIndex];
 658     }
 659 
 660     protected static String getStyleName(int styleIndex) {
 661         return styleNames[styleIndex];
 662     }
 663 
 664     /**
 665      * Returns the font face name for the given logical font
 666      * family name and style.
 667      * The style argument is interpreted as in java.awt.Font.Font.
 668      */
 669     public static String getLogicalFontFaceName(String familyName, int style) {
 670         assert isLogicalFontFamilyName(familyName);
 671         return familyName.toLowerCase(Locale.ENGLISH) + "." + getStyleString(style);
 672     }
 673 
 674     /**
 675      * Returns the string typically used in properties files
 676      * for the given style.
 677      * The style argument is interpreted as in java.awt.Font.Font.
 678      */
 679     public static String getStyleString(int style) {
 680         return getStyleName(getStyleIndex(style));
 681     }
 682 
 683     /**
 684      * Returns a fallback name for the given font name. For a few known
 685      * font names, matching logical font names are returned. For all
 686      * other font names, defaultFallback is returned.
 687      * defaultFallback differs between AWT and 2D.
 688      */
 689     public abstract String getFallbackFamilyName(String fontName, String defaultFallback);
 690 
 691     /**
 692      * Returns the 1.1 equivalent for some old 1.0 font family names for
 693      * which we need to maintain compatibility in some configurations.
 694      * Returns null for other font names.
 695      */
 696     protected String getCompatibilityFamilyName(String fontName) {
 697         fontName = fontName.toLowerCase(Locale.ENGLISH);
 698         if (fontName.equals("timesroman")) {
 699             return "serif";
 700         } else if (fontName.equals("helvetica")) {
 701             return "sansserif";
 702         } else if (fontName.equals("courier")) {
 703             return "monospaced";
 704         }
 705         return null;
 706     }
 707 
 708     protected static String[] installedFallbackFontFiles = null;
 709 
 710     /**
 711      * Maps a file name given in the font configuration file
 712      * to a format appropriate for the platform.
 713      */
 714     protected String mapFileName(String fileName) {
 715         return fileName;
 716     }
 717 
 718     //////////////////////////////////////////////////////////////////////
 719     //  reordering                                                      //
 720     //////////////////////////////////////////////////////////////////////
 721 
 722     /* Mappings from file encoding to font config name for font supporting
 723      * the corresponding language. This is filled in by initReorderMap()
 724      */
 725     protected HashMap reorderMap = null;
 726 
 727     /* Platform-specific mappings */
 728     protected abstract void initReorderMap();
 729 
 730     /* Move item at index "src" to "dst", shuffling all values in
 731      * between down
 732      */
 733     private void shuffle(String[] seq, int src, int dst) {
 734         if (dst >= src) {
 735             return;
 736         }
 737         String tmp = seq[src];
 738         for (int i=src; i>dst; i--) {
 739             seq[i] = seq[i-1];
 740         }
 741         seq[dst] = tmp;
 742     }
 743 
 744     /* Called to determine if there's a re-order sequence for this locale/
 745      * encoding. If there's none then the caller can "bail" and avoid
 746      * unnecessary work
 747      */
 748     public static boolean willReorderForStartupLocale() {
 749         return getReorderSequence() != null;
 750     }
 751 
 752     private static Object getReorderSequence() {
 753         if (fontConfig.reorderMap == null) {
 754              fontConfig.initReorderMap();
 755         }
 756         HashMap reorderMap = fontConfig.reorderMap;
 757 
 758         /* Find the most specific mapping */
 759         String language = startupLocale.getLanguage();
 760         String country = startupLocale.getCountry();
 761         Object val = reorderMap.get(encoding + "." + language + "." + country);
 762         if (val == null) {
 763             val = reorderMap.get(encoding + "." + language);
 764         }
 765         if (val == null) {
 766             val = reorderMap.get(encoding);
 767         }
 768         return val;
 769     }
 770 
 771     /* This method reorders the sequence such that the matches for the
 772      * file encoding are moved ahead of other elements.
 773      * If an encoding uses more than one font, they are all moved up.
 774      */
 775      private void reorderSequenceForLocale(String[] seq) {
 776         Object val =  getReorderSequence();
 777         if (val instanceof String) {
 778             for (int i=0; i< seq.length; i++) {
 779                 if (seq[i].equals(val)) {
 780                     shuffle(seq, i, 0);
 781                     return;
 782                 }
 783             }
 784         } else if (val instanceof String[]) {
 785             String[] fontLangs = (String[])val;
 786             for (int l=0; l<fontLangs.length;l++) {
 787                 for (int i=0; i<seq.length;i++) {
 788                     if (seq[i].equals(fontLangs[l])) {
 789                         shuffle(seq, i, l);
 790                     }
 791                 }
 792             }
 793         }
 794     }
 795 
 796     private static Vector splitSequence(String sequence) {
 797         //String.split would be more convenient, but incurs big performance penalty
 798         Vector parts = new Vector();
 799         int start = 0;
 800         int end;
 801         while ((end = sequence.indexOf(',', start)) >= 0) {
 802             parts.add(sequence.substring(start, end));
 803             start = end + 1;
 804         }
 805         if (sequence.length() > start) {
 806             parts.add(sequence.substring(start, sequence.length()));
 807         }
 808         return parts;
 809     }
 810 
 811     protected String[] split(String sequence) {
 812         Vector v = splitSequence(sequence);
 813         return (String[])v.toArray(new String[0]);
 814     }
 815 
 816     ////////////////////////////////////////////////////////////////////////
 817     // Methods for extracting information from the fontconfig data for AWT//
 818     ////////////////////////////////////////////////////////////////////////
 819     private Hashtable charsetRegistry = new Hashtable(5);
 820 
 821     /**
 822      * Returns FontDescriptors describing the physical fonts used for the
 823      * given logical font name and style. The font name is interpreted
 824      * in a case insensitive way.
 825      * The style argument is interpreted as in java.awt.Font.Font.
 826      */
 827     public FontDescriptor[] getFontDescriptors(String fontName, int style) {
 828         assert isLogicalFontFamilyName(fontName);
 829         fontName = fontName.toLowerCase(Locale.ENGLISH);
 830         int fontIndex = getFontIndex(fontName);
 831         int styleIndex = getStyleIndex(style);
 832         return getFontDescriptors(fontIndex, styleIndex);
 833     }
 834     private FontDescriptor[][][] fontDescriptors =
 835         new FontDescriptor[NUM_FONTS][NUM_STYLES][];
 836 
 837     private FontDescriptor[] getFontDescriptors(int fontIndex, int styleIndex) {
 838         FontDescriptor[] descriptors = fontDescriptors[fontIndex][styleIndex];
 839         if (descriptors == null) {
 840             descriptors = buildFontDescriptors(fontIndex, styleIndex);
 841             fontDescriptors[fontIndex][styleIndex] = descriptors;
 842         }
 843         return descriptors;
 844     }
 845 
 846     private FontDescriptor[] buildFontDescriptors(int fontIndex, int styleIndex) {
 847         String fontName = fontNames[fontIndex];
 848         String styleName = styleNames[styleIndex];
 849 
 850         short[] scriptIDs = getCoreScripts(fontIndex);
 851         short[] nameIDs = compFontNameIDs[fontIndex][styleIndex];
 852         String[] sequence = new String[scriptIDs.length];
 853         String[] names = new String[scriptIDs.length];
 854         for (int i = 0; i < sequence.length; i++) {
 855             names[i] = getComponentFontName(nameIDs[i]);
 856             sequence[i] = getScriptName(scriptIDs[i]);
 857             if (alphabeticSuffix != null && "alphabetic".equals(sequence[i])) {
 858                 sequence[i] = sequence[i] + "/" + alphabeticSuffix;
 859             }
 860         }
 861         int[][] fontExclusionRanges = compExclusions[fontIndex];
 862 
 863         FontDescriptor[] descriptors = new FontDescriptor[names.length];
 864 
 865         for (int i = 0; i < names.length; i++) {
 866             String awtFontName;
 867             String encoding;
 868 
 869             awtFontName = makeAWTFontName(names[i], sequence[i]);
 870 
 871             // look up character encoding
 872             encoding = getEncoding(names[i], sequence[i]);
 873             if (encoding == null) {
 874                 encoding = "default";
 875             }
 876             CharsetEncoder enc
 877                     = getFontCharsetEncoder(encoding.trim(), awtFontName);
 878 
 879             // we already have the exclusion ranges
 880             int[] exclusionRanges = fontExclusionRanges[i];
 881 
 882             // create descriptor
 883             descriptors[i] = new FontDescriptor(awtFontName, enc, exclusionRanges);
 884         }
 885         return descriptors;
 886     }
 887 
 888     /**
 889      * Returns the AWT font name for the given platform font name and
 890      * character subset.
 891      */
 892     protected String makeAWTFontName(String platformFontName,
 893             String characterSubsetName) {
 894         return platformFontName;
 895     }
 896 
 897     /**
 898      * Returns the java.io name of the platform character encoding for the
 899      * given AWT font name and character subset. May return "default"
 900      * to indicate that getDefaultFontCharset should be called to obtain
 901      * a charset encoder.
 902      */
 903     protected abstract String getEncoding(String awtFontName,
 904             String characterSubsetName);
 905 
 906     private CharsetEncoder getFontCharsetEncoder(final String charsetName,
 907             String fontName) {
 908 
 909         Charset fc = null;
 910         if (charsetName.equals("default")) {
 911             fc = (Charset) charsetRegistry.get(fontName);
 912         } else {
 913             fc = (Charset) charsetRegistry.get(charsetName);
 914         }
 915         if (fc != null) {
 916             return fc.newEncoder();
 917         }
 918 
 919         if (!charsetName.startsWith("sun.awt.") && !charsetName.equals("default")) {
 920             fc = Charset.forName(charsetName);
 921         } else {
 922             Class fcc = (Class) AccessController.doPrivileged(new PrivilegedAction() {
 923                     public Object run() {
 924                         try {
 925                             return Class.forName(charsetName, true,
 926                                                  Thread.currentThread().getContextClassLoader());
 927                         } catch (ClassNotFoundException e) {
 928                         }
 929                         return null;
 930                     }
 931                 });
 932 
 933             if (fcc != null) {
 934                 try {
 935                     fc = (Charset) fcc.newInstance();
 936                 } catch (Exception e) {
 937                 }
 938             }
 939         }
 940         if (fc == null) {
 941             fc = getDefaultFontCharset(fontName);
 942         }
 943 
 944         if (charsetName.equals("default")){
 945             charsetRegistry.put(fontName, fc);
 946         } else {
 947             charsetRegistry.put(charsetName, fc);
 948         }
 949         return fc.newEncoder();
 950     }
 951 
 952     protected abstract Charset getDefaultFontCharset(
 953             String fontName);
 954 
 955     /* This retrieves the platform font directories (path) calculated
 956      * by setAWTFontPathSequence(String[]). The default implementation
 957      * returns null, its expected that X11 platforms may return
 958      * non-null.
 959      */
 960     public HashSet<String> getAWTFontPathSet() {
 961         return null;
 962     }
 963 
 964     ////////////////////////////////////////////////////////////////////////
 965     // methods for extracting information from the fontconfig data for 2D //
 966     ////////////////////////////////////////////////////////////////////////
 967 
 968     /**
 969      * Returns an array of composite font descriptors for all logical font
 970      * faces.
 971      * If the font configuration file doesn't specify Lucida Sans Regular
 972      * or the given fallback font as component fonts, they are added here.
 973      */
 974     public CompositeFontDescriptor[] get2DCompositeFontInfo() {
 975         CompositeFontDescriptor[] result =
 976                 new CompositeFontDescriptor[NUM_FONTS * NUM_STYLES];
 977         String defaultFontFile = fontManager.getDefaultFontFile();
 978         String defaultFontFaceName = fontManager.getDefaultFontFaceName();
 979 
 980         for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) {
 981             String fontName = publicFontNames[fontIndex];
 982 
 983             // determine exclusion ranges for font
 984             // AWT uses separate exclusion range array per component font.
 985             // 2D packs all range boundaries into one array.
 986             // Both use separate entries for lower and upper boundary.
 987             int[][] exclusions = compExclusions[fontIndex];
 988             int numExclusionRanges = 0;
 989             for (int i = 0; i < exclusions.length; i++) {
 990                 numExclusionRanges += exclusions[i].length;
 991             }
 992             int[] exclusionRanges = new int[numExclusionRanges];
 993             int[] exclusionRangeLimits = new int[exclusions.length];
 994             int exclusionRangeIndex = 0;
 995             int exclusionRangeLimitIndex = 0;
 996             for (int i = 0; i < exclusions.length; i++) {
 997                 int[] componentRanges = exclusions[i];
 998                 for (int j = 0; j < componentRanges.length; ) {
 999                     int value = componentRanges[j];
1000                     exclusionRanges[exclusionRangeIndex++] = componentRanges[j++];
1001                     exclusionRanges[exclusionRangeIndex++] = componentRanges[j++];
1002                 }
1003                 exclusionRangeLimits[i] = exclusionRangeIndex;
1004             }
1005             // other info is per style
1006             for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) {
1007                 int maxComponentFontCount = compFontNameIDs[fontIndex][styleIndex].length;
1008                 boolean sawDefaultFontFile = false;
1009                 // fall back fonts listed in the lib/fonts/fallback directory
1010                 if (installedFallbackFontFiles != null) {
1011                     maxComponentFontCount += installedFallbackFontFiles.length;
1012                 }
1013                 String faceName = fontName + "." + styleNames[styleIndex];
1014 
1015                 // determine face names and file names of component fonts
1016                 String[] componentFaceNames = new String[maxComponentFontCount];
1017                 String[] componentFileNames = new String[maxComponentFontCount];
1018 
1019                 int index;
1020                 for (index = 0; index < compFontNameIDs[fontIndex][styleIndex].length; index++) {
1021                     short fontNameID = compFontNameIDs[fontIndex][styleIndex][index];
1022                     short fileNameID = getComponentFileID(fontNameID);
1023                     componentFaceNames[index] = getFaceNameFromComponentFontName(getComponentFontName(fontNameID));
1024                     componentFileNames[index] = mapFileName(getComponentFileName(fileNameID));
1025                     if (componentFileNames[index] == null ||
1026                         needToSearchForFile(componentFileNames[index])) {
1027                         componentFileNames[index] = getFileNameFromComponentFontName(getComponentFontName(fontNameID));
1028                     }
1029                     if (!sawDefaultFontFile &&
1030                         defaultFontFile.equals(componentFileNames[index])) {
1031                         sawDefaultFontFile = true;
1032                     }
1033                     /*
1034                     System.out.println(publicFontNames[fontIndex] + "." + styleNames[styleIndex] + "."
1035                         + getString(table_scriptIDs[coreScripts[index]]) + "=" + componentFileNames[index]);
1036                     */
1037                 }
1038 
1039                 //"Lucida Sans Regular" is not in the list, we add it here
1040                 if (!sawDefaultFontFile) {
1041                     int len = 0;
1042                     if (installedFallbackFontFiles != null) {
1043                         len = installedFallbackFontFiles.length;
1044                     }
1045                     if (index + len == maxComponentFontCount) {
1046                         String[] newComponentFaceNames = new String[maxComponentFontCount + 1];
1047                         System.arraycopy(componentFaceNames, 0, newComponentFaceNames, 0, index);
1048                         componentFaceNames = newComponentFaceNames;
1049                         String[] newComponentFileNames = new String[maxComponentFontCount + 1];
1050                         System.arraycopy(componentFileNames, 0, newComponentFileNames, 0, index);
1051                         componentFileNames = newComponentFileNames;
1052                     }
1053                     componentFaceNames[index] = defaultFontFaceName;
1054                     componentFileNames[index] = defaultFontFile;
1055                     index++;
1056                 }
1057 
1058                 if (installedFallbackFontFiles != null) {
1059                     for (int ifb=0; ifb<installedFallbackFontFiles.length; ifb++) {
1060                         componentFaceNames[index] = null;
1061                         componentFileNames[index] = installedFallbackFontFiles[ifb];
1062                         index++;
1063                     }
1064                 }
1065 
1066                 if (index < maxComponentFontCount) {
1067                     String[] newComponentFaceNames = new String[index];
1068                     System.arraycopy(componentFaceNames, 0, newComponentFaceNames, 0, index);
1069                     componentFaceNames = newComponentFaceNames;
1070                     String[] newComponentFileNames = new String[index];
1071                     System.arraycopy(componentFileNames, 0, newComponentFileNames, 0, index);
1072                     componentFileNames = newComponentFileNames;
1073                 }
1074                 // exclusion range limit array length must match component face name
1075                 // array length - native code relies on this
1076 
1077                 int[] clippedExclusionRangeLimits = exclusionRangeLimits;
1078                 if (index != clippedExclusionRangeLimits.length) {
1079                     int len = exclusionRangeLimits.length;
1080                     clippedExclusionRangeLimits = new int[index];
1081                     System.arraycopy(exclusionRangeLimits, 0, clippedExclusionRangeLimits, 0, len);
1082                     //padding for various fallback fonts
1083                     for (int i = len; i < index; i++) {
1084                         clippedExclusionRangeLimits[i] = exclusionRanges.length;
1085                     }
1086                 }
1087                 /*
1088                 System.out.println(faceName + ":");
1089                 for (int i = 0; i < componentFileNames.length; i++) {
1090                     System.out.println("    " + componentFaceNames[i]
1091                          + "  -> " + componentFileNames[i]);
1092                 }
1093                 */
1094                 result[fontIndex * NUM_STYLES + styleIndex]
1095                         = new CompositeFontDescriptor(
1096                             faceName,
1097                             compCoreNum[fontIndex],
1098                             componentFaceNames,
1099                             componentFileNames,
1100                             exclusionRanges,
1101                             clippedExclusionRangeLimits);
1102             }
1103         }
1104         return result;
1105     }
1106 
1107     protected abstract String getFaceNameFromComponentFontName(String componentFontName);
1108     protected abstract String getFileNameFromComponentFontName(String componentFontName);
1109 
1110     /*
1111     public class 2dFont {
1112         public String platformName;
1113         public String fontfileName;
1114     }
1115     private 2dFont [] componentFonts = null;
1116     */
1117 
1118     /* Used on Linux to test if a file referenced in a font configuration
1119      * file exists in the location that is expected. If it does, no need
1120      * to search for it. If it doesn't then unless its a fallback font,
1121      * return that expensive code should be invoked to search for the font.
1122      */
1123     HashMap<String, Boolean> existsMap;
1124     public boolean needToSearchForFile(String fileName) {
1125         if (!FontUtilities.isLinux) {
1126             return false;
1127         } else if (existsMap == null) {
1128            existsMap = new HashMap<String, Boolean>();
1129         }
1130         Boolean exists = existsMap.get(fileName);
1131         if (exists == null) {
1132             /* call getNumberCoreFonts() to ensure these are initialised, and
1133              * if this file isn't for a core component, ie, is a for a fallback
1134              * font which very typically isn't available, then can't afford
1135              * to take the start-up penalty to search for it.
1136              */
1137             getNumberCoreFonts();
1138             if (!coreFontFileNames.contains(fileName)) {
1139                 exists = Boolean.TRUE;
1140             } else {
1141                 exists = Boolean.valueOf((new File(fileName)).exists());
1142                 existsMap.put(fileName, exists);
1143                 if (FontUtilities.debugFonts() &&
1144                     exists == Boolean.FALSE) {
1145                     logger.warning("Couldn't locate font file " + fileName);
1146                 }
1147             }
1148         }
1149         return exists == Boolean.FALSE;
1150     }
1151 
1152     private int numCoreFonts = -1;
1153     private String[] componentFonts = null;
1154     HashMap <String, String> filenamesMap = new HashMap<String, String>();
1155     HashSet <String> coreFontFileNames = new HashSet<String>();
1156 
1157     /* Return the number of core fonts. Note this isn't thread safe but
1158      * a calling thread can call this and getPlatformFontNames() in either
1159      * order.
1160      */
1161     public int getNumberCoreFonts() {
1162         if (numCoreFonts == -1) {
1163             numCoreFonts = coreFontNameIDs.size();
1164             Short[] emptyShortArray = new Short[0];
1165             Short[] core = coreFontNameIDs.toArray(emptyShortArray);
1166             Short[] fallback = fallbackFontNameIDs.toArray(emptyShortArray);
1167 
1168             int numFallbackFonts = 0;
1169             int i;
1170             for (i = 0; i < fallback.length; i++) {
1171                 if (coreFontNameIDs.contains(fallback[i])) {
1172                     fallback[i] = null;
1173                     continue;
1174                 }
1175                 numFallbackFonts++;
1176             }
1177             componentFonts = new String[numCoreFonts + numFallbackFonts];
1178             String filename = null;
1179             for (i = 0; i < core.length; i++) {
1180                 short fontid = core[i];
1181                 short fileid = getComponentFileID(fontid);
1182                 componentFonts[i] = getComponentFontName(fontid);
1183                 String compFileName = getComponentFileName(fileid);
1184                 if (compFileName != null) {
1185                     coreFontFileNames.add(compFileName);
1186                 }
1187                 filenamesMap.put(componentFonts[i], mapFileName(compFileName));
1188             }
1189             for (int j = 0; j < fallback.length; j++) {
1190                 if (fallback[j] != null) {
1191                     short fontid = fallback[j];
1192                     short fileid = getComponentFileID(fontid);
1193                     componentFonts[i] = getComponentFontName(fontid);
1194                     filenamesMap.put(componentFonts[i],
1195                                      mapFileName(getComponentFileName(fileid)));
1196                     i++;
1197                 }
1198             }
1199         }
1200         return numCoreFonts;
1201     }
1202 
1203     /* Return all platform font names used by this font configuration.
1204      * The first getNumberCoreFonts() entries are guaranteed to be the
1205      * core fonts - ie no fall back only fonts.
1206      */
1207     public String[] getPlatformFontNames() {
1208         if (numCoreFonts == -1) {
1209             getNumberCoreFonts();
1210         }
1211         return componentFonts;
1212     }
1213 
1214     /**
1215      * Returns a file name for the physical font represented by this platform font name,
1216      * if the font configuration has such information available, or null if the
1217      * information is unavailable. The file name returned is just a hint; a null return
1218      * value doesn't necessarily mean that the font is unavailable, nor does a non-null
1219      * return value guarantee that the file exists and contains the physical font.
1220      * The file name can be an absolute or a relative path name.
1221      */
1222     public String getFileNameFromPlatformName(String platformName) {
1223         // get2DCompositeFontInfo
1224         //     ->  getFileNameFromComponentfontName()  (W/M)
1225         //       ->   getFileNameFromPlatformName()
1226         // it's a waste of time on Win32, but I have to give X11 a chance to
1227         // call getFileNameFromXLFD()
1228         return filenamesMap.get(platformName);
1229     }
1230 
1231     /**
1232      * Returns a configuration specific path to be appended to the font
1233      * search path.
1234      */
1235     public String getExtraFontPath() {
1236         return getString(head[INDEX_appendedfontpath]);
1237     }
1238 
1239     public String getVersion() {
1240         return getString(head[INDEX_version]);
1241     }
1242 
1243     /* subclass support */
1244     protected static FontConfiguration getFontConfiguration() {
1245         return fontConfig;
1246     }
1247 
1248     protected void setFontConfiguration() {
1249         fontConfig = this;      /* static initialization */
1250     }
1251 
1252     //////////////////////////////////////////////////////////////////////
1253     // FontConfig data tables and the index constants in binary file    //
1254     //////////////////////////////////////////////////////////////////////
1255     /* The binary font configuration file begins with a short[] "head", which
1256      * contains the offsets to the starts of the individual data table which
1257      * immediately follow. Teh current implemention includes the tables shown
1258      * below.
1259      *
1260      * (00) table_scriptIDs    :stringIDs of all defined CharacterSubsetNames
1261      * (01) table_scriptFonts  :scriptID x fontIndex x styleIndex->
1262      *                          PlatformFontNameID mapping. Each scriptID might
1263      *                          have 1 or 20 entries depends on if it is defined
1264      *                          via a "allfonts.CharacterSubsetname" or a list of
1265      *                          "LogicalFontName.StyleName.CharacterSubsetName"
1266      *                          entries, positive entry means it's a "allfonts"
1267      *                          entry, a negative value means this is a offset to
1268      *                          a NUM_FONTS x NUM_STYLES subtable.
1269      * (02) table_elcIDs       :stringIDs of all defined ELC names, string
1270      *                          "NULL.NULL.NULL" is used for "default"
1271      * (03) table_sequences    :elcID x logicalFont -> scriptIDs table defined
1272      *                          by "sequence.allfonts/LogicalFontName.ELC" in
1273      *                          font configuration file, each "elcID" has
1274      *                          NUM_FONTS (5) entries in this table.
1275      * (04) table_fontfileNameIDs
1276      *                         :stringIDs of all defined font file names
1277      * (05) table_componentFontNameIDs
1278      *                         :stringIDs of all defined PlatformFontNames
1279      * (06) table_filenames    :platformFontNamesID->fontfileNameID mapping
1280      *                          table, the index is the platformFontNamesID.
1281      * (07) table_awtfontpaths :CharacterSubsetNames->awtfontpaths mapping table,
1282      *                          the index is the CharacterSubsetName's stringID
1283      *                          and content is the stringID of awtfontpath.
1284      * (08) table_exclusions   :scriptID -> exclusionRanges mapping table,
1285      *                          the index is the scriptID and the content is
1286                                 a id of an exclusionRanges int[].
1287      * (09) table_proportionals:list of pairs of PlatformFontNameIDs, stores
1288      *                          the replacement info defined by "proportional"
1289      *                          keyword.
1290      * (10) table_scriptFontsMotif
1291      *                         :same as (01) except this table stores the
1292      *                          info defined with ".motif" keyword
1293      * (11) table_alphabeticSuffix
1294      *                         :elcID -> stringID of alphabetic/XXXX entries
1295      * (12) table_stringIDs    :The index of this table is the string ID, the
1296      *                          content is the "start index" of this string in
1297      *                          stringTable, use the start index of next entry
1298      *                          as the "end index".
1299      * (13) table_stringTable  :The real storage of all character strings defined
1300      *                          /used this font configuration, need a pair of
1301      *                          "start" and "end" indices to access.
1302      * (14) reserved
1303      * (15) table_fallbackScripts
1304      *                         :stringIDs of fallback CharacterSubsetnames, stored
1305      *                          in the order of they are defined in sequence.fallback.
1306      * (16) table_appendedfontpath
1307      *                         :stringtID of the "appendedfontpath" defined.
1308      * (17) table_version   :stringID of the version number of this fontconfig file.
1309      */
1310     private static final int HEAD_LENGTH = 20;
1311     private static final int INDEX_scriptIDs = 0;
1312     private static final int INDEX_scriptFonts = 1;
1313     private static final int INDEX_elcIDs = 2;
1314     private static final int INDEX_sequences = 3;
1315     private static final int INDEX_fontfileNameIDs = 4;
1316     private static final int INDEX_componentFontNameIDs = 5;
1317     private static final int INDEX_filenames = 6;
1318     private static final int INDEX_awtfontpaths = 7;
1319     private static final int INDEX_exclusions = 8;
1320     private static final int INDEX_proportionals = 9;
1321     private static final int INDEX_scriptFontsMotif = 10;
1322     private static final int INDEX_alphabeticSuffix = 11;
1323     private static final int INDEX_stringIDs = 12;
1324     private static final int INDEX_stringTable = 13;
1325     private static final int INDEX_TABLEEND = 14;
1326     private static final int INDEX_fallbackScripts = 15;
1327     private static final int INDEX_appendedfontpath = 16;
1328     private static final int INDEX_version = 17;
1329 
1330     private static short[] head;
1331     private static short[] table_scriptIDs;
1332     private static short[] table_scriptFonts;
1333     private static short[] table_elcIDs;
1334     private static short[] table_sequences;
1335     private static short[] table_fontfileNameIDs;
1336     private static short[] table_componentFontNameIDs;
1337     private static short[] table_filenames;
1338     protected static short[] table_awtfontpaths;
1339     private static short[] table_exclusions;
1340     private static short[] table_proportionals;
1341     private static short[] table_scriptFontsMotif;
1342     private static short[] table_alphabeticSuffix;
1343     private static short[] table_stringIDs;
1344     private static char[]  table_stringTable;
1345 
1346     private static void sanityCheck() {
1347         int errors = 0;
1348 
1349         //This method will only be called during build time, do we
1350         //need do PrivilegedAction?
1351         String osName = (String)java.security.AccessController.doPrivileged(
1352                             new java.security.PrivilegedAction() {
1353             public Object run() {
1354                 return System.getProperty("os.name");
1355             }
1356         });
1357 
1358         //componentFontNameID starts from "1"
1359         for (int ii = 1; ii < table_filenames.length; ii++) {
1360             if (table_filenames[ii] == -1) {
1361                 System.out.println("\n Warning: "
1362                                    + "<filename."
1363                                    + getString(table_componentFontNameIDs[ii])
1364                                    + "> entry is missing!!!");
1365                 if (!osName.contains("Linux")) {
1366                     errors++;
1367                 }
1368             }
1369         }
1370         for (int ii = 0; ii < table_scriptIDs.length; ii++) {
1371             short fid = table_scriptFonts[ii];
1372             if (fid == 0) {
1373                 System.out.println("\n Error: <allfonts."
1374                                    + getString(table_scriptIDs[ii])
1375                                    + "> entry is missing!!!");
1376                 errors++;
1377                 continue;
1378             } else if (fid < 0) {
1379                 fid = (short)-fid;
1380                 for (int iii = 0; iii < NUM_FONTS; iii++) {
1381                     for (int iij = 0; iij < NUM_STYLES; iij++) {
1382                         int jj = iii * NUM_STYLES + iij;
1383                         short ffid = table_scriptFonts[fid + jj];
1384                         if (ffid == 0) {
1385                             System.out.println("\n Error: <"
1386                                            + getFontName(iii) + "."
1387                                            + getStyleName(iij) + "."
1388                                            + getString(table_scriptIDs[ii])
1389                                            + "> entry is missing!!!");
1390                             errors++;
1391                         }
1392                     }
1393                 }
1394             }
1395         }
1396         if ("SunOS".equals(osName)) {
1397             for (int ii = 0; ii < table_awtfontpaths.length; ii++) {
1398                 if (table_awtfontpaths[ii] == 0) {
1399                     String script = getString(table_scriptIDs[ii]);
1400                     if (script.contains("lucida") ||
1401                         script.contains("dingbats") ||
1402                         script.contains("symbol")) {
1403                         continue;
1404                     }
1405                     System.out.println("\nError: "
1406                                        + "<awtfontpath."
1407                                        + script
1408                                        + "> entry is missing!!!");
1409                     errors++;
1410                 }
1411             }
1412         }
1413         if (errors != 0) {
1414             System.out.println("!!THERE ARE " + errors + " ERROR(S) IN "
1415                                + "THE FONTCONFIG FILE, PLEASE CHECK ITS CONTENT!!\n");
1416             System.exit(1);
1417 
1418         }
1419     }
1420 
1421     //dump the fontconfig data tables
1422     private static void dump() {
1423         System.out.println("\n----Head Table------------");
1424         for (int ii = 0; ii < HEAD_LENGTH; ii++) {
1425             System.out.println("  " + ii + " : " + head[ii]);
1426         }
1427         System.out.println("\n----scriptIDs-------------");
1428         printTable(table_scriptIDs, 0);
1429         System.out.println("\n----scriptFonts----------------");
1430         for (int ii = 0; ii < table_scriptIDs.length; ii++) {
1431             short fid = table_scriptFonts[ii];
1432             if (fid >= 0) {
1433                 System.out.println("  allfonts."
1434                                    + getString(table_scriptIDs[ii])
1435                                    + "="
1436                                    + getString(table_componentFontNameIDs[fid]));
1437             }
1438         }
1439         for (int ii = 0; ii < table_scriptIDs.length; ii++) {
1440             short fid = table_scriptFonts[ii];
1441             if (fid < 0) {
1442                 fid = (short)-fid;
1443                 for (int iii = 0; iii < NUM_FONTS; iii++) {
1444                     for (int iij = 0; iij < NUM_STYLES; iij++) {
1445                         int jj = iii * NUM_STYLES + iij;
1446                         short ffid = table_scriptFonts[fid + jj];
1447                         System.out.println("  "
1448                                            + getFontName(iii) + "."
1449                                            + getStyleName(iij) + "."
1450                                            + getString(table_scriptIDs[ii])
1451                                            + "="
1452                                            + getString(table_componentFontNameIDs[ffid]));
1453                     }
1454                 }
1455 
1456             }
1457         }
1458         System.out.println("\n----elcIDs----------------");
1459         printTable(table_elcIDs, 0);
1460         System.out.println("\n----sequences-------------");
1461         for (int ii = 0; ii< table_elcIDs.length; ii++) {
1462             System.out.println("  " + ii + "/" + getString((short)table_elcIDs[ii]));
1463             short[] ss = getShortArray(table_sequences[ii * NUM_FONTS + 0]);
1464             for (int jj = 0; jj < ss.length; jj++) {
1465                 System.out.println("     " + getString((short)table_scriptIDs[ss[jj]]));
1466             }
1467         }
1468         System.out.println("\n----fontfileNameIDs-------");
1469         printTable(table_fontfileNameIDs, 0);
1470 
1471         System.out.println("\n----componentFontNameIDs--");
1472         printTable(table_componentFontNameIDs, 1);
1473         System.out.println("\n----filenames-------------");
1474         for (int ii = 0; ii < table_filenames.length; ii++) {
1475             if (table_filenames[ii] == -1) {
1476                 System.out.println("  " + ii + " : null");
1477             } else {
1478                 System.out.println("  " + ii + " : "
1479                    + getString(table_fontfileNameIDs[table_filenames[ii]]));
1480             }
1481         }
1482         System.out.println("\n----awtfontpaths---------");
1483         for (int ii = 0; ii < table_awtfontpaths.length; ii++) {
1484             System.out.println("  " + getString(table_scriptIDs[ii])
1485                                + " : "
1486                                + getString(table_awtfontpaths[ii]));
1487         }
1488         System.out.println("\n----proportionals--------");
1489         for (int ii = 0; ii < table_proportionals.length; ii++) {
1490             System.out.println("  "
1491                    + getString((short)table_componentFontNameIDs[table_proportionals[ii++]])
1492                    + " -> "
1493                    + getString((short)table_componentFontNameIDs[table_proportionals[ii]]));
1494         }
1495         int i = 0;
1496         System.out.println("\n----alphabeticSuffix----");
1497         while (i < table_alphabeticSuffix.length) {
1498           System.out.println("    " + getString(table_elcIDs[table_alphabeticSuffix[i++]])
1499                              + " -> " + getString(table_alphabeticSuffix[i++]));
1500         }
1501         System.out.println("\n----String Table---------");
1502         System.out.println("    stringID:    Num =" + table_stringIDs.length);
1503         System.out.println("    stringTable: Size=" + table_stringTable.length * 2);
1504 
1505         System.out.println("\n----fallbackScriptIDs---");
1506         short[] fbsIDs = getShortArray(head[INDEX_fallbackScripts]);
1507         for (int ii = 0; ii < fbsIDs.length; ii++) {
1508           System.out.println("  " + getString(table_scriptIDs[fbsIDs[ii]]));
1509         }
1510         System.out.println("\n----appendedfontpath-----");
1511         System.out.println("  " + getString(head[INDEX_appendedfontpath]));
1512         System.out.println("\n----Version--------------");
1513         System.out.println("  " + getString(head[INDEX_version]));
1514     }
1515 
1516 
1517     //////////////////////////////////////////////////////////////////////
1518     // Data table access methods                                        //
1519     //////////////////////////////////////////////////////////////////////
1520 
1521     /* Return the fontID of the platformFontName defined in this font config
1522      * by "LogicalFontName.StyleName.CharacterSubsetName" entry or
1523      * "allfonts.CharacterSubsetName" entry in properties format fc file.
1524      */
1525     protected static short getComponentFontID(short scriptID, int fontIndex, int styleIndex) {
1526         short fid = table_scriptFonts[scriptID];
1527         //System.out.println("fid=" + fid + "/ scriptID=" + scriptID + ", fi=" + fontIndex + ", si=" + styleIndex);
1528         if (fid >= 0) {
1529             //"allfonts"
1530             return fid;
1531         } else {
1532             return table_scriptFonts[-fid + fontIndex * NUM_STYLES + styleIndex];
1533         }
1534     }
1535 
1536     /* Same as getCompoentFontID() except this method returns the fontID define by
1537      * "xxxx.motif" entry.
1538      */
1539     protected static short getComponentFontIDMotif(short scriptID, int fontIndex, int styleIndex) {
1540         if (table_scriptFontsMotif.length == 0) {
1541             return 0;
1542         }
1543         short fid = table_scriptFontsMotif[scriptID];
1544         if (fid >= 0) {
1545             //"allfonts" > 0 or "not defined" == 0
1546             return fid;
1547         } else {
1548             return table_scriptFontsMotif[-fid + fontIndex * NUM_STYLES + styleIndex];
1549         }
1550     }
1551 
1552     private static int[] getExclusionRanges(short scriptID) {
1553         short exID = table_exclusions[scriptID];
1554         if (exID == 0) {
1555             return EMPTY_INT_ARRAY;
1556         } else {
1557             char[] exChar = getString(exID).toCharArray();
1558             int[] exInt = new int[exChar.length / 2];
1559             int i = 0;
1560             for (int j = 0; j < exInt.length; j++) {
1561                 exInt[j] = (exChar[i++] << 16) + (exChar[i++] & 0xffff);
1562             }
1563             return exInt;
1564         }
1565     }
1566 
1567     private static boolean contains(short IDs[], short id, int limit) {
1568         for (int i = 0; i < limit; i++) {
1569             if (IDs[i] == id) {
1570                 return true;
1571             }
1572         }
1573         return false;
1574     }
1575 
1576     /* Return the PlatformFontName from its fontID*/
1577     protected static String getComponentFontName(short id) {
1578         if (id < 0) {
1579             return null;
1580         }
1581         return getString(table_componentFontNameIDs[id]);
1582     }
1583 
1584     private static String getComponentFileName(short id) {
1585         if (id < 0) {
1586             return null;
1587         }
1588         return getString(table_fontfileNameIDs[id]);
1589     }
1590 
1591     //componentFontID -> componentFileID
1592     private static short getComponentFileID(short nameID) {
1593         return table_filenames[nameID];
1594     }
1595 
1596     private static String getScriptName(short scriptID) {
1597         return getString(table_scriptIDs[scriptID]);
1598     }
1599 
1600    private HashMap<String, Short> reorderScripts;
1601    protected short[] getCoreScripts(int fontIndex) {
1602         short elc = getInitELC();
1603         /*
1604           System.out.println("getCoreScripts: elc=" + elc + ", fontIndex=" + fontIndex);
1605           short[] ss = getShortArray(table_sequences[elc * NUM_FONTS + fontIndex]);
1606           for (int i = 0; i < ss.length; i++) {
1607               System.out.println("     " + getString((short)table_scriptIDs[ss[i]]));
1608           }
1609           */
1610         short[] scripts = getShortArray(table_sequences[elc * NUM_FONTS + fontIndex]);
1611         if (preferLocaleFonts) {
1612             if (reorderScripts == null) {
1613                 reorderScripts = new HashMap<String, Short>();
1614             }
1615             String[] ss = new String[scripts.length];
1616             for (int i = 0; i < ss.length; i++) {
1617                 ss[i] = getScriptName(scripts[i]);
1618                 reorderScripts.put(ss[i], scripts[i]);
1619             }
1620             reorderSequenceForLocale(ss);
1621             for (int i = 0; i < ss.length; i++) {
1622                 scripts[i] = reorderScripts.get(ss[i]);
1623             }
1624         }
1625          return scripts;
1626     }
1627 
1628     private static short[] getFallbackScripts() {
1629         return getShortArray(head[INDEX_fallbackScripts]);
1630     }
1631 
1632     private static void printTable(short[] list, int start) {
1633         for (int i = start; i < list.length; i++) {
1634             System.out.println("  " + i + " : " + getString(list[i]));
1635         }
1636     }
1637 
1638     private static short[] readShortTable(DataInputStream in, int len )
1639         throws IOException {
1640         if (len == 0) {
1641             return EMPTY_SHORT_ARRAY;
1642         }
1643         short[] data = new short[len];
1644         byte[] bb = new byte[len * 2];
1645         in.read(bb);
1646         int i = 0,j = 0;
1647         while (i < len) {
1648             data[i++] = (short)(bb[j++] << 8 | (bb[j++] & 0xff));
1649         }
1650         return data;
1651     }
1652 
1653     private static void writeShortTable(DataOutputStream out, short[] data)
1654         throws IOException {
1655         for (int i = 0; i < data.length; i++) {
1656             out.writeShort(data[i]);
1657         }
1658     }
1659 
1660     private static short[] toList(HashMap map) {
1661         short[] list = new short[map.size()];
1662         for (int i = 0; i < list.length; i++) {
1663             list[i] = -1;
1664         }
1665         Iterator iterator = map.entrySet().iterator();
1666         while (iterator.hasNext()) {
1667             Entry<String, Short> entry = (Entry <String, Short>)iterator.next();
1668             list[entry.getValue().shortValue()] = getStringID(entry.getKey());
1669         }
1670         return list;
1671     }
1672 
1673     //runtime cache
1674     private static String[] stringCache;
1675     protected static String getString(short stringID) {
1676         if (stringID == 0)
1677             return null;
1678         /*
1679         if (loadingProperties) {
1680             return stringTable.substring(stringIDs[stringID],
1681                                          stringIDs[stringID+1]);
1682         }
1683         */
1684         //sync if we want it to be MT-enabled
1685         if (stringCache[stringID] == null){
1686             stringCache[stringID] =
1687               new String (table_stringTable,
1688                           table_stringIDs[stringID],
1689                           table_stringIDs[stringID+1] - table_stringIDs[stringID]);
1690         }
1691         return stringCache[stringID];
1692     }
1693 
1694     private static short[] getShortArray(short shortArrayID) {
1695         String s = getString(shortArrayID);
1696         char[] cc = s.toCharArray();
1697         short[] ss = new short[cc.length];
1698         for (int i = 0; i < cc.length; i++) {
1699             ss[i] = (short)(cc[i] & 0xffff);
1700         }
1701         return ss;
1702     }
1703 
1704     private static short getStringID(String s) {
1705         if (s == null) {
1706             return (short)0;
1707         }
1708         short pos0 = (short)stringTable.length();
1709         stringTable.append(s);
1710         short pos1 = (short)stringTable.length();
1711 
1712         stringIDs[stringIDNum] = pos0;
1713         stringIDs[stringIDNum + 1] = pos1;
1714         stringIDNum++;
1715         if (stringIDNum + 1 >= stringIDs.length) {
1716             short[] tmp = new short[stringIDNum + 1000];
1717             System.arraycopy(stringIDs, 0, tmp, 0, stringIDNum);
1718             stringIDs = tmp;
1719         }
1720         return (short)(stringIDNum - 1);
1721     }
1722 
1723     private static short getShortArrayID(short sa[]) {
1724         char[] cc = new char[sa.length];
1725         for (int i = 0; i < sa.length; i ++) {
1726             cc[i] = (char)sa[i];
1727         }
1728         String s = new String(cc);
1729         return getStringID(s);
1730     }
1731 
1732     //utility "empty" objects
1733     private static final int[] EMPTY_INT_ARRAY = new int[0];
1734     private static final String[] EMPTY_STRING_ARRAY = new String[0];
1735     private static final short[] EMPTY_SHORT_ARRAY = new short[0];
1736     private static final String UNDEFINED_COMPONENT_FONT = "unknown";
1737 
1738     //////////////////////////////////////////////////////////////////////////
1739     //Convert the FontConfig data in Properties file to binary data tables  //
1740     //////////////////////////////////////////////////////////////////////////
1741     static class PropertiesHandler {
1742         public void load(InputStream in) throws IOException {
1743             initLogicalNameStyle();
1744             initHashMaps();
1745             FontProperties fp = new FontProperties();
1746             fp.load(in);
1747             initBinaryTable();
1748         }
1749 
1750         private void initBinaryTable() {
1751             //(0)
1752             head = new short[HEAD_LENGTH];
1753             head[INDEX_scriptIDs] = (short)HEAD_LENGTH;
1754 
1755             table_scriptIDs = toList(scriptIDs);
1756             //(1)a: scriptAllfonts scriptID/allfonts -> componentFontNameID
1757             //   b: scriptFonts    scriptID -> componentFontNameID[20]
1758             //if we have a "allfonts.script" def, then we just put
1759             //the "-platformFontID" value in the slot, otherwise the slot
1760             //value is "offset" which "offset" is where 20 entries located
1761             //in the table attached.
1762             head[INDEX_scriptFonts] = (short)(head[INDEX_scriptIDs]  + table_scriptIDs.length);
1763             int len = table_scriptIDs.length + scriptFonts.size() * 20;
1764             table_scriptFonts = new short[len];
1765 
1766             Iterator iterator = scriptAllfonts.entrySet().iterator();
1767             while (iterator.hasNext()) {
1768                 Entry<Short, Short> entry = (Entry <Short, Short>)iterator.next();
1769                 table_scriptFonts[entry.getKey().intValue()] = (short)entry.getValue().shortValue();
1770             }
1771             int off = table_scriptIDs.length;
1772             iterator = scriptFonts.entrySet().iterator();
1773             while (iterator.hasNext()) {
1774                 Entry<Short, Short[]> entry = (Entry <Short, Short[]>)iterator.next();
1775                 table_scriptFonts[entry.getKey().intValue()] = (short)-off;
1776                 Short[] v = entry.getValue();
1777                 int i = 0;
1778                 while (i < 20) {
1779                     if (v[i] != null) {
1780                         table_scriptFonts[off++] = v[i].shortValue();
1781                     } else {
1782                         table_scriptFonts[off++] = 0;
1783                     }
1784                     i++;
1785                 }
1786             }
1787 
1788             //(2)
1789             head[INDEX_elcIDs] = (short)(head[INDEX_scriptFonts]  + table_scriptFonts.length);
1790             table_elcIDs = toList(elcIDs);
1791 
1792             //(3) sequences  elcID -> XXXX[1|5] -> scriptID[]
1793             head[INDEX_sequences] = (short)(head[INDEX_elcIDs]  + table_elcIDs.length);
1794             table_sequences = new short[elcIDs.size() * NUM_FONTS];
1795             iterator = sequences.entrySet().iterator();
1796             while (iterator.hasNext()) {
1797                 Entry<Short, short[]> entry = (Entry <Short, short[]>)iterator.next();
1798                 //table_sequences[entry.getKey().intValue()] = (short)-off;
1799                 int k = entry.getKey().intValue();
1800                 short[] v = entry.getValue();
1801                 /*
1802                   System.out.println("elc=" + k + "/" + getString((short)table_elcIDs[k]));
1803                   short[] ss = getShortArray(v[0]);
1804                   for (int i = 0; i < ss.length; i++) {
1805                   System.out.println("     " + getString((short)table_scriptIDs[ss[i]]));
1806                   }
1807                   */
1808                 if (v.length == 1) {
1809                     //the "allfonts" entries
1810                     for (int i = 0; i < NUM_FONTS; i++) {
1811                         table_sequences[k * NUM_FONTS + i] = v[0];
1812                     }
1813                 } else {
1814                     for (int i = 0; i < NUM_FONTS; i++) {
1815                         table_sequences[k * NUM_FONTS + i] = v[i];
1816                     }
1817                 }
1818             }
1819             //(4)
1820             head[INDEX_fontfileNameIDs] = (short)(head[INDEX_sequences]  + table_sequences.length);
1821             table_fontfileNameIDs = toList(fontfileNameIDs);
1822 
1823             //(5)
1824             head[INDEX_componentFontNameIDs] = (short)(head[INDEX_fontfileNameIDs]  + table_fontfileNameIDs.length);
1825             table_componentFontNameIDs = toList(componentFontNameIDs);
1826 
1827             //(6)componentFontNameID -> filenameID
1828             head[INDEX_filenames] = (short)(head[INDEX_componentFontNameIDs]  + table_componentFontNameIDs.length);
1829             table_filenames = new short[table_componentFontNameIDs.length];
1830             for (int i = 0; i < table_filenames.length; i++) {
1831                 table_filenames[i] = -1;
1832             }
1833             iterator = filenames.entrySet().iterator();
1834             while (iterator.hasNext()) {
1835                 Entry<Short, Short> entry = (Entry <Short, Short>)iterator.next();
1836                 table_filenames[entry.getKey().shortValue()] = entry.getValue().shortValue();
1837             }
1838 
1839             //(7)scriptID-> awtfontpath
1840             //the paths are stored as scriptID -> stringID in awtfontpahts
1841             head[INDEX_awtfontpaths] = (short)(head[INDEX_filenames]  + table_filenames.length);
1842             table_awtfontpaths = new short[table_scriptIDs.length];
1843             iterator = awtfontpaths.entrySet().iterator();
1844             while (iterator.hasNext()) {
1845                 Entry<Short, Short> entry = (Entry <Short, Short>)iterator.next();
1846                 table_awtfontpaths[entry.getKey().shortValue()] = entry.getValue().shortValue();
1847             }
1848 
1849             //(8)exclusions
1850             head[INDEX_exclusions] = (short)(head[INDEX_awtfontpaths]  + table_awtfontpaths.length);
1851             table_exclusions = new short[scriptIDs.size()];
1852             iterator = exclusions.entrySet().iterator();
1853             while (iterator.hasNext()) {
1854                 Entry<Short, int[]> entry = (Entry <Short, int[]>)iterator.next();
1855                 int[] exI = entry.getValue();
1856                 char[] exC = new char[exI.length * 2];
1857                 int j = 0;
1858                 for (int i = 0; i < exI.length; i++) {
1859                     exC[j++] = (char) (exI[i] >> 16);
1860                     exC[j++] = (char) (exI[i] & 0xffff);
1861                 }
1862                 table_exclusions[entry.getKey().shortValue()] = getStringID(new String (exC));
1863             }
1864             //(9)proportionals
1865             head[INDEX_proportionals] = (short)(head[INDEX_exclusions]  + table_exclusions.length);
1866             table_proportionals = new short[proportionals.size() * 2];
1867             iterator = proportionals.entrySet().iterator();
1868             int j = 0;
1869             while (iterator.hasNext()) {
1870                 Entry<Short, Short> entry = (Entry <Short, Short>)iterator.next();
1871                 table_proportionals[j++] = entry.getKey().shortValue();
1872                 table_proportionals[j++] = entry.getValue().shortValue();
1873             }
1874 
1875             //(10) see (1) for info, the only difference is "xxx.motif"
1876             head[INDEX_scriptFontsMotif] = (short)(head[INDEX_proportionals] + table_proportionals.length);
1877             if (scriptAllfontsMotif.size() != 0 || scriptFontsMotif.size() != 0) {
1878                 len = table_scriptIDs.length + scriptFontsMotif.size() * 20;
1879                 table_scriptFontsMotif = new short[len];
1880 
1881                 iterator = scriptAllfontsMotif.entrySet().iterator();
1882                 while (iterator.hasNext()) {
1883                     Entry<Short, Short> entry = (Entry <Short, Short>)iterator.next();
1884                     table_scriptFontsMotif[entry.getKey().intValue()] =
1885                       (short)entry.getValue().shortValue();
1886                 }
1887                 off = table_scriptIDs.length;
1888                 iterator = scriptFontsMotif.entrySet().iterator();
1889                 while (iterator.hasNext()) {
1890                     Entry<Short, Short[]> entry = (Entry <Short, Short[]>)iterator.next();
1891                     table_scriptFontsMotif[entry.getKey().intValue()] = (short)-off;
1892                     Short[] v = entry.getValue();
1893                     int i = 0;
1894                     while (i < 20) {
1895                         if (v[i] != null) {
1896                             table_scriptFontsMotif[off++] = v[i].shortValue();
1897                         } else {
1898                             table_scriptFontsMotif[off++] = 0;
1899                         }
1900                         i++;
1901                     }
1902                 }
1903             } else {
1904                 table_scriptFontsMotif = EMPTY_SHORT_ARRAY;
1905             }
1906 
1907             //(11)short[] alphabeticSuffix
1908             head[INDEX_alphabeticSuffix] = (short)(head[INDEX_scriptFontsMotif] + table_scriptFontsMotif.length);
1909             table_alphabeticSuffix = new short[alphabeticSuffix.size() * 2];
1910             iterator = alphabeticSuffix.entrySet().iterator();
1911             j = 0;
1912             while (iterator.hasNext()) {
1913                 Entry<Short, Short> entry = (Entry <Short, Short>)iterator.next();
1914                 table_alphabeticSuffix[j++] = entry.getKey().shortValue();
1915                 table_alphabeticSuffix[j++] = entry.getValue().shortValue();
1916             }
1917 
1918             //(15)short[] fallbackScriptIDs; just put the ID in head
1919             head[INDEX_fallbackScripts] = getShortArrayID(fallbackScriptIDs);
1920 
1921             //(16)appendedfontpath
1922             head[INDEX_appendedfontpath] = getStringID(appendedfontpath);
1923 
1924             //(17)version
1925             head[INDEX_version] = getStringID(version);
1926 
1927             //(12)short[] StringIDs
1928             head[INDEX_stringIDs] = (short)(head[INDEX_alphabeticSuffix] + table_alphabeticSuffix.length);
1929             table_stringIDs = new short[stringIDNum + 1];
1930             System.arraycopy(stringIDs, 0, table_stringIDs, 0, stringIDNum + 1);
1931 
1932             //(13)StringTable
1933             head[INDEX_stringTable] = (short)(head[INDEX_stringIDs] + stringIDNum + 1);
1934             table_stringTable = stringTable.toString().toCharArray();
1935             //(14)
1936             head[INDEX_TABLEEND] = (short)(head[INDEX_stringTable] + stringTable.length());
1937 
1938             //StringTable cache
1939             stringCache = new String[table_stringIDs.length];
1940         }
1941 
1942         //////////////////////////////////////////////
1943         private HashMap<String, Short> scriptIDs;
1944         //elc -> Encoding.Language.Country
1945         private HashMap<String, Short> elcIDs;
1946         //componentFontNameID starts from "1", "0" reserves for "undefined"
1947         private HashMap<String, Short> componentFontNameIDs;
1948         private HashMap<String, Short> fontfileNameIDs;
1949         private HashMap<String, Integer> logicalFontIDs;
1950         private HashMap<String, Integer> fontStyleIDs;
1951 
1952         //componentFontNameID -> fontfileNameID
1953         private HashMap<Short, Short>  filenames;
1954 
1955         //elcID -> allfonts/logicalFont -> scriptID list
1956         //(1)if we have a "allfonts", then the length of the
1957         //   value array is "1", otherwise it's 5, each font
1958         //   must have their own individual entry.
1959         //scriptID list "short[]" is stored as an ID
1960         private HashMap<Short, short[]> sequences;
1961 
1962         //scriptID ->logicFontID/fontStyleID->componentFontNameID,
1963         //a 20-entry array (5-name x 4-style) for each script
1964         private HashMap<Short, Short[]> scriptFonts;
1965 
1966         //scriptID -> componentFontNameID
1967         private HashMap<Short, Short> scriptAllfonts;
1968 
1969         //scriptID -> exclusionRanges[]
1970         private HashMap<Short, int[]> exclusions;
1971 
1972         //scriptID -> fontpath
1973         private HashMap<Short, Short> awtfontpaths;
1974 
1975         //fontID -> fontID
1976         private HashMap<Short, Short> proportionals;
1977 
1978         //scriptID -> componentFontNameID
1979         private HashMap<Short, Short> scriptAllfontsMotif;
1980 
1981         //scriptID ->logicFontID/fontStyleID->componentFontNameID,
1982         private HashMap<Short, Short[]> scriptFontsMotif;
1983 
1984         //elcID -> stringID of alphabetic/XXXX
1985         private HashMap<Short, Short> alphabeticSuffix;
1986 
1987         private short[] fallbackScriptIDs;
1988         private String version;
1989         private String appendedfontpath;
1990 
1991         private void initLogicalNameStyle() {
1992             logicalFontIDs = new HashMap<String, Integer>();
1993             fontStyleIDs = new HashMap<String, Integer>();
1994             logicalFontIDs.put("serif",      0);
1995             logicalFontIDs.put("sansserif",  1);
1996             logicalFontIDs.put("monospaced", 2);
1997             logicalFontIDs.put("dialog",     3);
1998             logicalFontIDs.put("dialoginput",4);
1999             fontStyleIDs.put("plain",      0);
2000             fontStyleIDs.put("bold",       1);
2001             fontStyleIDs.put("italic",     2);
2002             fontStyleIDs.put("bolditalic", 3);
2003         }
2004 
2005         private void initHashMaps() {
2006             scriptIDs = new HashMap<String, Short>();
2007             elcIDs = new HashMap<String, Short>();
2008             componentFontNameIDs = new HashMap<String, Short>();
2009             /*Init these tables to allow componentFontNameID, fontfileNameIDs
2010               to start from "1".
2011             */
2012             componentFontNameIDs.put("", Short.valueOf((short)0));
2013 
2014             fontfileNameIDs = new HashMap<String, Short>();
2015             filenames = new HashMap<Short, Short>();
2016             sequences = new HashMap<Short, short[]>();
2017             scriptFonts = new HashMap<Short, Short[]>();
2018             scriptAllfonts = new HashMap<Short, Short>();
2019             exclusions = new HashMap<Short, int[]>();
2020             awtfontpaths = new HashMap<Short, Short>();
2021             proportionals = new HashMap<Short, Short>();
2022             scriptFontsMotif = new HashMap<Short, Short[]>();
2023             scriptAllfontsMotif = new HashMap<Short, Short>();
2024             alphabeticSuffix = new HashMap<Short, Short>();
2025             fallbackScriptIDs = EMPTY_SHORT_ARRAY;
2026             /*
2027               version
2028               appendedfontpath
2029             */
2030         }
2031 
2032         private int[] parseExclusions(String key, String exclusions) {
2033             if (exclusions == null) {
2034                 return EMPTY_INT_ARRAY;
2035             }
2036             // range format is xxxx-XXXX,yyyyyy-YYYYYY,.....
2037             int numExclusions = 1;
2038             int pos = 0;
2039             while ((pos = exclusions.indexOf(',', pos)) != -1) {
2040                 numExclusions++;
2041                 pos++;
2042             }
2043             int[] exclusionRanges = new int[numExclusions * 2];
2044             pos = 0;
2045             int newPos = 0;
2046             for (int j = 0; j < numExclusions * 2; ) {
2047                 String lower, upper;
2048                 int lo = 0, up = 0;
2049                 try {
2050                     newPos = exclusions.indexOf('-', pos);
2051                     lower = exclusions.substring(pos, newPos);
2052                     pos = newPos + 1;
2053                     newPos = exclusions.indexOf(',', pos);
2054                     if (newPos == -1) {
2055                         newPos = exclusions.length();
2056                     }
2057                     upper = exclusions.substring(pos, newPos);
2058                     pos = newPos + 1;
2059                     int lowerLength = lower.length();
2060                     int upperLength = upper.length();
2061                     if (lowerLength != 4 && lowerLength != 6
2062                         || upperLength != 4 && upperLength != 6) {
2063                         throw new Exception();
2064                     }
2065                     lo = Integer.parseInt(lower, 16);
2066                     up = Integer.parseInt(upper, 16);
2067                     if (lo > up) {
2068                         throw new Exception();
2069                     }
2070                 } catch (Exception e) {
2071                     if (FontUtilities.debugFonts() && 
2072                         logger != null) {
2073                         logger.config("Failed parsing " + key +
2074                                   " property of font configuration.");
2075 
2076                     }
2077                     return EMPTY_INT_ARRAY;
2078                 }
2079                 exclusionRanges[j++] = lo;
2080                 exclusionRanges[j++] = up;
2081             }
2082             return exclusionRanges;
2083         }
2084 
2085         private Short getID(HashMap<String, Short> map, String key) {
2086             Short ret = map.get(key);
2087             if ( ret == null) {
2088                 map.put(key, (short)map.size());
2089                 return map.get(key);
2090             }
2091             return ret;
2092         }
2093 
2094         class FontProperties extends Properties {
2095             public synchronized Object put(Object k, Object v) {
2096                 parseProperty((String)k, (String)v);
2097                 return null;
2098             }
2099         }
2100 
2101         private void parseProperty(String key, String value) {
2102             if (key.startsWith("filename.")) {
2103                 //the only special case is "MingLiu_HKSCS" which has "_" in its
2104                 //facename, we dont want to replace the "_" with " "
2105                 key = key.substring(9);
2106                 if (!"MingLiU_HKSCS".equals(key)) {
2107                     key = key.replace('_', ' ');
2108                 }
2109                 Short faceID = getID(componentFontNameIDs, key);
2110                 Short fileID = getID(fontfileNameIDs, value);
2111                 //System.out.println("faceID=" + faceID + "/" + key + " -> "
2112                 //    + "fileID=" + fileID + "/" + value);
2113                 filenames.put(faceID, fileID);
2114             } else if (key.startsWith("exclusion.")) {
2115                 key = key.substring(10);
2116                 exclusions.put(getID(scriptIDs,key), parseExclusions(key,value));
2117             } else if (key.startsWith("sequence.")) {
2118                 key = key.substring(9);
2119                 boolean hasDefault = false;
2120                 boolean has1252 = false;
2121 
2122                 //get the scriptID list
2123                 String[] ss = (String[])splitSequence(value).toArray(EMPTY_STRING_ARRAY);
2124                 short [] sa = new short[ss.length];
2125                 for (int i = 0; i < ss.length; i++) {
2126                     if ("alphabetic/default".equals(ss[i])) {
2127                         //System.out.println(key + " -> " + ss[i]);
2128                         ss[i] = "alphabetic";
2129                         hasDefault = true;
2130                     } else if ("alphabetic/1252".equals(ss[i])) {
2131                         //System.out.println(key + " -> " + ss[i]);
2132                         ss[i] = "alphabetic";
2133                         has1252 = true;
2134                     }
2135                     sa[i] = getID(scriptIDs, ss[i]).shortValue();
2136                     //System.out.println("scriptID=" + si[i] + "/" + ss[i]);
2137                 }
2138                 //convert the "short[] -> string -> stringID"
2139                 short scriptArrayID = getShortArrayID(sa);
2140                 Short elcID = null;
2141                 int dot = key.indexOf('.');
2142                 if (dot == -1) {
2143                     if ("fallback".equals(key)) {
2144                         fallbackScriptIDs = sa;
2145                         return;
2146                     }
2147                     if ("allfonts".equals(key)) {
2148                         elcID = getID(elcIDs, "NULL.NULL.NULL");
2149                     } else {
2150                         if (logger != null) {
2151                             logger.config("Error sequence def: <sequence." + key + ">");
2152                         }
2153                         return;
2154                     }
2155                 } else {
2156                     elcID = getID(elcIDs, key.substring(dot + 1));
2157                     //System.out.println("elcID=" + elcID + "/" + key.substring(dot + 1));
2158                     key = key.substring(0, dot);
2159                 }
2160                 short[] scriptArrayIDs = null;
2161                 if ("allfonts".equals(key)) {
2162                     scriptArrayIDs = new short[1];
2163                     scriptArrayIDs[0] = scriptArrayID;
2164                 } else {
2165                     scriptArrayIDs = sequences.get(elcID);
2166                     if (scriptArrayIDs == null) {
2167                        scriptArrayIDs = new short[5];
2168                     }
2169                     Integer fid = logicalFontIDs.get(key);
2170                     if (fid == null) {
2171                         if (logger != null) {
2172                             logger.config("Unrecognizable logicfont name " + key);
2173                         }
2174                         return;
2175                     }
2176                     //System.out.println("sequence." + key + "/" + id);
2177                     scriptArrayIDs[fid.intValue()] = scriptArrayID;
2178                 }
2179                 sequences.put(elcID, scriptArrayIDs);
2180                 if (hasDefault) {
2181                     alphabeticSuffix.put(elcID, getStringID("default"));
2182                 } else
2183                 if (has1252) {
2184                     alphabeticSuffix.put(elcID, getStringID("1252"));
2185                 }
2186             } else if (key.startsWith("allfonts.")) {
2187                 key = key.substring(9);
2188                 if (key.endsWith(".motif")) {
2189                     key = key.substring(0, key.length() - 6);
2190                     //System.out.println("motif: all." + key + "=" + value);
2191                     scriptAllfontsMotif.put(getID(scriptIDs,key), getID(componentFontNameIDs,value));
2192                 } else {
2193                     scriptAllfonts.put(getID(scriptIDs,key), getID(componentFontNameIDs,value));
2194                 }
2195             } else if (key.startsWith("awtfontpath.")) {
2196                 key = key.substring(12);
2197                 //System.out.println("scriptID=" + getID(scriptIDs, key) + "/" + key);
2198                 awtfontpaths.put(getID(scriptIDs, key), getStringID(value));
2199             } else if ("version".equals(key)) {
2200                 version = value;
2201             } else if ("appendedfontpath".equals(key)) {
2202                 appendedfontpath = value;
2203             } else if (key.startsWith("proportional.")) {
2204                 key = key.substring(13).replace('_', ' ');
2205                 //System.out.println(key + "=" + value);
2206                 proportionals.put(getID(componentFontNameIDs, key),
2207                                   getID(componentFontNameIDs, value));
2208             } else {
2209                 //"name.style.script(.motif)", we dont care anything else
2210                 int dot1, dot2;
2211                 boolean isMotif = false;
2212 
2213                 dot1 = key.indexOf('.');
2214                 if (dot1 == -1) {
2215                     if (logger != null) {
2216                         logger.config("Failed parsing " + key +
2217                                   " property of font configuration.");
2218 
2219                     }
2220                     return;
2221                 }
2222                 dot2 = key.indexOf('.', dot1 + 1);
2223                 if (dot2 == -1) {
2224                     if (logger != null) {
2225                         logger.config("Failed parsing " + key +
2226                                   " property of font configuration.");
2227 
2228                     }
2229                     return;
2230                 }
2231                 if (key.endsWith(".motif")) {
2232                     key = key.substring(0, key.length() - 6);
2233                     isMotif = true;
2234                     //System.out.println("motif: " + key + "=" + value);
2235                 }
2236                 Integer nameID = logicalFontIDs.get(key.substring(0, dot1));
2237                 Integer styleID = fontStyleIDs.get(key.substring(dot1+1, dot2));
2238                 Short scriptID = getID(scriptIDs, key.substring(dot2 + 1));
2239                 if (nameID == null || styleID == null) {
2240                     if (logger != null) {
2241                         logger.config("unrecognizable logicfont name/style at " + key);
2242                     }
2243                     return;
2244                 }
2245                 Short[] pnids;
2246                 if (isMotif) {
2247                     pnids = scriptFontsMotif.get(scriptID);
2248                 } else {
2249                     pnids = scriptFonts.get(scriptID);
2250                 }
2251                 if (pnids == null) {
2252                     pnids =  new Short[20];
2253                 }
2254                 pnids[nameID.intValue() * NUM_STYLES + styleID.intValue()]
2255                   = getID(componentFontNameIDs, value);
2256                 /*
2257                 System.out.println("key=" + key + "/<" + nameID + "><" + styleID
2258                                      + "><" + scriptID + ">=" + value
2259                                      + "/" + getID(componentFontNameIDs, value));
2260                 */
2261                 if (isMotif) {
2262                     scriptFontsMotif.put(scriptID, pnids);
2263                 } else {
2264                     scriptFonts.put(scriptID, pnids);
2265                 }
2266             }
2267         }
2268     }
2269 }