1 /* 2 * Copyright 2003-2006 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.font; 27 28 import java.awt.Font; 29 30 /* Remind: need to enhance to extend component list with a fallback 31 * list, which is not used in metrics or queries on the composite, but 32 * is used in drawing primitives and queries which supply an actual string. 33 * ie for a codepoint that is only in a fallback, font-wide queries such 34 * as FontMetrics.getHeight() will not take it into account. 35 * But getStringBounds(..) would take it into account. 36 * Its fuzzier for queries such as "canDisplay". If this does not include 37 * the fallback, then we probably want to add "canDisplayFallback()" 38 * But its probably OK to include it so long as only composites include 39 * fallbacks. If physicals do then it would be really confusing .. 40 */ 41 public final class CompositeFont extends Font2D { 42 43 private boolean[] deferredInitialisation; 44 String[] componentFileNames; 45 String[] componentNames; 46 /* because components can be lazily initialised the components field is 47 * private, to ensure all clients call getSlotFont() 48 */ 49 private PhysicalFont[] components; 50 int numSlots; 51 int numMetricsSlots; 52 int[] exclusionRanges; 53 int[] maxIndices; 54 int numGlyphs = 0; 55 int localeSlot = -1; // primary slot for this locale. 56 57 /* See isStdComposite() for when/how this is used */ 58 boolean isStdComposite = true; 59 60 public CompositeFont(String name, String[] compFileNames, 61 String[] compNames, int metricsSlotCnt, 62 int[] exclRanges, int[] maxIndexes, 63 boolean defer, SunFontManager fm) { 64 65 handle = new Font2DHandle(this); 66 fullName = name; 67 componentFileNames = compFileNames; 68 componentNames = compNames; 69 if (compNames == null) { 70 numSlots = componentFileNames.length; 71 } else { 72 numSlots = componentNames.length; 73 } 74 75 /* Only the first "numMetricsSlots" slots are used for font metrics. 76 * the rest are considered "fallback" slots". 77 */ 78 numMetricsSlots = metricsSlotCnt; 79 exclusionRanges = exclRanges; 80 maxIndices = maxIndexes; 81 82 /* 83 * See if this is a windows locale which has a system EUDC font. 84 * If so add it as the final fallback component of the composite. 85 * The caller could be responsible for this, but for now it seems 86 * better that it is handled internally to the CompositeFont class. 87 */ 88 if (fm.getEUDCFont() != null) { 89 numSlots++; 90 if (componentNames != null) { 91 componentNames = new String[numSlots]; 92 System.arraycopy(compNames, 0, componentNames, 0, numSlots-1); 93 componentNames[numSlots-1] = 94 fm.getEUDCFont().getFontName(null); 95 } 96 if (componentFileNames != null) { 97 componentFileNames = new String[numSlots]; 98 System.arraycopy(compFileNames, 0, 99 componentFileNames, 0, numSlots-1); 100 } 101 components = new PhysicalFont[numSlots]; 102 components[numSlots-1] = fm.getEUDCFont(); 103 deferredInitialisation = new boolean[numSlots]; 104 if (defer) { 105 for (int i=0; i<numSlots-1; i++) { 106 deferredInitialisation[i] = true; 107 } 108 } 109 } else { 110 components = new PhysicalFont[numSlots]; 111 deferredInitialisation = new boolean[numSlots]; 112 if (defer) { 113 for (int i=0; i<numSlots; i++) { 114 deferredInitialisation[i] = true; 115 } 116 } 117 } 118 119 fontRank = Font2D.FONT_CONFIG_RANK; 120 121 int index = fullName.indexOf('.'); 122 if (index>0) { 123 familyName = fullName.substring(0, index); 124 /* composites don't call setStyle() as parsing the style 125 * takes place at the same time as parsing the family name. 126 * Do I really have to parse the style from the name? 127 * Need to look into having the caller provide this. */ 128 if (index+1 < fullName.length()) { 129 String styleStr = fullName.substring(index+1); 130 if ("plain".equals(styleStr)) { 131 style = Font.PLAIN; 132 } else if ("bold".equals(styleStr)) { 133 style = Font.BOLD; 134 } else if ("italic".equals(styleStr)) { 135 style = Font.ITALIC; 136 } else if ("bolditalic".equals(styleStr)) { 137 style = Font.BOLD | Font.ITALIC; 138 } 139 } 140 } else { 141 familyName = fullName; 142 } 143 } 144 145 /* This method is currently intended to be called only from 146 * FontManager.getCompositeFontUIResource(Font) 147 * It creates a new CompositeFont with the contents of the Physical 148 * one pre-pended as slot 0. 149 */ 150 CompositeFont(PhysicalFont physFont, CompositeFont compFont) { 151 152 isStdComposite = false; 153 handle = new Font2DHandle(this); 154 fullName = physFont.fullName; 155 familyName = physFont.familyName; 156 style = physFont.style; 157 158 numMetricsSlots = 1; /* Only the physical Font */ 159 numSlots = compFont.numSlots+1; 160 161 /* Ugly though it is, we synchronize here on the FontManager class 162 * because it is the lock used to do deferred initialisation. 163 * We need to ensure that the arrays have consistent information. 164 * But it may be possible to dispense with the synchronisation if 165 * it is harmless that we do not know a slot is already initialised 166 * and just need to discover that and mark it so. 167 */ 168 synchronized (FontManagerFactory.getInstance()) { 169 components = new PhysicalFont[numSlots]; 170 components[0] = physFont; 171 System.arraycopy(compFont.components, 0, 172 components, 1, compFont.numSlots); 173 174 if (compFont.componentNames != null) { 175 componentNames = new String[numSlots]; 176 componentNames[0] = physFont.fullName; 177 System.arraycopy(compFont.componentNames, 0, 178 componentNames, 1, compFont.numSlots); 179 } 180 if (compFont.componentFileNames != null) { 181 componentFileNames = new String[numSlots]; 182 componentFileNames[0] = null; 183 System.arraycopy(compFont.componentFileNames, 0, 184 componentFileNames, 1, compFont.numSlots); 185 } 186 deferredInitialisation = new boolean[numSlots]; 187 deferredInitialisation[0] = false; 188 System.arraycopy(compFont.deferredInitialisation, 0, 189 deferredInitialisation, 1, compFont.numSlots); 190 } 191 } 192 193 /* This is used for deferred initialisation, so that the components of 194 * a logical font are initialised only when the font is used. 195 * This can have a positive impact on start-up of most UI applications. 196 * Note that this technique cannot be used with a TTC font as it 197 * doesn't know which font in the collection is needed. The solution to 198 * this is that the initialisation checks if the returned font is 199 * really the one it wants by comparing the name against the name that 200 * was passed in (if none was passed in then you aren't using a TTC 201 * as you would have to specify the name in such a case). 202 * Assuming there's only two or three fonts in a collection then it 203 * may be sufficient to verify the returned name is the expected one. 204 * But half the time it won't be. However since initialisation of the 205 * TTC will initialise all its components then just do a findFont2D call 206 * to locate the right one. 207 * This code allows for initialisation of each slot on demand. 208 * There are two issues with this. 209 * 1) All metrics slots probably may be initialised anyway as many 210 * apps will query the overall font metrics. However this is not an 211 * absolute requirement 212 * 2) Some font configuration files on Solaris reference two versions 213 * of a TT font: a Latin-1 version, then a Pan-European version. 214 * One from /usr/openwin/lib/X11/fonts/TrueType, the other from 215 * a euro_fonts directory which is symlinked from numerous locations. 216 * This is difficult to avoid because the two do not share XLFDs so 217 * both will be consequently mapped by separate XLFDs needed by AWT. 218 * The difficulty this presents for lazy initialisation is that if 219 * all the components are not mapped at once, the smaller version may 220 * have been used only to be replaced later, and what is the consequence 221 * for a client that displayed the contents of this font already. 222 * After some thought I think this will not be a problem because when 223 * client tries to display a glyph only in the Euro font, the composite 224 * will ask all components of this font for that glyph and will get 225 * the euro one. Subsequent uses will all come from the 100% compatible 226 * euro one. 227 */ 228 private void doDeferredInitialisation(int slot) { 229 if (deferredInitialisation[slot] == false) { 230 return; 231 } 232 233 /* Synchronize on FontManager so that is the global lock 234 * to update its static set of deferred fonts. 235 * This global lock is rarely likely to be an issue as there 236 * are only going to be a few calls into this code. 237 */ 238 SunFontManager fm = SunFontManager.getInstance(); 239 synchronized (fm) { 240 if (componentNames == null) { 241 componentNames = new String[numSlots]; 242 } 243 if (components[slot] == null) { 244 /* Warning: it is possible that the returned component is 245 * not derived from the file name argument, this can happen if: 246 * - the file can't be found 247 * - the file has a bad font 248 * - the font in the file is superseded by a more complete one 249 * This should not be a problem for composite font as it will 250 * make no further use of this file, but code debuggers/ 251 * maintainers need to be conscious of this possibility. 252 */ 253 if (componentFileNames != null && 254 componentFileNames[slot] != null) { 255 components[slot] = 256 fm.initialiseDeferredFont(componentFileNames[slot]); 257 } 258 259 if (components[slot] == null) { 260 components[slot] = fm.getDefaultPhysicalFont(); 261 } 262 String name = components[slot].getFontName(null); 263 if (componentNames[slot] == null) { 264 componentNames[slot] = name; 265 } else if (!componentNames[slot].equalsIgnoreCase(name)) { 266 components[slot] = 267 (PhysicalFont) fm.findFont2D(componentNames[slot], 268 style, 269 FontManager.PHYSICAL_FALLBACK); 270 } 271 } 272 deferredInitialisation[slot] = false; 273 } 274 } 275 276 /* To called only by FontManager.replaceFont */ 277 void replaceComponentFont(PhysicalFont oldFont, PhysicalFont newFont) { 278 if (components == null) { 279 return; 280 } 281 for (int slot=0; slot<numSlots; slot++) { 282 if (components[slot] == oldFont) { 283 components[slot] = newFont; 284 if (componentNames != null) { 285 componentNames[slot] = newFont.getFontName(null); 286 } 287 } 288 } 289 } 290 291 public boolean isExcludedChar(int slot, int charcode) { 292 293 if (exclusionRanges == null || maxIndices == null || 294 slot >= numMetricsSlots) { 295 return false; 296 } 297 298 int minIndex = 0; 299 int maxIndex = maxIndices[slot]; 300 if (slot > 0) { 301 minIndex = maxIndices[slot - 1]; 302 } 303 int curIndex = minIndex; 304 while (maxIndex > curIndex) { 305 if ((charcode >= exclusionRanges[curIndex]) 306 && (charcode <= exclusionRanges[curIndex+1])) { 307 return true; // excluded 308 } 309 curIndex += 2; 310 } 311 return false; 312 } 313 314 public void getStyleMetrics(float pointSize, float[] metrics, int offset) { 315 PhysicalFont font = getSlotFont(0); 316 if (font == null) { // possible? 317 super.getStyleMetrics(pointSize, metrics, offset); 318 } else { 319 font.getStyleMetrics(pointSize, metrics, offset); 320 } 321 } 322 323 public int getNumSlots() { 324 return numSlots; 325 } 326 327 public PhysicalFont getSlotFont(int slot) { 328 /* This is essentially the runtime overhead for deferred font 329 * initialisation: a boolean test on obtaining a slot font, 330 * which will happen per slot, on initialisation of a strike 331 * (as that is the only frequent call site of this method. 332 */ 333 if (deferredInitialisation[slot]) { 334 doDeferredInitialisation(slot); 335 } 336 SunFontManager fm = SunFontManager.getInstance(); 337 try { 338 PhysicalFont font = components[slot]; 339 if (font == null) { 340 try { 341 font = (PhysicalFont) fm. 342 findFont2D(componentNames[slot], style, 343 FontManager.PHYSICAL_FALLBACK); 344 components[slot] = font; 345 } catch (ClassCastException cce) { 346 font = fm.getDefaultPhysicalFont(); 347 } 348 } 349 return font; 350 } catch (Exception e) { 351 return fm.getDefaultPhysicalFont(); 352 } 353 } 354 355 FontStrike createStrike(FontStrikeDesc desc) { 356 return new CompositeStrike(this, desc); 357 } 358 359 /* This is set false when the composite is created using a specified 360 * physical font as the first slot and called by code which 361 * selects composites by locale preferences to know that this 362 * isn't a font which should be adjusted. 363 */ 364 public boolean isStdComposite() { 365 return isStdComposite; 366 } 367 368 /* This isn't very efficient but its infrequently used. 369 * StandardGlyphVector uses it when the client assigns the glyph codes. 370 * These may not be valid. This validates them substituting the missing 371 * glyph elsewhere. 372 */ 373 protected int getValidatedGlyphCode(int glyphCode) { 374 int slot = glyphCode >>> 24; 375 if (slot >= numSlots) { 376 return getMapper().getMissingGlyphCode(); 377 } 378 379 int slotglyphCode = glyphCode & CompositeStrike.SLOTMASK; 380 PhysicalFont slotFont = getSlotFont(slot); 381 if (slotFont.getValidatedGlyphCode(slotglyphCode) == 382 slotFont.getMissingGlyphCode()) { 383 return getMapper().getMissingGlyphCode(); 384 } else { 385 return glyphCode; 386 } 387 } 388 389 public CharToGlyphMapper getMapper() { 390 if (mapper == null) { 391 mapper = new CompositeGlyphMapper(this); 392 } 393 return mapper; 394 } 395 396 public boolean hasSupplementaryChars() { 397 for (int i=0; i<numSlots; i++) { 398 if (getSlotFont(i).hasSupplementaryChars()) { 399 return true; 400 } 401 } 402 return false; 403 } 404 405 public int getNumGlyphs() { 406 if (numGlyphs == 0) { 407 numGlyphs = getMapper().getNumGlyphs(); 408 } 409 return numGlyphs; 410 } 411 412 public int getMissingGlyphCode() { 413 return getMapper().getMissingGlyphCode(); 414 } 415 416 public boolean canDisplay(char c) { 417 return getMapper().canDisplay(c); 418 } 419 420 public boolean useAAForPtSize(int ptsize) { 421 /* Find the first slot that supports the default encoding and use 422 * that to decide the "gasp" behaviour of the composite font. 423 * REMIND "default encoding" isn't applicable to a Unicode locale 424 * and we need to replace this with a better mechanism for deciding 425 * if a font "supports" the user's language. See TrueTypeFont.java 426 */ 427 if (localeSlot == -1) { 428 /* Ordinarily check numMetricsSlots, but non-standard composites 429 * set that to "1" whilst not necessarily supporting the default 430 * encoding with that first slot. In such a case check all slots. 431 */ 432 int numCoreSlots = numMetricsSlots; 433 if (numCoreSlots == 1 && !isStdComposite()) { 434 numCoreSlots = numSlots; 435 } 436 for (int slot=0; slot<numCoreSlots; slot++) { 437 if (getSlotFont(slot).supportsEncoding(null)) { 438 localeSlot = slot; 439 break; 440 } 441 } 442 if (localeSlot == -1) { 443 localeSlot = 0; 444 } 445 } 446 return getSlotFont(localeSlot).useAAForPtSize(ptsize); 447 } 448 449 public String toString() { 450 String ls = (String)java.security.AccessController.doPrivileged( 451 new sun.security.action.GetPropertyAction("line.separator")); 452 String componentsStr = ""; 453 for (int i=0; i<numSlots; i++) { 454 componentsStr += " Slot["+i+"]="+getSlotFont(i)+ls; 455 } 456 return "** Composite Font: Family=" + familyName + 457 " Name=" + fullName + " style=" + style + ls + componentsStr; 458 } 459 }