/* * Copyright (c) 1996, 2020, 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 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. 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() { 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 elcIDs = new HashMap(); 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 coreFontNameIDs = new HashSet(); private Set fallbackFontNameIDs = new HashSet(); 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 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 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 splitSequence(String sequence) { //String.split would be more convenient, but incurs big performance penalty Vector 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 v = splitSequence(sequence); return v.toArray(new String[0]); } //////////////////////////////////////////////////////////////////////// // Methods for extracting information from the fontconfig data for AWT// //////////////////////////////////////////////////////////////////////// private Hashtable 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>() { 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 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 " + 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 existsMap; public boolean needToSearchForFile(String fileName) { if (!FontUtilities.isLinux) { return false; } else if (existsMap == null) { existsMap = new HashMap(); } 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 filenamesMap = new HashMap(); HashSet coreFontFileNames = new HashSet(); /* 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() { 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: 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: 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 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 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[] 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 map) { short[] list = new short[map.size()]; Arrays.fill(list, (short) -1); for (Entry 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 entry : scriptAllfonts.entrySet()) { table_scriptFonts[entry.getKey().intValue()] = entry.getValue(); } int off = table_scriptIDs.length; for (Entry 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 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 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 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 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 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 entry : scriptAllfontsMotif.entrySet()) { table_scriptFontsMotif[entry.getKey().intValue()] = (short)entry.getValue(); } off = table_scriptIDs.length; for (Entry 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 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 scriptIDs; //elc -> Encoding.Language.Country private HashMap elcIDs; //componentFontNameID starts from "1", "0" reserves for "undefined" private HashMap componentFontNameIDs; private HashMap fontfileNameIDs; private HashMap logicalFontIDs; private HashMap fontStyleIDs; //componentFontNameID -> fontfileNameID private HashMap 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 sequences; //scriptID ->logicFontID/fontStyleID->componentFontNameID, //a 20-entry array (5-name x 4-style) for each script private HashMap scriptFonts; //scriptID -> componentFontNameID private HashMap scriptAllfonts; //scriptID -> exclusionRanges[] private HashMap exclusions; //scriptID -> fontpath private HashMap awtfontpaths; //fontID -> fontID private HashMap proportionals; //scriptID -> componentFontNameID private HashMap scriptAllfontsMotif; //scriptID ->logicFontID/fontStyleID->componentFontNameID, private HashMap scriptFontsMotif; //elcID -> stringID of alphabetic/XXXX private HashMap alphabeticSuffix; private short[] fallbackScriptIDs; private String version; private String appendedfontpath; private void initLogicalNameStyle() { logicalFontIDs = new HashMap(); fontStyleIDs = new HashMap(); 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(); elcIDs = new HashMap(); componentFontNameIDs = new HashMap(); /*Init these tables to allow componentFontNameID, fontfileNameIDs to start from "1". */ componentFontNameIDs.put("", Short.valueOf((short)0)); fontfileNameIDs = new HashMap(); filenames = new HashMap(); sequences = new HashMap(); scriptFonts = new HashMap(); scriptAllfonts = new HashMap(); exclusions = new HashMap(); awtfontpaths = new HashMap(); proportionals = new HashMap(); scriptFontsMotif = new HashMap(); scriptAllfontsMotif = new HashMap(); alphabeticSuffix = new HashMap(); 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 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: "); } 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); } } } } }