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