| /* | |
| * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.awt; | |
| import java.awt.Font; | |
| import java.io.DataInputStream; | |
| import java.io.DataOutputStream; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.InputStream; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.nio.charset.Charset; | |
| import java.nio.charset.CharsetEncoder; | |
| import java.util.Arrays; | |
| import java.util.HashMap; | |
| import java.util.HashSet; | |
| import java.util.Hashtable; | |
| import java.util.Locale; | |
| import java.util.Map.Entry; | |
| import java.util.Properties; | |
| import java.util.Set; | |
| import java.util.Vector; | |
| import sun.font.CompositeFontDescriptor; | |
| import sun.font.SunFontManager; | |
| import sun.font.FontUtilities; | |
| import sun.util.logging.PlatformLogger; | |
| /** | |
| * Provides the definitions of the five logical fonts: Serif, SansSerif, | |
| * Monospaced, Dialog, and DialogInput. The necessary information | |
| * is obtained from fontconfig files. | |
| */ | |
| public abstract class FontConfiguration { | |
| //static global runtime env | |
| protected static String osVersion; | |
| protected static String osName; | |
| protected static String encoding; // canonical name of default nio charset | |
| protected static Locale startupLocale = null; | |
| protected static Hashtable<String, String> localeMap = null; | |
| private static FontConfiguration fontConfig; | |
| private static PlatformLogger logger; | |
| protected static boolean isProperties = true; | |
| protected SunFontManager fontManager; | |
| protected boolean preferLocaleFonts; | |
| protected boolean preferPropFonts; | |
| private File fontConfigFile; | |
| private boolean foundOsSpecificFile; | |
| private boolean inited; | |
| private String javaLib; | |
| /* A default FontConfiguration must be created before an alternate | |
| * one to ensure proper static initialisation takes place. | |
| */ | |
| public FontConfiguration(SunFontManager fm) { | |
| if (FontUtilities.debugFonts()) { | |
| FontUtilities.logInfo("Creating standard Font Configuration"); | |
| } | |
| if (FontUtilities.debugFonts() && logger == null) { | |
| logger = PlatformLogger.getLogger("sun.awt.FontConfiguration"); | |
| } | |
| fontManager = fm; | |
| setOsNameAndVersion(); /* static initialization */ | |
| setEncoding(); /* static initialization */ | |
| /* Separating out the file location from the rest of the | |
| * initialisation, so the caller has the option of doing | |
| * something else if a suitable file isn't found. | |
| */ | |
| findFontConfigFile(); | |
| } | |
| public synchronized boolean init() { | |
| if (!inited) { | |
| this.preferLocaleFonts = false; | |
| this.preferPropFonts = false; | |
| setFontConfiguration(); | |
| readFontConfigFile(fontConfigFile); | |
| initFontConfig(); | |
| inited = true; | |
| } | |
| return true; | |
| } | |
| public FontConfiguration(SunFontManager fm, | |
| boolean preferLocaleFonts, | |
| boolean preferPropFonts) { | |
| fontManager = fm; | |
| if (FontUtilities.debugFonts()) { | |
| FontUtilities.logInfo("Creating alternate Font Configuration"); | |
| } | |
| this.preferLocaleFonts = preferLocaleFonts; | |
| this.preferPropFonts = preferPropFonts; | |
| /* fontConfig should be initialised by default constructor, and | |
| * its data tables can be shared, since readFontConfigFile doesn't | |
| * update any other state. | |
| */ | |
| initFontConfig(); | |
| } | |
| /** | |
| * Fills in this instance's osVersion and osName members. By | |
| * default uses the system properties os.name and os.version; | |
| * subclasses may override. | |
| */ | |
| protected void setOsNameAndVersion() { | |
| osName = System.getProperty("os.name"); | |
| osVersion = System.getProperty("os.version"); | |
| } | |
| private void setEncoding() { | |
| encoding = Charset.defaultCharset().name(); | |
| startupLocale = SunToolkit.getStartupLocale(); | |
| } | |
| ///////////////////////////////////////////////////////////////////// | |
| // methods for loading the FontConfig file // | |
| ///////////////////////////////////////////////////////////////////// | |
| public boolean foundOsSpecificFile() { | |
| return foundOsSpecificFile; | |
| } | |
| /* Smoke test to see if we can trust this configuration by testing if | |
| * the first slot of a composite font maps to an installed file. | |
| */ | |
| public boolean fontFilesArePresent() { | |
| init(); | |
| short fontNameID = compFontNameIDs[0][0][0]; | |
| short fileNameID = getComponentFileID(fontNameID); | |
| final String fileName = mapFileName(getComponentFileName(fileNameID)); | |
| File f = new File(fileName); | |
| return f.exists(); | |
| } | |
| private void findFontConfigFile() { | |
| foundOsSpecificFile = true; // default assumption. | |
| String javaHome = System.getProperty("java.home"); | |
| if (javaHome == null) { | |
| throw new Error("java.home property not set"); | |
| } | |
| javaLib = javaHome + File.separator + "lib"; | |
| String javaConfFonts = javaHome + | |
| File.separator + "conf" + | |
| File.separator + "fonts"; | |
| String userConfigFile = System.getProperty("sun.awt.fontconfig"); | |
| if (userConfigFile != null) { | |
| fontConfigFile = new File(userConfigFile); | |
| } else { | |
| fontConfigFile = findFontConfigFile(javaConfFonts); | |
| if (fontConfigFile == null) { | |
| fontConfigFile = findFontConfigFile(javaLib); | |
| } | |
| } | |
| } | |
| private void readFontConfigFile(File f) { | |
| /* This is invoked here as readFontConfigFile is only invoked | |
| * once per VM, and always in a privileged context, thus the | |
| * directory containing installed fall back fonts is accessed | |
| * from this context | |
| */ | |
| getInstalledFallbackFonts(javaLib); | |
| if (f != null) { | |
| try (FileInputStream in = new FileInputStream(f.getPath())) { | |
| if (isProperties) { | |
| loadProperties(in); | |
| } else { | |
| loadBinary(in); | |
| } | |
| if (FontUtilities.debugFonts()) { | |
| logger.config("Read logical font configuration from " + f); | |
| } | |
| } catch (IOException e) { | |
| if (FontUtilities.debugFonts()) { | |
| logger.config("Failed to read logical font configuration from " + f); | |
| } | |
| } | |
| } | |
| String version = getVersion(); | |
| if (!"1".equals(version) && FontUtilities.debugFonts()) { | |
| logger.config("Unsupported fontconfig version: " + version); | |
| } | |
| } | |
| protected void getInstalledFallbackFonts(String javaLib) { | |
| String fallbackDirName = javaLib + File.separator + | |
| "fonts" + File.separator + "fallback"; | |
| File fallbackDir = new File(fallbackDirName); | |
| if (fallbackDir.exists() && fallbackDir.isDirectory()) { | |
| String[] ttfs = fallbackDir.list(fontManager.getTrueTypeFilter()); | |
| String[] t1s = fallbackDir.list(fontManager.getType1Filter()); | |
| int numTTFs = (ttfs == null) ? 0 : ttfs.length; | |
| int numT1s = (t1s == null) ? 0 : t1s.length; | |
| int len = numTTFs + numT1s; | |
| if (numTTFs + numT1s == 0) { | |
| return; | |
| } | |
| installedFallbackFontFiles = new String[len]; | |
| for (int i=0; i<numTTFs; i++) { | |
| installedFallbackFontFiles[i] = | |
| fallbackDir + File.separator + ttfs[i]; | |
| } | |
| for (int i=0; i<numT1s; i++) { | |
| installedFallbackFontFiles[i+numTTFs] = | |
| fallbackDir + File.separator + t1s[i]; | |
| } | |
| fontManager.registerFontsInDir(fallbackDirName); | |
| } | |
| } | |
| private File findImpl(String fname) { | |
| File f = new File(fname + ".properties"); | |
| if (FontUtilities.debugFonts()) { | |
| logger.info("Looking for text fontconfig file : " + f); | |
| } | |
| if (f.canRead()) { | |
| if (FontUtilities.debugFonts()) { | |
| logger.info("Found file : " + f); | |
| } | |
| isProperties = true; | |
| return f; | |
| } | |
| f = new File(fname + ".bfc"); | |
| if (FontUtilities.debugFonts()) { | |
| logger.info("Looking for binary fontconfig file : " + f); | |
| } | |
| if (f.canRead()) { | |
| if (FontUtilities.debugFonts()) { | |
| logger.info("Found file : " + f); | |
| } | |
| isProperties = false; | |
| return f; | |
| } | |
| return null; | |
| } | |
| private File findFontConfigFile(String dir) { | |
| if (!(new File(dir)).exists()) { | |
| return null; | |
| } | |
| String baseName = dir + File.separator + "fontconfig"; | |
| File configFile; | |
| String osMajorVersion = null; | |
| if (osVersion != null && osName != null) { | |
| configFile = findImpl(baseName + "." + osName + "." + osVersion); | |
| if (configFile != null) { | |
| return configFile; | |
| } | |
| int decimalPointIndex = osVersion.indexOf('.'); | |
| if (decimalPointIndex != -1) { | |
| osMajorVersion = osVersion.substring(0, osVersion.indexOf('.')); | |
| configFile = findImpl(baseName + "." + osName + "." + osMajorVersion); | |
| if (configFile != null) { | |
| return configFile; | |
| } | |
| } | |
| } | |
| if (osName != null) { | |
| configFile = findImpl(baseName + "." + osName); | |
| if (configFile != null) { | |
| return configFile; | |
| } | |
| } | |
| if (osVersion != null) { | |
| configFile = findImpl(baseName + "." + osVersion); | |
| if (configFile != null) { | |
| return configFile; | |
| } | |
| if (osMajorVersion != null) { | |
| configFile = findImpl(baseName + "." + osMajorVersion); | |
| if (configFile != null) { | |
| return configFile; | |
| } | |
| } | |
| } | |
| foundOsSpecificFile = false; | |
| configFile = findImpl(baseName); | |
| if (configFile != null) { | |
| return configFile; | |
| } | |
| if (FontUtilities.debugFonts()) { | |
| logger.info("Did not find a fontconfig file."); | |
| } | |
| return null; | |
| } | |
| /* Initialize the internal data tables from binary format font | |
| * configuration file. | |
| */ | |
| public static void loadBinary(InputStream inStream) throws IOException { | |
| DataInputStream in = new DataInputStream(inStream); | |
| head = readShortTable(in, HEAD_LENGTH); | |
| int[] tableSizes = new int[INDEX_TABLEEND]; | |
| for (int i = 0; i < INDEX_TABLEEND; i++) { | |
| tableSizes[i] = head[i + 1] - head[i]; | |
| } | |
| table_scriptIDs = readShortTable(in, tableSizes[INDEX_scriptIDs]); | |
| table_scriptFonts = readShortTable(in, tableSizes[INDEX_scriptFonts]); | |
| table_elcIDs = readShortTable(in, tableSizes[INDEX_elcIDs]); | |
| table_sequences = readShortTable(in, tableSizes[INDEX_sequences]); | |
| table_fontfileNameIDs = readShortTable(in, tableSizes[INDEX_fontfileNameIDs]); | |
| table_componentFontNameIDs = readShortTable(in, tableSizes[INDEX_componentFontNameIDs]); | |
| table_filenames = readShortTable(in, tableSizes[INDEX_filenames]); | |
| table_awtfontpaths = readShortTable(in, tableSizes[INDEX_awtfontpaths]); | |
| table_exclusions = readShortTable(in, tableSizes[INDEX_exclusions]); | |
| table_proportionals = readShortTable(in, tableSizes[INDEX_proportionals]); | |
| table_scriptFontsMotif = readShortTable(in, tableSizes[INDEX_scriptFontsMotif]); | |
| table_alphabeticSuffix = readShortTable(in, tableSizes[INDEX_alphabeticSuffix]); | |
| table_stringIDs = readShortTable(in, tableSizes[INDEX_stringIDs]); | |
| //StringTable cache | |
| stringCache = new String[table_stringIDs.length + 1]; | |
| int len = tableSizes[INDEX_stringTable]; | |
| byte[] bb = new byte[len * 2]; | |
| table_stringTable = new char[len]; | |
| in.read(bb); | |
| int i = 0, j = 0; | |
| while (i < len) { | |
| table_stringTable[i++] = (char)(bb[j++] << 8 | (bb[j++] & 0xff)); | |
| } | |
| if (verbose) { | |
| dump(); | |
| } | |
| } | |
| /* Generate a binary format font configuration from internal data | |
| * tables. | |
| */ | |
| public static void saveBinary(OutputStream out) throws IOException { | |
| sanityCheck(); | |
| DataOutputStream dataOut = new DataOutputStream(out); | |
| writeShortTable(dataOut, head); | |
| writeShortTable(dataOut, table_scriptIDs); | |
| writeShortTable(dataOut, table_scriptFonts); | |
| writeShortTable(dataOut, table_elcIDs); | |
| writeShortTable(dataOut, table_sequences); | |
| writeShortTable(dataOut, table_fontfileNameIDs); | |
| writeShortTable(dataOut, table_componentFontNameIDs); | |
| writeShortTable(dataOut, table_filenames); | |
| writeShortTable(dataOut, table_awtfontpaths); | |
| writeShortTable(dataOut, table_exclusions); | |
| writeShortTable(dataOut, table_proportionals); | |
| writeShortTable(dataOut, table_scriptFontsMotif); | |
| writeShortTable(dataOut, table_alphabeticSuffix); | |
| writeShortTable(dataOut, table_stringIDs); | |
| //stringTable | |
| dataOut.writeChars(new String(table_stringTable)); | |
| out.close(); | |
| if (verbose) { | |
| dump(); | |
| } | |
| } | |
| //private static boolean loadingProperties; | |
| private static short stringIDNum; | |
| private static short[] stringIDs; | |
| private static StringBuilder stringTable; | |
| public static void loadProperties(InputStream in) throws IOException { | |
| //loadingProperties = true; | |
| //StringID starts from "1", "0" is reserved for "not defined" | |
| stringIDNum = 1; | |
| stringIDs = new short[1000]; | |
| stringTable = new StringBuilder(4096); | |
| if (verbose && logger == null) { | |
| logger = PlatformLogger.getLogger("sun.awt.FontConfiguration"); | |
| } | |
| new PropertiesHandler().load(in); | |
| //loadingProperties = false; | |
| stringIDs = null; | |
| stringTable = null; | |
| } | |
| ///////////////////////////////////////////////////////////////////// | |
| // methods for initializing the FontConfig // | |
| ///////////////////////////////////////////////////////////////////// | |
| /** | |
| * set initLocale, initEncoding and initELC for this FontConfig object | |
| * currently we just simply use the startup locale and encoding | |
| */ | |
| private void initFontConfig() { | |
| initLocale = startupLocale; | |
| initEncoding = encoding; | |
| if (preferLocaleFonts && !willReorderForStartupLocale()) { | |
| preferLocaleFonts = false; | |
| } | |
| initELC = getInitELC(); | |
| initAllComponentFonts(); | |
| } | |
| //"ELC" stands for "Encoding.Language.Country". This method returns | |
| //the ID of the matched elc setting of "initLocale" in elcIDs table. | |
| //If no match is found, it returns the default ID, which is | |
| //"NULL.NULL.NULL" in elcIDs table. | |
| private short getInitELC() { | |
| if (initELC != -1) { | |
| return initELC; | |
| } | |
| HashMap <String, Integer> elcIDs = new HashMap<String, Integer>(); | |
| for (int i = 0; i < table_elcIDs.length; i++) { | |
| elcIDs.put(getString(table_elcIDs[i]), i); | |
| } | |
| String language = initLocale.getLanguage(); | |
| String country = initLocale.getCountry(); | |
| String elc; | |
| if (elcIDs.containsKey(elc=initEncoding + "." + language + "." + country) | |
| || elcIDs.containsKey(elc=initEncoding + "." + language) | |
| || elcIDs.containsKey(elc=initEncoding)) { | |
| initELC = elcIDs.get(elc).shortValue(); | |
| } else { | |
| initELC = elcIDs.get("NULL.NULL.NULL").shortValue(); | |
| } | |
| int i = 0; | |
| while (i < table_alphabeticSuffix.length) { | |
| if (initELC == table_alphabeticSuffix[i]) { | |
| alphabeticSuffix = getString(table_alphabeticSuffix[i + 1]); | |
| return initELC; | |
| } | |
| i += 2; | |
| } | |
| return initELC; | |
| } | |
| public static boolean verbose; | |
| private short initELC = -1; | |
| private Locale initLocale; | |
| private String initEncoding; | |
| private String alphabeticSuffix; | |
| private short[][][] compFontNameIDs = new short[NUM_FONTS][NUM_STYLES][]; | |
| private int[][][] compExclusions = new int[NUM_FONTS][][]; | |
| private int[] compCoreNum = new int[NUM_FONTS]; | |
| private Set<Short> coreFontNameIDs = new HashSet<Short>(); | |
| private Set<Short> fallbackFontNameIDs = new HashSet<Short>(); | |
| private void initAllComponentFonts() { | |
| short[] fallbackScripts = getFallbackScripts(); | |
| for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) { | |
| short[] coreScripts = getCoreScripts(fontIndex); | |
| compCoreNum[fontIndex] = coreScripts.length; | |
| /* | |
| System.out.println("coreScriptID=" + table_sequences[initELC * 5 + fontIndex]); | |
| for (int i = 0; i < coreScripts.length; i++) { | |
| System.out.println(" " + i + " :" + getString(table_scriptIDs[coreScripts[i]])); | |
| } | |
| */ | |
| //init exclusionRanges | |
| int[][] exclusions = new int[coreScripts.length][]; | |
| for (int i = 0; i < coreScripts.length; i++) { | |
| exclusions[i] = getExclusionRanges(coreScripts[i]); | |
| } | |
| compExclusions[fontIndex] = exclusions; | |
| //init componentFontNames | |
| for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) { | |
| int index; | |
| short[] nameIDs = new short[coreScripts.length + fallbackScripts.length]; | |
| //core | |
| for (index = 0; index < coreScripts.length; index++) { | |
| nameIDs[index] = getComponentFontID(coreScripts[index], | |
| fontIndex, styleIndex); | |
| if (preferLocaleFonts && localeMap != null && | |
| fontManager.usingAlternateFontforJALocales()) { | |
| nameIDs[index] = remapLocaleMap(fontIndex, styleIndex, | |
| coreScripts[index], nameIDs[index]); | |
| } | |
| if (preferPropFonts) { | |
| nameIDs[index] = remapProportional(fontIndex, nameIDs[index]); | |
| } | |
| //System.out.println("nameid=" + nameIDs[index]); | |
| coreFontNameIDs.add(nameIDs[index]); | |
| } | |
| //fallback | |
| for (int i = 0; i < fallbackScripts.length; i++) { | |
| short id = getComponentFontID(fallbackScripts[i], | |
| fontIndex, styleIndex); | |
| if (preferLocaleFonts && localeMap != null && | |
| fontManager.usingAlternateFontforJALocales()) { | |
| id = remapLocaleMap(fontIndex, styleIndex, fallbackScripts[i], id); | |
| } | |
| if (preferPropFonts) { | |
| id = remapProportional(fontIndex, id); | |
| } | |
| if (contains(nameIDs, id, index)) { | |
| continue; | |
| } | |
| /* | |
| System.out.println("fontIndex=" + fontIndex + ", styleIndex=" + styleIndex | |
| + ", fbIndex=" + i + ",fbS=" + fallbackScripts[i] + ", id=" + id); | |
| */ | |
| fallbackFontNameIDs.add(id); | |
| nameIDs[index++] = id; | |
| } | |
| if (index < nameIDs.length) { | |
| short[] newNameIDs = new short[index]; | |
| System.arraycopy(nameIDs, 0, newNameIDs, 0, index); | |
| nameIDs = newNameIDs; | |
| } | |
| compFontNameIDs[fontIndex][styleIndex] = nameIDs; | |
| } | |
| } | |
| } | |
| private short remapLocaleMap(int fontIndex, int styleIndex, short scriptID, short fontID) { | |
| String scriptName = getString(table_scriptIDs[scriptID]); | |
| String value = localeMap.get(scriptName); | |
| if (value == null) { | |
| String fontName = fontNames[fontIndex]; | |
| String styleName = styleNames[styleIndex]; | |
| value = localeMap.get(fontName + "." + styleName + "." + scriptName); | |
| } | |
| if (value == null) { | |
| return fontID; | |
| } | |
| for (int i = 0; i < table_componentFontNameIDs.length; i++) { | |
| String name = getString(table_componentFontNameIDs[i]); | |
| if (value.equalsIgnoreCase(name)) { | |
| fontID = (short)i; | |
| break; | |
| } | |
| } | |
| return fontID; | |
| } | |
| public static boolean hasMonoToPropMap() { | |
| return table_proportionals != null && table_proportionals.length != 0; | |
| } | |
| private short remapProportional(int fontIndex, short id) { | |
| if (preferPropFonts && | |
| table_proportionals.length != 0 && | |
| fontIndex != 2 && //"monospaced" | |
| fontIndex != 4) { //"dialoginput" | |
| int i = 0; | |
| while (i < table_proportionals.length) { | |
| if (table_proportionals[i] == id) { | |
| return table_proportionals[i + 1]; | |
| } | |
| i += 2; | |
| } | |
| } | |
| return id; | |
| } | |
| ///////////////////////////////////////////////////////////////////// | |
| // Methods for handling font and style names // | |
| ///////////////////////////////////////////////////////////////////// | |
| protected static final int NUM_FONTS = 5; | |
| protected static final int NUM_STYLES = 4; | |
| protected static final String[] fontNames | |
| = {"serif", "sansserif", "monospaced", "dialog", "dialoginput"}; | |
| protected static final String[] publicFontNames | |
| = {Font.SERIF, Font.SANS_SERIF, Font.MONOSPACED, Font.DIALOG, | |
| Font.DIALOG_INPUT}; | |
| protected static final String[] styleNames | |
| = {"plain", "bold", "italic", "bolditalic"}; | |
| /** | |
| * Checks whether the given font family name is a valid logical font name. | |
| * The check is case insensitive. | |
| */ | |
| public static boolean isLogicalFontFamilyName(String fontName) { | |
| return isLogicalFontFamilyNameLC(fontName.toLowerCase(Locale.ENGLISH)); | |
| } | |
| /** | |
| * Checks whether the given font family name is a valid logical font name. | |
| * The check is case sensitive. | |
| */ | |
| public static boolean isLogicalFontFamilyNameLC(String fontName) { | |
| for (int i = 0; i < fontNames.length; i++) { | |
| if (fontName.equals(fontNames[i])) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Checks whether the given style name is a valid logical font style name. | |
| */ | |
| private static boolean isLogicalFontStyleName(String styleName) { | |
| for (int i = 0; i < styleNames.length; i++) { | |
| if (styleName.equals(styleNames[i])) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Checks whether the given font face name is a valid logical font name. | |
| * The check is case insensitive. | |
| */ | |
| public static boolean isLogicalFontFaceName(String fontName) { | |
| return isLogicalFontFaceNameLC(fontName.toLowerCase(Locale.ENGLISH)); | |
| } | |
| /** | |
| * Checks whether the given font face name is a valid logical font name. | |
| * The check is case sensitive. | |
| */ | |
| public static boolean isLogicalFontFaceNameLC(String fontName) { | |
| int period = fontName.indexOf('.'); | |
| if (period >= 0) { | |
| String familyName = fontName.substring(0, period); | |
| String styleName = fontName.substring(period + 1); | |
| return isLogicalFontFamilyName(familyName) && | |
| isLogicalFontStyleName(styleName); | |
| } else { | |
| return isLogicalFontFamilyName(fontName); | |
| } | |
| } | |
| protected static int getFontIndex(String fontName) { | |
| return getArrayIndex(fontNames, fontName); | |
| } | |
| protected static int getStyleIndex(String styleName) { | |
| return getArrayIndex(styleNames, styleName); | |
| } | |
| private static int getArrayIndex(String[] names, String name) { | |
| for (int i = 0; i < names.length; i++) { | |
| if (name.equals(names[i])) { | |
| return i; | |
| } | |
| } | |
| assert false; | |
| return 0; | |
| } | |
| protected static int getStyleIndex(int style) { | |
| switch (style) { | |
| case Font.PLAIN: | |
| return 0; | |
| case Font.BOLD: | |
| return 1; | |
| case Font.ITALIC: | |
| return 2; | |
| case Font.BOLD | Font.ITALIC: | |
| return 3; | |
| default: | |
| return 0; | |
| } | |
| } | |
| protected static String getFontName(int fontIndex) { | |
| return fontNames[fontIndex]; | |
| } | |
| protected static String getStyleName(int styleIndex) { | |
| return styleNames[styleIndex]; | |
| } | |
| /** | |
| * Returns the font face name for the given logical font | |
| * family name and style. | |
| * The style argument is interpreted as in java.awt.Font.Font. | |
| */ | |
| public static String getLogicalFontFaceName(String familyName, int style) { | |
| assert isLogicalFontFamilyName(familyName); | |
| return familyName.toLowerCase(Locale.ENGLISH) + "." + getStyleString(style); | |
| } | |
| /** | |
| * Returns the string typically used in properties files | |
| * for the given style. | |
| * The style argument is interpreted as in java.awt.Font.Font. | |
| */ | |
| public static String getStyleString(int style) { | |
| return getStyleName(getStyleIndex(style)); | |
| } | |
| /** | |
| * Returns a fallback name for the given font name. For a few known | |
| * font names, matching logical font names are returned. For all | |
| * other font names, defaultFallback is returned. | |
| * defaultFallback differs between AWT and 2D. | |
| */ | |
| public abstract String getFallbackFamilyName(String fontName, String defaultFallback); | |
| /** | |
| * Returns the 1.1 equivalent for some old 1.0 font family names for | |
| * which we need to maintain compatibility in some configurations. | |
| * Returns null for other font names. | |
| */ | |
| protected String getCompatibilityFamilyName(String fontName) { | |
| fontName = fontName.toLowerCase(Locale.ENGLISH); | |
| if (fontName.equals("timesroman")) { | |
| return "serif"; | |
| } else if (fontName.equals("helvetica")) { | |
| return "sansserif"; | |
| } else if (fontName.equals("courier")) { | |
| return "monospaced"; | |
| } | |
| return null; | |
| } | |
| protected static String[] installedFallbackFontFiles = null; | |
| /** | |
| * Maps a file name given in the font configuration file | |
| * to a format appropriate for the platform. | |
| */ | |
| protected String mapFileName(String fileName) { | |
| return fileName; | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| // reordering // | |
| ////////////////////////////////////////////////////////////////////// | |
| /* Mappings from file encoding to font config name for font supporting | |
| * the corresponding language. This is filled in by initReorderMap() | |
| */ | |
| protected HashMap<String, Object> reorderMap = null; | |
| /* Platform-specific mappings */ | |
| protected abstract void initReorderMap(); | |
| /* Move item at index "src" to "dst", shuffling all values in | |
| * between down | |
| */ | |
| private void shuffle(String[] seq, int src, int dst) { | |
| if (dst >= src) { | |
| return; | |
| } | |
| String tmp = seq[src]; | |
| for (int i=src; i>dst; i--) { | |
| seq[i] = seq[i-1]; | |
| } | |
| seq[dst] = tmp; | |
| } | |
| /* Called to determine if there's a re-order sequence for this locale/ | |
| * encoding. If there's none then the caller can "bail" and avoid | |
| * unnecessary work | |
| */ | |
| public static boolean willReorderForStartupLocale() { | |
| return getReorderSequence() != null; | |
| } | |
| private static Object getReorderSequence() { | |
| if (fontConfig.reorderMap == null) { | |
| fontConfig.initReorderMap(); | |
| } | |
| HashMap<String, Object> reorderMap = fontConfig.reorderMap; | |
| /* Find the most specific mapping */ | |
| String language = startupLocale.getLanguage(); | |
| String country = startupLocale.getCountry(); | |
| Object val = reorderMap.get(encoding + "." + language + "." + country); | |
| if (val == null) { | |
| val = reorderMap.get(encoding + "." + language); | |
| } | |
| if (val == null) { | |
| val = reorderMap.get(encoding); | |
| } | |
| return val; | |
| } | |
| /* This method reorders the sequence such that the matches for the | |
| * file encoding are moved ahead of other elements. | |
| * If an encoding uses more than one font, they are all moved up. | |
| */ | |
| private void reorderSequenceForLocale(String[] seq) { | |
| Object val = getReorderSequence(); | |
| if (val instanceof String) { | |
| for (int i=0; i< seq.length; i++) { | |
| if (seq[i].equals(val)) { | |
| shuffle(seq, i, 0); | |
| return; | |
| } | |
| } | |
| } else if (val instanceof String[]) { | |
| String[] fontLangs = (String[])val; | |
| for (int l=0; l<fontLangs.length;l++) { | |
| for (int i=0; i<seq.length;i++) { | |
| if (seq[i].equals(fontLangs[l])) { | |
| shuffle(seq, i, l); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| private static Vector<String> splitSequence(String sequence) { | |
| //String.split would be more convenient, but incurs big performance penalty | |
| Vector<String> parts = new Vector<>(); | |
| int start = 0; | |
| int end; | |
| while ((end = sequence.indexOf(',', start)) >= 0) { | |
| parts.add(sequence.substring(start, end)); | |
| start = end + 1; | |
| } | |
| if (sequence.length() > start) { | |
| parts.add(sequence.substring(start)); | |
| } | |
| return parts; | |
| } | |
| //////////////////////////////////////////////////////////////////////// | |
| // Methods for extracting information from the fontconfig data for AWT// | |
| //////////////////////////////////////////////////////////////////////// | |
| private Hashtable<String, Charset> charsetRegistry = new Hashtable<>(5); | |
| /** | |
| * Returns FontDescriptors describing the physical fonts used for the | |
| * given logical font name and style. The font name is interpreted | |
| * in a case insensitive way. | |
| * The style argument is interpreted as in java.awt.Font.Font. | |
| */ | |
| public FontDescriptor[] getFontDescriptors(String fontName, int style) { | |
| assert isLogicalFontFamilyName(fontName); | |
| fontName = fontName.toLowerCase(Locale.ENGLISH); | |
| int fontIndex = getFontIndex(fontName); | |
| int styleIndex = getStyleIndex(style); | |
| return getFontDescriptors(fontIndex, styleIndex); | |
| } | |
| private FontDescriptor[][][] fontDescriptors = | |
| new FontDescriptor[NUM_FONTS][NUM_STYLES][]; | |
| private FontDescriptor[] getFontDescriptors(int fontIndex, int styleIndex) { | |
| FontDescriptor[] descriptors = fontDescriptors[fontIndex][styleIndex]; | |
| if (descriptors == null) { | |
| descriptors = buildFontDescriptors(fontIndex, styleIndex); | |
| fontDescriptors[fontIndex][styleIndex] = descriptors; | |
| } | |
| return descriptors; | |
| } | |
| protected FontDescriptor[] buildFontDescriptors(int fontIndex, int styleIndex) { | |
| String fontName = fontNames[fontIndex]; | |
| String styleName = styleNames[styleIndex]; | |
| short[] scriptIDs = getCoreScripts(fontIndex); | |
| short[] nameIDs = compFontNameIDs[fontIndex][styleIndex]; | |
| String[] sequence = new String[scriptIDs.length]; | |
| String[] names = new String[scriptIDs.length]; | |
| for (int i = 0; i < sequence.length; i++) { | |
| names[i] = getComponentFontName(nameIDs[i]); | |
| sequence[i] = getScriptName(scriptIDs[i]); | |
| if (alphabeticSuffix != null && "alphabetic".equals(sequence[i])) { | |
| sequence[i] = sequence[i] + "/" + alphabeticSuffix; | |
| } | |
| } | |
| int[][] fontExclusionRanges = compExclusions[fontIndex]; | |
| FontDescriptor[] descriptors = new FontDescriptor[names.length]; | |
| for (int i = 0; i < names.length; i++) { | |
| String awtFontName; | |
| String encoding; | |
| awtFontName = makeAWTFontName(names[i], sequence[i]); | |
| // look up character encoding | |
| encoding = getEncoding(names[i], sequence[i]); | |
| if (encoding == null) { | |
| encoding = "default"; | |
| } | |
| CharsetEncoder enc | |
| = getFontCharsetEncoder(encoding.trim(), awtFontName); | |
| // we already have the exclusion ranges | |
| int[] exclusionRanges = fontExclusionRanges[i]; | |
| // create descriptor | |
| descriptors[i] = new FontDescriptor(awtFontName, enc, exclusionRanges); | |
| } | |
| return descriptors; | |
| } | |
| /** | |
| * Returns the AWT font name for the given platform font name and | |
| * character subset. | |
| */ | |
| protected String makeAWTFontName(String platformFontName, | |
| String characterSubsetName) { | |
| return platformFontName; | |
| } | |
| /** | |
| * Returns the java.io name of the platform character encoding for the | |
| * given AWT font name and character subset. May return "default" | |
| * to indicate that getDefaultFontCharset should be called to obtain | |
| * a charset encoder. | |
| */ | |
| protected abstract String getEncoding(String awtFontName, | |
| String characterSubsetName); | |
| private CharsetEncoder getFontCharsetEncoder(final String charsetName, | |
| String fontName) { | |
| Charset fc = null; | |
| if (charsetName.equals("default")) { | |
| fc = charsetRegistry.get(fontName); | |
| } else { | |
| fc = charsetRegistry.get(charsetName); | |
| } | |
| if (fc != null) { | |
| return fc.newEncoder(); | |
| } | |
| if (!charsetName.startsWith("sun.awt.") && | |
| !charsetName.equals("default") && | |
| !charsetName.startsWith("sun.font.")) { | |
| fc = Charset.forName(charsetName); | |
| } else { | |
| Class<?> fcc = null; | |
| try { | |
| fcc = Class.forName(charsetName, true, ClassLoader.getSystemClassLoader()); | |
| } catch (ClassNotFoundException e) { | |
| } | |
| if (fcc != null) { | |
| try { | |
| fc = (Charset) fcc.getDeclaredConstructor().newInstance(); | |
| } catch (Exception e) { | |
| } | |
| } | |
| } | |
| if (fc == null) { | |
| fc = getDefaultFontCharset(fontName); | |
| } | |
| if (charsetName.equals("default")){ | |
| charsetRegistry.put(fontName, fc); | |
| } else { | |
| charsetRegistry.put(charsetName, fc); | |
| } | |
| return fc.newEncoder(); | |
| } | |
| protected abstract Charset getDefaultFontCharset( | |
| String fontName); | |
| /* This retrieves the platform font directories (path) calculated | |
| * by setAWTFontPathSequence(String[]). The default implementation | |
| * returns null, its expected that X11 platforms may return | |
| * non-null. | |
| */ | |
| public HashSet<String> getAWTFontPathSet() { | |
| return null; | |
| } | |
| //////////////////////////////////////////////////////////////////////// | |
| // methods for extracting information from the fontconfig data for 2D // | |
| //////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Returns an array of composite font descriptors for all logical font | |
| * faces. | |
| */ | |
| public CompositeFontDescriptor[] get2DCompositeFontInfo() { | |
| CompositeFontDescriptor[] result = | |
| new CompositeFontDescriptor[NUM_FONTS * NUM_STYLES]; | |
| String defaultFontFile = fontManager.getDefaultFontFile(); | |
| String defaultFontFaceName = fontManager.getDefaultFontFaceName(); | |
| for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) { | |
| String fontName = publicFontNames[fontIndex]; | |
| // determine exclusion ranges for font | |
| // AWT uses separate exclusion range array per component font. | |
| // 2D packs all range boundaries into one array. | |
| // Both use separate entries for lower and upper boundary. | |
| int[][] exclusions = compExclusions[fontIndex]; | |
| int numExclusionRanges = 0; | |
| for (int i = 0; i < exclusions.length; i++) { | |
| numExclusionRanges += exclusions[i].length; | |
| } | |
| int[] exclusionRanges = new int[numExclusionRanges]; | |
| int[] exclusionRangeLimits = new int[exclusions.length]; | |
| int exclusionRangeIndex = 0; | |
| int exclusionRangeLimitIndex = 0; | |
| for (int i = 0; i < exclusions.length; i++) { | |
| int[] componentRanges = exclusions[i]; | |
| for (int j = 0; j < componentRanges.length; ) { | |
| int value = componentRanges[j]; | |
| exclusionRanges[exclusionRangeIndex++] = componentRanges[j++]; | |
| exclusionRanges[exclusionRangeIndex++] = componentRanges[j++]; | |
| } | |
| exclusionRangeLimits[i] = exclusionRangeIndex; | |
| } | |
| // other info is per style | |
| for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) { | |
| int maxComponentFontCount = compFontNameIDs[fontIndex][styleIndex].length; | |
| // fall back fonts listed in the lib/fonts/fallback directory | |
| if (installedFallbackFontFiles != null) { | |
| maxComponentFontCount += installedFallbackFontFiles.length; | |
| } | |
| String faceName = fontName + "." + styleNames[styleIndex]; | |
| // determine face names and file names of component fonts | |
| String[] componentFaceNames = new String[maxComponentFontCount]; | |
| String[] componentFileNames = new String[maxComponentFontCount]; | |
| int index; | |
| for (index = 0; index < compFontNameIDs[fontIndex][styleIndex].length; index++) { | |
| short fontNameID = compFontNameIDs[fontIndex][styleIndex][index]; | |
| short fileNameID = getComponentFileID(fontNameID); | |
| componentFaceNames[index] = getFaceNameFromComponentFontName(getComponentFontName(fontNameID)); | |
| componentFileNames[index] = mapFileName(getComponentFileName(fileNameID)); | |
| if (componentFileNames[index] == null || | |
| needToSearchForFile(componentFileNames[index])) { | |
| componentFileNames[index] = getFileNameFromComponentFontName(getComponentFontName(fontNameID)); | |
| } | |
| /* | |
| System.out.println(publicFontNames[fontIndex] + "." + styleNames[styleIndex] + "." | |
| + getString(table_scriptIDs[coreScripts[index]]) + "=" + componentFileNames[index]); | |
| */ | |
| } | |
| if (installedFallbackFontFiles != null) { | |
| for (int ifb=0; ifb<installedFallbackFontFiles.length; ifb++) { | |
| componentFaceNames[index] = null; | |
| componentFileNames[index] = installedFallbackFontFiles[ifb]; | |
| index++; | |
| } | |
| } | |
| if (index < maxComponentFontCount) { | |
| String[] newComponentFaceNames = new String[index]; | |
| System.arraycopy(componentFaceNames, 0, newComponentFaceNames, 0, index); | |
| componentFaceNames = newComponentFaceNames; | |
| String[] newComponentFileNames = new String[index]; | |
| System.arraycopy(componentFileNames, 0, newComponentFileNames, 0, index); | |
| componentFileNames = newComponentFileNames; | |
| } | |
| // exclusion range limit array length must match component face name | |
| // array length - native code relies on this | |
| int[] clippedExclusionRangeLimits = exclusionRangeLimits; | |
| if (index != clippedExclusionRangeLimits.length) { | |
| int len = exclusionRangeLimits.length; | |
| clippedExclusionRangeLimits = new int[index]; | |
| System.arraycopy(exclusionRangeLimits, 0, clippedExclusionRangeLimits, 0, len); | |
| //padding for various fallback fonts | |
| for (int i = len; i < index; i++) { | |
| clippedExclusionRangeLimits[i] = exclusionRanges.length; | |
| } | |
| } | |
| /* | |
| System.out.println(faceName + ":"); | |
| for (int i = 0; i < componentFileNames.length; i++) { | |
| System.out.println(" " + componentFaceNames[i] | |
| + " -> " + componentFileNames[i]); | |
| } | |
| */ | |
| result[fontIndex * NUM_STYLES + styleIndex] | |
| = new CompositeFontDescriptor( | |
| faceName, | |
| compCoreNum[fontIndex], | |
| componentFaceNames, | |
| componentFileNames, | |
| exclusionRanges, | |
| clippedExclusionRangeLimits); | |
| } | |
| } | |
| return result; | |
| } | |
| protected abstract String getFaceNameFromComponentFontName(String componentFontName); | |
| protected abstract String getFileNameFromComponentFontName(String componentFontName); | |
| /* | |
| public class 2dFont { | |
| public String platformName; | |
| public String fontfileName; | |
| } | |
| private 2dFont [] componentFonts = null; | |
| */ | |
| /* Used on Linux to test if a file referenced in a font configuration | |
| * file exists in the location that is expected. If it does, no need | |
| * to search for it. If it doesn't then unless its a fallback font, | |
| * return that expensive code should be invoked to search for the font. | |
| */ | |
| HashMap<String, Boolean> existsMap; | |
| public boolean needToSearchForFile(String fileName) { | |
| if (!FontUtilities.isLinux) { | |
| return false; | |
| } else if (existsMap == null) { | |
| existsMap = new HashMap<String, Boolean>(); | |
| } | |
| Boolean exists = existsMap.get(fileName); | |
| if (exists == null) { | |
| /* call getNumberCoreFonts() to ensure these are initialised, and | |
| * if this file isn't for a core component, ie, is a for a fallback | |
| * font which very typically isn't available, then can't afford | |
| * to take the start-up penalty to search for it. | |
| */ | |
| getNumberCoreFonts(); | |
| if (!coreFontFileNames.contains(fileName)) { | |
| exists = Boolean.TRUE; | |
| } else { | |
| exists = Boolean.valueOf((new File(fileName)).exists()); | |
| existsMap.put(fileName, exists); | |
| if (FontUtilities.debugFonts() && | |
| exists == Boolean.FALSE) { | |
| logger.warning("Couldn't locate font file " + fileName); | |
| } | |
| } | |
| } | |
| return exists == Boolean.FALSE; | |
| } | |
| private int numCoreFonts = -1; | |
| private String[] componentFonts = null; | |
| HashMap <String, String> filenamesMap = new HashMap<String, String>(); | |
| HashSet <String> coreFontFileNames = new HashSet<String>(); | |
| /* Return the number of core fonts. Note this isn't thread safe but | |
| * a calling thread can call this and getPlatformFontNames() in either | |
| * order. | |
| */ | |
| public int getNumberCoreFonts() { | |
| if (numCoreFonts == -1) { | |
| numCoreFonts = coreFontNameIDs.size(); | |
| Short[] emptyShortArray = new Short[0]; | |
| Short[] core = coreFontNameIDs.toArray(emptyShortArray); | |
| Short[] fallback = fallbackFontNameIDs.toArray(emptyShortArray); | |
| int numFallbackFonts = 0; | |
| int i; | |
| for (i = 0; i < fallback.length; i++) { | |
| if (coreFontNameIDs.contains(fallback[i])) { | |
| fallback[i] = null; | |
| continue; | |
| } | |
| numFallbackFonts++; | |
| } | |
| componentFonts = new String[numCoreFonts + numFallbackFonts]; | |
| String filename = null; | |
| for (i = 0; i < core.length; i++) { | |
| short fontid = core[i]; | |
| short fileid = getComponentFileID(fontid); | |
| componentFonts[i] = getComponentFontName(fontid); | |
| String compFileName = getComponentFileName(fileid); | |
| if (compFileName != null) { | |
| coreFontFileNames.add(compFileName); | |
| } | |
| filenamesMap.put(componentFonts[i], mapFileName(compFileName)); | |
| } | |
| for (int j = 0; j < fallback.length; j++) { | |
| if (fallback[j] != null) { | |
| short fontid = fallback[j]; | |
| short fileid = getComponentFileID(fontid); | |
| componentFonts[i] = getComponentFontName(fontid); | |
| filenamesMap.put(componentFonts[i], | |
| mapFileName(getComponentFileName(fileid))); | |
| i++; | |
| } | |
| } | |
| } | |
| return numCoreFonts; | |
| } | |
| /* Return all platform font names used by this font configuration. | |
| * The first getNumberCoreFonts() entries are guaranteed to be the | |
| * core fonts - ie no fall back only fonts. | |
| */ | |
| public String[] getPlatformFontNames() { | |
| if (numCoreFonts == -1) { | |
| getNumberCoreFonts(); | |
| } | |
| return componentFonts; | |
| } | |
| /** | |
| * Returns a file name for the physical font represented by this platform font name, | |
| * if the font configuration has such information available, or null if the | |
| * information is unavailable. The file name returned is just a hint; a null return | |
| * value doesn't necessarily mean that the font is unavailable, nor does a non-null | |
| * return value guarantee that the file exists and contains the physical font. | |
| * The file name can be an absolute or a relative path name. | |
| */ | |
| public String getFileNameFromPlatformName(String platformName) { | |
| // get2DCompositeFontInfo | |
| // -> getFileNameFromComponentfontName() (W/M) | |
| // -> getFileNameFromPlatformName() | |
| // it's a waste of time on Win32, but I have to give X11 a chance to | |
| // call getFileNameFromXLFD() | |
| return filenamesMap.get(platformName); | |
| } | |
| private static final String fontconfigErrorMessage = | |
| "Fontconfig head is null, check your fonts or fonts configuration"; | |
| /** | |
| * Returns a configuration specific path to be appended to the font | |
| * search path. | |
| */ | |
| public String getExtraFontPath() { | |
| if (head == null) { | |
| throw new RuntimeException(fontconfigErrorMessage); | |
| } | |
| return getString(head[INDEX_appendedfontpath]); | |
| } | |
| public String getVersion() { | |
| if (head == null) { | |
| throw new RuntimeException(fontconfigErrorMessage); | |
| } | |
| return getString(head[INDEX_version]); | |
| } | |
| /* subclass support */ | |
| protected static FontConfiguration getFontConfiguration() { | |
| return fontConfig; | |
| } | |
| protected void setFontConfiguration() { | |
| fontConfig = this; /* static initialization */ | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| // FontConfig data tables and the index constants in binary file // | |
| ////////////////////////////////////////////////////////////////////// | |
| /* The binary font configuration file begins with a short[] "head", which | |
| * contains the offsets to the starts of the individual data table which | |
| * immediately follow. The current implementation includes the tables shown | |
| * below. | |
| * | |
| * (00) table_scriptIDs :stringIDs of all defined CharacterSubsetNames | |
| * (01) table_scriptFonts :scriptID x fontIndex x styleIndex-> | |
| * PlatformFontNameID mapping. Each scriptID might | |
| * have 1 or 20 entries depends on if it is defined | |
| * via a "allfonts.CharacterSubsetname" or a list of | |
| * "LogicalFontName.StyleName.CharacterSubsetName" | |
| * entries, positive entry means it's a "allfonts" | |
| * entry, a negative value means this is a offset to | |
| * a NUM_FONTS x NUM_STYLES subtable. | |
| * (02) table_elcIDs :stringIDs of all defined ELC names, string | |
| * "NULL.NULL.NULL" is used for "default" | |
| * (03) table_sequences :elcID x logicalFont -> scriptIDs table defined | |
| * by "sequence.allfonts/LogicalFontName.ELC" in | |
| * font configuration file, each "elcID" has | |
| * NUM_FONTS (5) entries in this table. | |
| * (04) table_fontfileNameIDs | |
| * :stringIDs of all defined font file names | |
| * (05) table_componentFontNameIDs | |
| * :stringIDs of all defined PlatformFontNames | |
| * (06) table_filenames :platformFontNamesID->fontfileNameID mapping | |
| * table, the index is the platformFontNamesID. | |
| * (07) table_awtfontpaths :CharacterSubsetNames->awtfontpaths mapping table, | |
| * the index is the CharacterSubsetName's stringID | |
| * and content is the stringID of awtfontpath. | |
| * (08) table_exclusions :scriptID -> exclusionRanges mapping table, | |
| * the index is the scriptID and the content is | |
| a id of an exclusionRanges int[]. | |
| * (09) table_proportionals:list of pairs of PlatformFontNameIDs, stores | |
| * the replacement info defined by "proportional" | |
| * keyword. | |
| * (10) table_scriptFontsMotif | |
| * :same as (01) except this table stores the | |
| * info defined with ".motif" keyword | |
| * (11) table_alphabeticSuffix | |
| * :elcID -> stringID of alphabetic/XXXX entries | |
| * (12) table_stringIDs :The index of this table is the string ID, the | |
| * content is the "start index" of this string in | |
| * stringTable, use the start index of next entry | |
| * as the "end index". | |
| * (13) table_stringTable :The real storage of all character strings defined | |
| * /used this font configuration, need a pair of | |
| * "start" and "end" indices to access. | |
| * (14) reserved | |
| * (15) table_fallbackScripts | |
| * :stringIDs of fallback CharacterSubsetnames, stored | |
| * in the order of they are defined in sequence.fallback. | |
| * (16) table_appendedfontpath | |
| * :stringtID of the "appendedfontpath" defined. | |
| * (17) table_version :stringID of the version number of this fontconfig file. | |
| */ | |
| private static final int HEAD_LENGTH = 20; | |
| private static final int INDEX_scriptIDs = 0; | |
| private static final int INDEX_scriptFonts = 1; | |
| private static final int INDEX_elcIDs = 2; | |
| private static final int INDEX_sequences = 3; | |
| private static final int INDEX_fontfileNameIDs = 4; | |
| private static final int INDEX_componentFontNameIDs = 5; | |
| private static final int INDEX_filenames = 6; | |
| private static final int INDEX_awtfontpaths = 7; | |
| private static final int INDEX_exclusions = 8; | |
| private static final int INDEX_proportionals = 9; | |
| private static final int INDEX_scriptFontsMotif = 10; | |
| private static final int INDEX_alphabeticSuffix = 11; | |
| private static final int INDEX_stringIDs = 12; | |
| private static final int INDEX_stringTable = 13; | |
| private static final int INDEX_TABLEEND = 14; | |
| private static final int INDEX_fallbackScripts = 15; | |
| private static final int INDEX_appendedfontpath = 16; | |
| private static final int INDEX_version = 17; | |
| private static short[] head; | |
| private static short[] table_scriptIDs; | |
| private static short[] table_scriptFonts; | |
| private static short[] table_elcIDs; | |
| private static short[] table_sequences; | |
| private static short[] table_fontfileNameIDs; | |
| private static short[] table_componentFontNameIDs; | |
| private static short[] table_filenames; | |
| protected static short[] table_awtfontpaths; | |
| private static short[] table_exclusions; | |
| private static short[] table_proportionals; | |
| private static short[] table_scriptFontsMotif; | |
| private static short[] table_alphabeticSuffix; | |
| private static short[] table_stringIDs; | |
| private static char[] table_stringTable; | |
| /** | |
| * Checks consistencies of complied fontconfig data. This method | |
| * is called only at the build-time from | |
| * build.tools.compilefontconfig.CompileFontConfig. | |
| */ | |
| private static void sanityCheck() { | |
| int errors = 0; | |
| //componentFontNameID starts from "1" | |
| for (int ii = 1; ii < table_filenames.length; ii++) { | |
| if (table_filenames[ii] == -1) { | |
| // The corresponding finename entry for a component | |
| // font name is mandatory on Windows, but it's | |
| // optional on Solaris and Linux. | |
| if (OSInfo.getOSType() == OSInfo.OSType.WINDOWS) { | |
| System.err.println("\n Error: <filename." | |
| + getString(table_componentFontNameIDs[ii]) | |
| + "> entry is missing!!!"); | |
| errors++; | |
| } else { | |
| if (verbose && !isEmpty(table_filenames)) { | |
| System.err.println("\n Note: 'filename' entry is undefined for \"" | |
| + getString(table_componentFontNameIDs[ii]) | |
| + "\""); | |
| } | |
| } | |
| } | |
| } | |
| for (int ii = 0; ii < table_scriptIDs.length; ii++) { | |
| short fid = table_scriptFonts[ii]; | |
| if (fid == 0) { | |
| System.out.println("\n Error: <allfonts." | |
| + getString(table_scriptIDs[ii]) | |
| + "> entry is missing!!!"); | |
| errors++; | |
| continue; | |
| } else if (fid < 0) { | |
| fid = (short)-fid; | |
| for (int iii = 0; iii < NUM_FONTS; iii++) { | |
| for (int iij = 0; iij < NUM_STYLES; iij++) { | |
| int jj = iii * NUM_STYLES + iij; | |
| short ffid = table_scriptFonts[fid + jj]; | |
| if (ffid == 0) { | |
| System.err.println("\n Error: <" | |
| + getFontName(iii) + "." | |
| + getStyleName(iij) + "." | |
| + getString(table_scriptIDs[ii]) | |
| + "> entry is missing!!!"); | |
| errors++; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (errors != 0) { | |
| System.err.println("!!THERE ARE " + errors + " ERROR(S) IN " | |
| + "THE FONTCONFIG FILE, PLEASE CHECK ITS CONTENT!!\n"); | |
| System.exit(1); | |
| } | |
| } | |
| private static boolean isEmpty(short[] a) { | |
| for (short s : a) { | |
| if (s != -1) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| //dump the fontconfig data tables | |
| private static void dump() { | |
| System.out.println("\n----Head Table------------"); | |
| for (int ii = 0; ii < HEAD_LENGTH; ii++) { | |
| System.out.println(" " + ii + " : " + head[ii]); | |
| } | |
| System.out.println("\n----scriptIDs-------------"); | |
| printTable(table_scriptIDs, 0); | |
| System.out.println("\n----scriptFonts----------------"); | |
| for (int ii = 0; ii < table_scriptIDs.length; ii++) { | |
| short fid = table_scriptFonts[ii]; | |
| if (fid >= 0) { | |
| System.out.println(" allfonts." | |
| + getString(table_scriptIDs[ii]) | |
| + "=" | |
| + getString(table_componentFontNameIDs[fid])); | |
| } | |
| } | |
| for (int ii = 0; ii < table_scriptIDs.length; ii++) { | |
| short fid = table_scriptFonts[ii]; | |
| if (fid < 0) { | |
| fid = (short)-fid; | |
| for (int iii = 0; iii < NUM_FONTS; iii++) { | |
| for (int iij = 0; iij < NUM_STYLES; iij++) { | |
| int jj = iii * NUM_STYLES + iij; | |
| short ffid = table_scriptFonts[fid + jj]; | |
| System.out.println(" " | |
| + getFontName(iii) + "." | |
| + getStyleName(iij) + "." | |
| + getString(table_scriptIDs[ii]) | |
| + "=" | |
| + getString(table_componentFontNameIDs[ffid])); | |
| } | |
| } | |
| } | |
| } | |
| System.out.println("\n----elcIDs----------------"); | |
| printTable(table_elcIDs, 0); | |
| System.out.println("\n----sequences-------------"); | |
| for (int ii = 0; ii< table_elcIDs.length; ii++) { | |
| System.out.println(" " + ii + "/" + getString(table_elcIDs[ii])); | |
| short[] ss = getShortArray(table_sequences[ii * NUM_FONTS + 0]); | |
| for (int jj = 0; jj < ss.length; jj++) { | |
| System.out.println(" " + getString(table_scriptIDs[ss[jj]])); | |
| } | |
| } | |
| System.out.println("\n----fontfileNameIDs-------"); | |
| printTable(table_fontfileNameIDs, 0); | |
| System.out.println("\n----componentFontNameIDs--"); | |
| printTable(table_componentFontNameIDs, 1); | |
| System.out.println("\n----filenames-------------"); | |
| for (int ii = 0; ii < table_filenames.length; ii++) { | |
| if (table_filenames[ii] == -1) { | |
| System.out.println(" " + ii + " : null"); | |
| } else { | |
| System.out.println(" " + ii + " : " | |
| + getString(table_fontfileNameIDs[table_filenames[ii]])); | |
| } | |
| } | |
| System.out.println("\n----awtfontpaths---------"); | |
| for (int ii = 0; ii < table_awtfontpaths.length; ii++) { | |
| System.out.println(" " + getString(table_scriptIDs[ii]) | |
| + " : " | |
| + getString(table_awtfontpaths[ii])); | |
| } | |
| System.out.println("\n----proportionals--------"); | |
| for (int ii = 0; ii < table_proportionals.length; ii++) { | |
| System.out.println(" " | |
| + getString(table_componentFontNameIDs[table_proportionals[ii++]]) | |
| + " -> " | |
| + getString(table_componentFontNameIDs[table_proportionals[ii]])); | |
| } | |
| int i = 0; | |
| System.out.println("\n----alphabeticSuffix----"); | |
| while (i < table_alphabeticSuffix.length) { | |
| System.out.println(" " + getString(table_elcIDs[table_alphabeticSuffix[i++]]) | |
| + " -> " + getString(table_alphabeticSuffix[i++])); | |
| } | |
| System.out.println("\n----String Table---------"); | |
| System.out.println(" stringID: Num =" + table_stringIDs.length); | |
| System.out.println(" stringTable: Size=" + table_stringTable.length * 2); | |
| System.out.println("\n----fallbackScriptIDs---"); | |
| short[] fbsIDs = getShortArray(head[INDEX_fallbackScripts]); | |
| for (int ii = 0; ii < fbsIDs.length; ii++) { | |
| System.out.println(" " + getString(table_scriptIDs[fbsIDs[ii]])); | |
| } | |
| System.out.println("\n----appendedfontpath-----"); | |
| System.out.println(" " + getString(head[INDEX_appendedfontpath])); | |
| System.out.println("\n----Version--------------"); | |
| System.out.println(" " + getString(head[INDEX_version])); | |
| } | |
| ////////////////////////////////////////////////////////////////////// | |
| // Data table access methods // | |
| ////////////////////////////////////////////////////////////////////// | |
| /* Return the fontID of the platformFontName defined in this font config | |
| * by "LogicalFontName.StyleName.CharacterSubsetName" entry or | |
| * "allfonts.CharacterSubsetName" entry in properties format fc file. | |
| */ | |
| protected static short getComponentFontID(short scriptID, int fontIndex, int styleIndex) { | |
| short fid = table_scriptFonts[scriptID]; | |
| //System.out.println("fid=" + fid + "/ scriptID=" + scriptID + ", fi=" + fontIndex + ", si=" + styleIndex); | |
| if (fid >= 0) { | |
| //"allfonts" | |
| return fid; | |
| } else { | |
| return table_scriptFonts[-fid + fontIndex * NUM_STYLES + styleIndex]; | |
| } | |
| } | |
| /* Same as getComponentFontID() except this method returns the fontID defined by | |
| * "xxxx.motif" entry. | |
| */ | |
| protected static short getComponentFontIDMotif(short scriptID, int fontIndex, int styleIndex) { | |
| if (table_scriptFontsMotif.length == 0) { | |
| return 0; | |
| } | |
| short fid = table_scriptFontsMotif[scriptID]; | |
| if (fid >= 0) { | |
| //"allfonts" > 0 or "not defined" == 0 | |
| return fid; | |
| } else { | |
| return table_scriptFontsMotif[-fid + fontIndex * NUM_STYLES + styleIndex]; | |
| } | |
| } | |
| private static int[] getExclusionRanges(short scriptID) { | |
| short exID = table_exclusions[scriptID]; | |
| if (exID == 0) { | |
| return EMPTY_INT_ARRAY; | |
| } else { | |
| char[] exChar = getString(exID).toCharArray(); | |
| int[] exInt = new int[exChar.length / 2]; | |
| int i = 0; | |
| for (int j = 0; j < exInt.length; j++) { | |
| exInt[j] = (exChar[i++] << 16) + (exChar[i++] & 0xffff); | |
| } | |
| return exInt; | |
| } | |
| } | |
| private static boolean contains(short[] IDs, short id, int limit) { | |
| for (int i = 0; i < limit; i++) { | |
| if (IDs[i] == id) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /* Return the PlatformFontName from its fontID*/ | |
| protected static String getComponentFontName(short id) { | |
| if (id < 0) { | |
| return null; | |
| } | |
| return getString(table_componentFontNameIDs[id]); | |
| } | |
| private static String getComponentFileName(short id) { | |
| if (id < 0) { | |
| return null; | |
| } | |
| return getString(table_fontfileNameIDs[id]); | |
| } | |
| //componentFontID -> componentFileID | |
| private static short getComponentFileID(short nameID) { | |
| return table_filenames[nameID]; | |
| } | |
| private static String getScriptName(short scriptID) { | |
| return getString(table_scriptIDs[scriptID]); | |
| } | |
| private HashMap<String, Short> reorderScripts; | |
| protected short[] getCoreScripts(int fontIndex) { | |
| short elc = getInitELC(); | |
| /* | |
| System.out.println("getCoreScripts: elc=" + elc + ", fontIndex=" + fontIndex); | |
| short[] ss = getShortArray(table_sequences[elc * NUM_FONTS + fontIndex]); | |
| for (int i = 0; i < ss.length; i++) { | |
| System.out.println(" " + getString((short)table_scriptIDs[ss[i]])); | |
| } | |
| */ | |
| short[] scripts = getShortArray(table_sequences[elc * NUM_FONTS + fontIndex]); | |
| if (preferLocaleFonts) { | |
| if (reorderScripts == null) { | |
| reorderScripts = new HashMap<String, Short>(); | |
| } | |
| String[] ss = new String[scripts.length]; | |
| for (int i = 0; i < ss.length; i++) { | |
| ss[i] = getScriptName(scripts[i]); | |
| reorderScripts.put(ss[i], scripts[i]); | |
| } | |
| reorderSequenceForLocale(ss); | |
| for (int i = 0; i < ss.length; i++) { | |
| scripts[i] = reorderScripts.get(ss[i]); | |
| } | |
| } | |
| return scripts; | |
| } | |
| private static short[] getFallbackScripts() { | |
| return getShortArray(head[INDEX_fallbackScripts]); | |
| } | |
| private static void printTable(short[] list, int start) { | |
| for (int i = start; i < list.length; i++) { | |
| System.out.println(" " + i + " : " + getString(list[i])); | |
| } | |
| } | |
| private static short[] readShortTable(DataInputStream in, int len ) | |
| throws IOException { | |
| if (len == 0) { | |
| return EMPTY_SHORT_ARRAY; | |
| } | |
| short[] data = new short[len]; | |
| byte[] bb = new byte[len * 2]; | |
| in.read(bb); | |
| int i = 0,j = 0; | |
| while (i < len) { | |
| data[i++] = (short)(bb[j++] << 8 | (bb[j++] & 0xff)); | |
| } | |
| return data; | |
| } | |
| private static void writeShortTable(DataOutputStream out, short[] data) | |
| throws IOException { | |
| for (short val : data) { | |
| out.writeShort(val); | |
| } | |
| } | |
| private static short[] toList(HashMap<String, Short> map) { | |
| short[] list = new short[map.size()]; | |
| Arrays.fill(list, (short) -1); | |
| for (Entry<String, Short> entry : map.entrySet()) { | |
| list[entry.getValue()] = getStringID(entry.getKey()); | |
| } | |
| return list; | |
| } | |
| //runtime cache | |
| private static String[] stringCache; | |
| protected static String getString(short stringID) { | |
| if (stringID == 0) | |
| return null; | |
| /* | |
| if (loadingProperties) { | |
| return stringTable.substring(stringIDs[stringID], | |
| stringIDs[stringID+1]); | |
| } | |
| */ | |
| //sync if we want it to be MT-enabled | |
| if (stringCache[stringID] == null){ | |
| stringCache[stringID] = | |
| new String (table_stringTable, | |
| table_stringIDs[stringID], | |
| table_stringIDs[stringID+1] - table_stringIDs[stringID]); | |
| } | |
| return stringCache[stringID]; | |
| } | |
| private static short[] getShortArray(short shortArrayID) { | |
| String s = getString(shortArrayID); | |
| char[] cc = s.toCharArray(); | |
| short[] ss = new short[cc.length]; | |
| for (int i = 0; i < cc.length; i++) { | |
| ss[i] = (short)(cc[i] & 0xffff); | |
| } | |
| return ss; | |
| } | |
| private static short getStringID(String s) { | |
| if (s == null) { | |
| return (short)0; | |
| } | |
| short pos0 = (short)stringTable.length(); | |
| stringTable.append(s); | |
| short pos1 = (short)stringTable.length(); | |
| stringIDs[stringIDNum] = pos0; | |
| stringIDs[stringIDNum + 1] = pos1; | |
| stringIDNum++; | |
| if (stringIDNum + 1 >= stringIDs.length) { | |
| short[] tmp = new short[stringIDNum + 1000]; | |
| System.arraycopy(stringIDs, 0, tmp, 0, stringIDNum); | |
| stringIDs = tmp; | |
| } | |
| return (short)(stringIDNum - 1); | |
| } | |
| private static short getShortArrayID(short[] sa) { | |
| char[] cc = new char[sa.length]; | |
| for (int i = 0; i < sa.length; i ++) { | |
| cc[i] = (char)sa[i]; | |
| } | |
| String s = new String(cc); | |
| return getStringID(s); | |
| } | |
| //utility "empty" objects | |
| private static final int[] EMPTY_INT_ARRAY = new int[0]; | |
| private static final String[] EMPTY_STRING_ARRAY = new String[0]; | |
| private static final short[] EMPTY_SHORT_ARRAY = new short[0]; | |
| private static final String UNDEFINED_COMPONENT_FONT = "unknown"; | |
| ////////////////////////////////////////////////////////////////////////// | |
| //Convert the FontConfig data in Properties file to binary data tables // | |
| ////////////////////////////////////////////////////////////////////////// | |
| static class PropertiesHandler { | |
| public void load(InputStream in) throws IOException { | |
| initLogicalNameStyle(); | |
| initHashMaps(); | |
| FontProperties fp = new FontProperties(); | |
| fp.load(in); | |
| initBinaryTable(); | |
| } | |
| private void initBinaryTable() { | |
| //(0) | |
| head = new short[HEAD_LENGTH]; | |
| head[INDEX_scriptIDs] = (short)HEAD_LENGTH; | |
| table_scriptIDs = toList(scriptIDs); | |
| //(1)a: scriptAllfonts scriptID/allfonts -> componentFontNameID | |
| // b: scriptFonts scriptID -> componentFontNameID[20] | |
| //if we have a "allfonts.script" def, then we just put | |
| //the "-platformFontID" value in the slot, otherwise the slot | |
| //value is "offset" which "offset" is where 20 entries located | |
| //in the table attached. | |
| head[INDEX_scriptFonts] = (short)(head[INDEX_scriptIDs] + table_scriptIDs.length); | |
| int len = table_scriptIDs.length + scriptFonts.size() * 20; | |
| table_scriptFonts = new short[len]; | |
| for (Entry<Short, Short> entry : scriptAllfonts.entrySet()) { | |
| table_scriptFonts[entry.getKey().intValue()] = entry.getValue(); | |
| } | |
| int off = table_scriptIDs.length; | |
| for (Entry<Short, Short[]> entry : scriptFonts.entrySet()) { | |
| table_scriptFonts[entry.getKey().intValue()] = (short)-off; | |
| Short[] v = entry.getValue(); | |
| for (int i = 0; i < 20; i++) { | |
| if (v[i] != null) { | |
| table_scriptFonts[off++] = v[i]; | |
| } else { | |
| table_scriptFonts[off++] = 0; | |
| } | |
| } | |
| } | |
| //(2) | |
| head[INDEX_elcIDs] = (short)(head[INDEX_scriptFonts] + table_scriptFonts.length); | |
| table_elcIDs = toList(elcIDs); | |
| //(3) sequences elcID -> XXXX[1|5] -> scriptID[] | |
| head[INDEX_sequences] = (short)(head[INDEX_elcIDs] + table_elcIDs.length); | |
| table_sequences = new short[elcIDs.size() * NUM_FONTS]; | |
| for (Entry<Short, short[]> entry : sequences.entrySet()) { | |
| //table_sequences[entry.getKey().intValue()] = (short)-off; | |
| int k = entry.getKey().intValue(); | |
| short[] v = entry.getValue(); | |
| /* | |
| System.out.println("elc=" + k + "/" + getString((short)table_elcIDs[k])); | |
| short[] ss = getShortArray(v[0]); | |
| for (int i = 0; i < ss.length; i++) { | |
| System.out.println(" " + getString((short)table_scriptIDs[ss[i]])); | |
| } | |
| */ | |
| if (v.length == 1) { | |
| //the "allfonts" entries | |
| for (int i = 0; i < NUM_FONTS; i++) { | |
| table_sequences[k * NUM_FONTS + i] = v[0]; | |
| } | |
| } else { | |
| for (int i = 0; i < NUM_FONTS; i++) { | |
| table_sequences[k * NUM_FONTS + i] = v[i]; | |
| } | |
| } | |
| } | |
| //(4) | |
| head[INDEX_fontfileNameIDs] = (short)(head[INDEX_sequences] + table_sequences.length); | |
| table_fontfileNameIDs = toList(fontfileNameIDs); | |
| //(5) | |
| head[INDEX_componentFontNameIDs] = (short)(head[INDEX_fontfileNameIDs] + table_fontfileNameIDs.length); | |
| table_componentFontNameIDs = toList(componentFontNameIDs); | |
| //(6)componentFontNameID -> filenameID | |
| head[INDEX_filenames] = (short)(head[INDEX_componentFontNameIDs] + table_componentFontNameIDs.length); | |
| table_filenames = new short[table_componentFontNameIDs.length]; | |
| Arrays.fill(table_filenames, (short) -1); | |
| for (Entry<Short, Short> entry : filenames.entrySet()) { | |
| table_filenames[entry.getKey()] = entry.getValue(); | |
| } | |
| //(7)scriptID-> awtfontpath | |
| //the paths are stored as scriptID -> stringID in awtfontpahts | |
| head[INDEX_awtfontpaths] = (short)(head[INDEX_filenames] + table_filenames.length); | |
| table_awtfontpaths = new short[table_scriptIDs.length]; | |
| for (Entry<Short, Short> entry : awtfontpaths.entrySet()) { | |
| table_awtfontpaths[entry.getKey()] = entry.getValue(); | |
| } | |
| //(8)exclusions | |
| head[INDEX_exclusions] = (short)(head[INDEX_awtfontpaths] + table_awtfontpaths.length); | |
| table_exclusions = new short[scriptIDs.size()]; | |
| for (Entry<Short, int[]> entry : exclusions.entrySet()) { | |
| int[] exI = entry.getValue(); | |
| char[] exC = new char[exI.length * 2]; | |
| int j = 0; | |
| for (int i = 0; i < exI.length; i++) { | |
| exC[j++] = (char) (exI[i] >> 16); | |
| exC[j++] = (char) (exI[i] & 0xffff); | |
| } | |
| table_exclusions[entry.getKey()] = getStringID(new String (exC)); | |
| } | |
| //(9)proportionals | |
| head[INDEX_proportionals] = (short)(head[INDEX_exclusions] + table_exclusions.length); | |
| table_proportionals = new short[proportionals.size() * 2]; | |
| int j = 0; | |
| for (Entry<Short, Short> entry : proportionals.entrySet()) { | |
| table_proportionals[j++] = entry.getKey(); | |
| table_proportionals[j++] = entry.getValue(); | |
| } | |
| //(10) see (1) for info, the only difference is "xxx.motif" | |
| head[INDEX_scriptFontsMotif] = (short)(head[INDEX_proportionals] + table_proportionals.length); | |
| if (scriptAllfontsMotif.size() != 0 || scriptFontsMotif.size() != 0) { | |
| len = table_scriptIDs.length + scriptFontsMotif.size() * 20; | |
| table_scriptFontsMotif = new short[len]; | |
| for (Entry<Short, Short> entry : scriptAllfontsMotif.entrySet()) { | |
| table_scriptFontsMotif[entry.getKey().intValue()] = | |
| (short)entry.getValue(); | |
| } | |
| off = table_scriptIDs.length; | |
| for (Entry<Short, Short[]> entry : scriptFontsMotif.entrySet()) { | |
| table_scriptFontsMotif[entry.getKey().intValue()] = (short)-off; | |
| Short[] v = entry.getValue(); | |
| int i = 0; | |
| while (i < 20) { | |
| if (v[i] != null) { | |
| table_scriptFontsMotif[off++] = v[i]; | |
| } else { | |
| table_scriptFontsMotif[off++] = 0; | |
| } | |
| i++; | |
| } | |
| } | |
| } else { | |
| table_scriptFontsMotif = EMPTY_SHORT_ARRAY; | |
| } | |
| //(11)short[] alphabeticSuffix | |
| head[INDEX_alphabeticSuffix] = (short)(head[INDEX_scriptFontsMotif] + table_scriptFontsMotif.length); | |
| table_alphabeticSuffix = new short[alphabeticSuffix.size() * 2]; | |
| j = 0; | |
| for (Entry<Short, Short> entry : alphabeticSuffix.entrySet()) { | |
| table_alphabeticSuffix[j++] = entry.getKey(); | |
| table_alphabeticSuffix[j++] = entry.getValue(); | |
| } | |
| //(15)short[] fallbackScriptIDs; just put the ID in head | |
| head[INDEX_fallbackScripts] = getShortArrayID(fallbackScriptIDs); | |
| //(16)appendedfontpath | |
| head[INDEX_appendedfontpath] = getStringID(appendedfontpath); | |
| //(17)version | |
| head[INDEX_version] = getStringID(version); | |
| //(12)short[] StringIDs | |
| head[INDEX_stringIDs] = (short)(head[INDEX_alphabeticSuffix] + table_alphabeticSuffix.length); | |
| table_stringIDs = new short[stringIDNum + 1]; | |
| System.arraycopy(stringIDs, 0, table_stringIDs, 0, stringIDNum + 1); | |
| //(13)StringTable | |
| head[INDEX_stringTable] = (short)(head[INDEX_stringIDs] + stringIDNum + 1); | |
| table_stringTable = stringTable.toString().toCharArray(); | |
| //(14) | |
| head[INDEX_TABLEEND] = (short)(head[INDEX_stringTable] + stringTable.length()); | |
| //StringTable cache | |
| stringCache = new String[table_stringIDs.length]; | |
| } | |
| ////////////////////////////////////////////// | |
| private HashMap<String, Short> scriptIDs; | |
| //elc -> Encoding.Language.Country | |
| private HashMap<String, Short> elcIDs; | |
| //componentFontNameID starts from "1", "0" reserves for "undefined" | |
| private HashMap<String, Short> componentFontNameIDs; | |
| private HashMap<String, Short> fontfileNameIDs; | |
| private HashMap<String, Integer> logicalFontIDs; | |
| private HashMap<String, Integer> fontStyleIDs; | |
| //componentFontNameID -> fontfileNameID | |
| private HashMap<Short, Short> filenames; | |
| //elcID -> allfonts/logicalFont -> scriptID list | |
| //(1)if we have a "allfonts", then the length of the | |
| // value array is "1", otherwise it's 5, each font | |
| // must have their own individual entry. | |
| //scriptID list "short[]" is stored as an ID | |
| private HashMap<Short, short[]> sequences; | |
| //scriptID ->logicFontID/fontStyleID->componentFontNameID, | |
| //a 20-entry array (5-name x 4-style) for each script | |
| private HashMap<Short, Short[]> scriptFonts; | |
| //scriptID -> componentFontNameID | |
| private HashMap<Short, Short> scriptAllfonts; | |
| //scriptID -> exclusionRanges[] | |
| private HashMap<Short, int[]> exclusions; | |
| //scriptID -> fontpath | |
| private HashMap<Short, Short> awtfontpaths; | |
| //fontID -> fontID | |
| private HashMap<Short, Short> proportionals; | |
| //scriptID -> componentFontNameID | |
| private HashMap<Short, Short> scriptAllfontsMotif; | |
| //scriptID ->logicFontID/fontStyleID->componentFontNameID, | |
| private HashMap<Short, Short[]> scriptFontsMotif; | |
| //elcID -> stringID of alphabetic/XXXX | |
| private HashMap<Short, Short> alphabeticSuffix; | |
| private short[] fallbackScriptIDs; | |
| private String version; | |
| private String appendedfontpath; | |
| private void initLogicalNameStyle() { | |
| logicalFontIDs = new HashMap<String, Integer>(); | |
| fontStyleIDs = new HashMap<String, Integer>(); | |
| logicalFontIDs.put("serif", 0); | |
| logicalFontIDs.put("sansserif", 1); | |
| logicalFontIDs.put("monospaced", 2); | |
| logicalFontIDs.put("dialog", 3); | |
| logicalFontIDs.put("dialoginput",4); | |
| fontStyleIDs.put("plain", 0); | |
| fontStyleIDs.put("bold", 1); | |
| fontStyleIDs.put("italic", 2); | |
| fontStyleIDs.put("bolditalic", 3); | |
| } | |
| private void initHashMaps() { | |
| scriptIDs = new HashMap<String, Short>(); | |
| elcIDs = new HashMap<String, Short>(); | |
| componentFontNameIDs = new HashMap<String, Short>(); | |
| /*Init these tables to allow componentFontNameID, fontfileNameIDs | |
| to start from "1". | |
| */ | |
| componentFontNameIDs.put("", Short.valueOf((short)0)); | |
| fontfileNameIDs = new HashMap<String, Short>(); | |
| filenames = new HashMap<Short, Short>(); | |
| sequences = new HashMap<Short, short[]>(); | |
| scriptFonts = new HashMap<Short, Short[]>(); | |
| scriptAllfonts = new HashMap<Short, Short>(); | |
| exclusions = new HashMap<Short, int[]>(); | |
| awtfontpaths = new HashMap<Short, Short>(); | |
| proportionals = new HashMap<Short, Short>(); | |
| scriptFontsMotif = new HashMap<Short, Short[]>(); | |
| scriptAllfontsMotif = new HashMap<Short, Short>(); | |
| alphabeticSuffix = new HashMap<Short, Short>(); | |
| fallbackScriptIDs = EMPTY_SHORT_ARRAY; | |
| /* | |
| version | |
| appendedfontpath | |
| */ | |
| } | |
| private int[] parseExclusions(String key, String exclusions) { | |
| if (exclusions == null) { | |
| return EMPTY_INT_ARRAY; | |
| } | |
| // range format is xxxx-XXXX,yyyyyy-YYYYYY,..... | |
| int numExclusions = 1; | |
| int pos = 0; | |
| while ((pos = exclusions.indexOf(',', pos)) != -1) { | |
| numExclusions++; | |
| pos++; | |
| } | |
| int[] exclusionRanges = new int[numExclusions * 2]; | |
| pos = 0; | |
| int newPos = 0; | |
| for (int j = 0; j < numExclusions * 2; ) { | |
| String lower, upper; | |
| int lo = 0, up = 0; | |
| try { | |
| newPos = exclusions.indexOf('-', pos); | |
| lower = exclusions.substring(pos, newPos); | |
| pos = newPos + 1; | |
| newPos = exclusions.indexOf(',', pos); | |
| if (newPos == -1) { | |
| newPos = exclusions.length(); | |
| } | |
| upper = exclusions.substring(pos, newPos); | |
| pos = newPos + 1; | |
| int lowerLength = lower.length(); | |
| int upperLength = upper.length(); | |
| if (lowerLength != 4 && lowerLength != 6 | |
| || upperLength != 4 && upperLength != 6) { | |
| throw new Exception(); | |
| } | |
| lo = Integer.parseInt(lower, 16); | |
| up = Integer.parseInt(upper, 16); | |
| if (lo > up) { | |
| throw new Exception(); | |
| } | |
| } catch (Exception e) { | |
| if (FontUtilities.debugFonts() && | |
| logger != null) { | |
| logger.config("Failed parsing " + key + | |
| " property of font configuration."); | |
| } | |
| return EMPTY_INT_ARRAY; | |
| } | |
| exclusionRanges[j++] = lo; | |
| exclusionRanges[j++] = up; | |
| } | |
| return exclusionRanges; | |
| } | |
| private Short getID(HashMap<String, Short> map, String key) { | |
| Short ret = map.get(key); | |
| if ( ret == null) { | |
| map.put(key, (short)map.size()); | |
| return map.get(key); | |
| } | |
| return ret; | |
| } | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| class FontProperties extends Properties { | |
| public synchronized Object put(Object k, Object v) { | |
| parseProperty((String)k, (String)v); | |
| return null; | |
| } | |
| } | |
| private void parseProperty(String key, String value) { | |
| if (key.startsWith("filename.")) { | |
| //the only special case is "MingLiu_HKSCS" which has "_" in its | |
| //facename, we don't want to replace the "_" with " " | |
| key = key.substring(9); | |
| if (!"MingLiU_HKSCS".equals(key)) { | |
| key = key.replace('_', ' '); | |
| } | |
| Short faceID = getID(componentFontNameIDs, key); | |
| Short fileID = getID(fontfileNameIDs, value); | |
| //System.out.println("faceID=" + faceID + "/" + key + " -> " | |
| // + "fileID=" + fileID + "/" + value); | |
| filenames.put(faceID, fileID); | |
| } else if (key.startsWith("exclusion.")) { | |
| key = key.substring(10); | |
| exclusions.put(getID(scriptIDs,key), parseExclusions(key,value)); | |
| } else if (key.startsWith("sequence.")) { | |
| key = key.substring(9); | |
| boolean hasDefault = false; | |
| boolean has1252 = false; | |
| //get the scriptID list | |
| String[] ss = splitSequence(value).toArray(EMPTY_STRING_ARRAY); | |
| short [] sa = new short[ss.length]; | |
| for (int i = 0; i < ss.length; i++) { | |
| if ("alphabetic/default".equals(ss[i])) { | |
| //System.out.println(key + " -> " + ss[i]); | |
| ss[i] = "alphabetic"; | |
| hasDefault = true; | |
| } else if ("alphabetic/1252".equals(ss[i])) { | |
| //System.out.println(key + " -> " + ss[i]); | |
| ss[i] = "alphabetic"; | |
| has1252 = true; | |
| } | |
| sa[i] = getID(scriptIDs, ss[i]).shortValue(); | |
| //System.out.println("scriptID=" + si[i] + "/" + ss[i]); | |
| } | |
| //convert the "short[] -> string -> stringID" | |
| short scriptArrayID = getShortArrayID(sa); | |
| Short elcID = null; | |
| int dot = key.indexOf('.'); | |
| if (dot == -1) { | |
| if ("fallback".equals(key)) { | |
| fallbackScriptIDs = sa; | |
| return; | |
| } | |
| if ("allfonts".equals(key)) { | |
| elcID = getID(elcIDs, "NULL.NULL.NULL"); | |
| } else { | |
| if (logger != null) { | |
| logger.config("Error sequence def: <sequence." + key + ">"); | |
| } | |
| return; | |
| } | |
| } else { | |
| elcID = getID(elcIDs, key.substring(dot + 1)); | |
| //System.out.println("elcID=" + elcID + "/" + key.substring(dot + 1)); | |
| key = key.substring(0, dot); | |
| } | |
| short[] scriptArrayIDs = null; | |
| if ("allfonts".equals(key)) { | |
| scriptArrayIDs = new short[1]; | |
| scriptArrayIDs[0] = scriptArrayID; | |
| } else { | |
| scriptArrayIDs = sequences.get(elcID); | |
| if (scriptArrayIDs == null) { | |
| scriptArrayIDs = new short[5]; | |
| } | |
| Integer fid = logicalFontIDs.get(key); | |
| if (fid == null) { | |
| if (logger != null) { | |
| logger.config("Unrecognizable logicfont name " + key); | |
| } | |
| return; | |
| } | |
| //System.out.println("sequence." + key + "/" + id); | |
| scriptArrayIDs[fid.intValue()] = scriptArrayID; | |
| } | |
| sequences.put(elcID, scriptArrayIDs); | |
| if (hasDefault) { | |
| alphabeticSuffix.put(elcID, getStringID("default")); | |
| } else | |
| if (has1252) { | |
| alphabeticSuffix.put(elcID, getStringID("1252")); | |
| } | |
| } else if (key.startsWith("allfonts.")) { | |
| key = key.substring(9); | |
| if (key.endsWith(".motif")) { | |
| key = key.substring(0, key.length() - 6); | |
| //System.out.println("motif: all." + key + "=" + value); | |
| scriptAllfontsMotif.put(getID(scriptIDs,key), getID(componentFontNameIDs,value)); | |
| } else { | |
| scriptAllfonts.put(getID(scriptIDs,key), getID(componentFontNameIDs,value)); | |
| } | |
| } else if (key.startsWith("awtfontpath.")) { | |
| key = key.substring(12); | |
| //System.out.println("scriptID=" + getID(scriptIDs, key) + "/" + key); | |
| awtfontpaths.put(getID(scriptIDs, key), getStringID(value)); | |
| } else if ("version".equals(key)) { | |
| version = value; | |
| } else if ("appendedfontpath".equals(key)) { | |
| appendedfontpath = value; | |
| } else if (key.startsWith("proportional.")) { | |
| key = key.substring(13).replace('_', ' '); | |
| //System.out.println(key + "=" + value); | |
| proportionals.put(getID(componentFontNameIDs, key), | |
| getID(componentFontNameIDs, value)); | |
| } else { | |
| //"name.style.script(.motif)", we don't care anything else | |
| int dot1, dot2; | |
| boolean isMotif = false; | |
| dot1 = key.indexOf('.'); | |
| if (dot1 == -1) { | |
| if (logger != null) { | |
| logger.config("Failed parsing " + key + | |
| " property of font configuration."); | |
| } | |
| return; | |
| } | |
| dot2 = key.indexOf('.', dot1 + 1); | |
| if (dot2 == -1) { | |
| if (logger != null) { | |
| logger.config("Failed parsing " + key + | |
| " property of font configuration."); | |
| } | |
| return; | |
| } | |
| if (key.endsWith(".motif")) { | |
| key = key.substring(0, key.length() - 6); | |
| isMotif = true; | |
| //System.out.println("motif: " + key + "=" + value); | |
| } | |
| Integer nameID = logicalFontIDs.get(key.substring(0, dot1)); | |
| Integer styleID = fontStyleIDs.get(key.substring(dot1+1, dot2)); | |
| Short scriptID = getID(scriptIDs, key.substring(dot2 + 1)); | |
| if (nameID == null || styleID == null) { | |
| if (logger != null) { | |
| logger.config("unrecognizable logicfont name/style at " + key); | |
| } | |
| return; | |
| } | |
| Short[] pnids; | |
| if (isMotif) { | |
| pnids = scriptFontsMotif.get(scriptID); | |
| } else { | |
| pnids = scriptFonts.get(scriptID); | |
| } | |
| if (pnids == null) { | |
| pnids = new Short[20]; | |
| } | |
| pnids[nameID.intValue() * NUM_STYLES + styleID.intValue()] | |
| = getID(componentFontNameIDs, value); | |
| /* | |
| System.out.println("key=" + key + "/<" + nameID + "><" + styleID | |
| + "><" + scriptID + ">=" + value | |
| + "/" + getID(componentFontNameIDs, value)); | |
| */ | |
| if (isMotif) { | |
| scriptFontsMotif.put(scriptID, pnids); | |
| } else { | |
| scriptFonts.put(scriptID, pnids); | |
| } | |
| } | |
| } | |
| } | |
| } |