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