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