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