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