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