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