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