| /* |
| * Copyright (c) 1996, 2014, 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.security.AccessController; |
| import java.security.PrivilegedAction; |
| 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.FontManagerFactory; |
| 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.getLogger() |
| .info("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.getLogger() |
| .info("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. Also avoid a doPrivileged block. |
| */ |
| 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)); |
| Boolean exists = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<Boolean>() { |
| public Boolean run() { |
| try { |
| File f = new File(fileName); |
| return Boolean.valueOf(f.exists()); |
| } |
| catch (Exception e) { |
| return Boolean.FALSE; |
| } |
| } |
| }); |
| return exists.booleanValue(); |
| } |
| |
| 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); |
| } |
| in.close(); |
| 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, sequence.length())); |
| } |
| return parts; |
| } |
| |
| protected String[] split(String sequence) { |
| Vector<String> v = splitSequence(sequence); |
| return v.toArray(new String[0]); |
| } |
| |
| //////////////////////////////////////////////////////////////////////// |
| // 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")) { |
| fc = Charset.forName(charsetName); |
| } else { |
| Class<?> fcc = AccessController.doPrivileged(new PrivilegedAction<Class<?>>() { |
| public Class<?> run() { |
| try { |
| return Class.forName(charsetName, true, |
| ClassLoader.getSystemClassLoader()); |
| } catch (ClassNotFoundException e) { |
| } |
| return null; |
| } |
| }); |
| |
| 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. |
| * If the font configuration file doesn't specify Lucida Sans Regular |
| * or the given fallback font as component fonts, they are added here. |
| */ |
| 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; |
| boolean sawDefaultFontFile = false; |
| // 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)); |
| } |
| if (!sawDefaultFontFile && |
| defaultFontFile.equals(componentFileNames[index])) { |
| sawDefaultFontFile = true; |
| } |
| /* |
| System.out.println(publicFontNames[fontIndex] + "." + styleNames[styleIndex] + "." |
| + getString(table_scriptIDs[coreScripts[index]]) + "=" + componentFileNames[index]); |
| */ |
| } |
| |
| //"Lucida Sans Regular" is not in the list, we add it here |
| if (!sawDefaultFontFile) { |
| int len = 0; |
| if (installedFallbackFontFiles != null) { |
| len = installedFallbackFontFiles.length; |
| } |
| if (index + len == maxComponentFontCount) { |
| String[] newComponentFaceNames = new String[maxComponentFontCount + 1]; |
| System.arraycopy(componentFaceNames, 0, newComponentFaceNames, 0, index); |
| componentFaceNames = newComponentFaceNames; |
| String[] newComponentFileNames = new String[maxComponentFontCount + 1]; |
| System.arraycopy(componentFileNames, 0, newComponentFileNames, 0, index); |
| componentFileNames = newComponentFileNames; |
| } |
| componentFaceNames[index] = defaultFontFaceName; |
| componentFileNames[index] = defaultFontFile; |
| 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); |
| } |
| |
| /** |
| * Returns a configuration specific path to be appended to the font |
| * search path. |
| */ |
| public String getExtraFontPath() { |
| return getString(head[INDEX_appendedfontpath]); |
| } |
| |
| public String getVersion() { |
| 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; |
| |
| //This method will only be called during build time, do we |
| //need do PrivilegedAction? |
| String osName = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<String>() { |
| public String run() { |
| return System.getProperty("os.name"); |
| } |
| }); |
| |
| //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 (osName.contains("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 ("SunOS".equals(osName)) { |
| for (int ii = 0; ii < table_awtfontpaths.length; ii++) { |
| if (table_awtfontpaths[ii] == 0) { |
| String script = getString(table_scriptIDs[ii]); |
| if (script.contains("lucida") || |
| script.contains("dingbats") || |
| script.contains("symbol")) { |
| continue; |
| } |
| System.err.println("\nError: " |
| + "<awtfontpath." |
| + script |
| + "> 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 getCompoentFontID() except this method returns the fontID define 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); |
| } |
| } |
| } |
| } |
| } |