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