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