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) { 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 (FontManager.eudcFont != 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 FontManager.eudcFont.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] = FontManager.eudcFont; 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 (FontManager.class) { 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 synchronized (FontManager.class) { 239 if (componentNames == null) { 240 componentNames = new String[numSlots]; 241 } 242 if (components[slot] == null) { 243 /* Warning: it is possible that the returned component is 244 * not derived from the file name argument, this can happen if: 245 * - the file can't be found 246 * - the file has a bad font 247 * - the font in the file is superseded by a more complete one 248 * This should not be a problem for composite font as it will 249 * make no further use of this file, but code debuggers/ 250 * maintainers need to be conscious of this possibility. 251 */ 252 if (componentFileNames != null && 253 componentFileNames[slot] != null) { 254 components[slot] = FontManager.initialiseDeferredFont 255 (componentFileNames[slot]); 256 } 257 258 if (components[slot] == null) { 259 components[slot] = FontManager.getDefaultPhysicalFont(); 260 } 261 String name = components[slot].getFontName(null); 262 if (componentNames[slot] == null) { 263 componentNames[slot] = name; 264 } else if (!componentNames[slot].equalsIgnoreCase(name)) { 265 components[slot] = 266 (PhysicalFont) 267 FontManager.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 try { 337 PhysicalFont font = components[slot]; 338 if (font == null) { 339 try { 340 font = (PhysicalFont)FontManager. 341 findFont2D(componentNames[slot], style, 342 FontManager.PHYSICAL_FALLBACK); 343 components[slot] = font; 344 } catch (ClassCastException cce) { 345 font = FontManager.getDefaultPhysicalFont(); 346 } 347 } 348 return font; 349 } catch (Exception e) { 350 return FontManager.getDefaultPhysicalFont(); 351 } 352 } 353 354 FontStrike createStrike(FontStrikeDesc desc) { 355 return new CompositeStrike(this, desc); 356 } 357 358 /* This is set false when the composite is created using a specified 359 * physical font as the first slot and called by code which 360 * selects composites by locale preferences to know that this 361 * isn't a font which should be adjusted. 362 */ 363 public boolean isStdComposite() { 364 return isStdComposite; 365 } 366 367 /* This isn't very efficient but its infrequently used. 368 * StandardGlyphVector uses it when the client assigns the glyph codes. 369 * These may not be valid. This validates them substituting the missing 370 * glyph elsewhere. 371 */ 372 protected int getValidatedGlyphCode(int glyphCode) { 373 int slot = glyphCode >>> 24; 374 if (slot >= numSlots) { 375 return getMapper().getMissingGlyphCode(); 376 } 377 378 int slotglyphCode = glyphCode & CompositeStrike.SLOTMASK; 379 PhysicalFont slotFont = getSlotFont(slot); 380 if (slotFont.getValidatedGlyphCode(slotglyphCode) == 381 slotFont.getMissingGlyphCode()) { 382 return getMapper().getMissingGlyphCode(); 383 } else { 384 return glyphCode; 385 } 386 } 387 388 public CharToGlyphMapper getMapper() { 389 if (mapper == null) { 390 mapper = new CompositeGlyphMapper(this); 391 } 392 return mapper; 393 } 394 395 public boolean hasSupplementaryChars() { 396 for (int i=0; i<numSlots; i++) { 397 if (getSlotFont(i).hasSupplementaryChars()) { 398 return true; 399 } 400 } 401 return false; 402 } 403 404 public int getNumGlyphs() { 405 if (numGlyphs == 0) { 406 numGlyphs = getMapper().getNumGlyphs(); 407 } 408 return numGlyphs; 409 } 410 411 public int getMissingGlyphCode() { 412 return getMapper().getMissingGlyphCode(); 413 } 414 415 public boolean canDisplay(char c) { 416 return getMapper().canDisplay(c); 417 } 418 419 public boolean useAAForPtSize(int ptsize) { 420 /* Find the first slot that supports the default encoding and use 421 * that to decide the "gasp" behaviour of the composite font. 422 * REMIND "default encoding" isn't applicable to a Unicode locale 423 * and we need to replace this with a better mechanism for deciding 424 * if a font "supports" the user's language. See TrueTypeFont.java 425 */ 426 if (localeSlot == -1) { 427 /* Ordinarily check numMetricsSlots, but non-standard composites 428 * set that to "1" whilst not necessarily supporting the default 429 * encoding with that first slot. In such a case check all slots. 430 */ 431 int numCoreSlots = numMetricsSlots; 432 if (numCoreSlots == 1 && !isStdComposite()) { 433 numCoreSlots = numSlots; 434 } 435 for (int slot=0; slot<numCoreSlots; slot++) { 436 if (getSlotFont(slot).supportsEncoding(null)) { 437 localeSlot = slot; 438 break; 439 } 440 } 441 if (localeSlot == -1) { 442 localeSlot = 0; 443 } 444 } 445 return getSlotFont(localeSlot).useAAForPtSize(ptsize); 446 } 447 448 public String toString() { 449 String ls = (String)java.security.AccessController.doPrivileged( 450 new sun.security.action.GetPropertyAction("line.separator")); 451 String componentsStr = ""; 452 for (int i=0; i<numSlots; i++) { 453 componentsStr += " Slot["+i+"]="+getSlotFont(i)+ls; 454 } 455 return "** Composite Font: Family=" + familyName + 456 " Name=" + fullName + " style=" + style + ls + componentsStr; 457 } 458 }