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