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