1/* 2 * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package sun.font; 27 28import 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 */ 41public 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 /* We will limit the number of slots to 254. 75 * We store the slot for a glyph id in a byte and we may use one slot 76 * for an EUDC font, and we may also create a composite 77 * using this composite as a backup for a physical font. 78 * So we want to leave space for the two additional slots. 79 */ 80 numSlots = (numSlots <= 254) ? numSlots : 254; 81 82 /* Only the first "numMetricsSlots" slots are used for font metrics. 83 * the rest are considered "fallback" slots". 84 */ 85 numMetricsSlots = metricsSlotCnt; 86 exclusionRanges = exclRanges; 87 maxIndices = maxIndexes; 88 89 /* 90 * See if this is a windows locale which has a system EUDC font. 91 * If so add it as the final fallback component of the composite. 92 * The caller could be responsible for this, but for now it seems 93 * better that it is handled internally to the CompositeFont class. 94 */ 95 if (fm.getEUDCFont() != null) { 96 int msCnt = numMetricsSlots; 97 int fbCnt = numSlots - msCnt; 98 numSlots++; 99 if (componentNames != null) { 100 componentNames = new String[numSlots]; 101 System.arraycopy(compNames, 0, componentNames, 0, msCnt); 102 componentNames[msCnt] = fm.getEUDCFont().getFontName(null); 103 System.arraycopy(compNames, msCnt, 104 componentNames, msCnt+1, fbCnt); 105 } 106 if (componentFileNames != null) { 107 componentFileNames = new String[numSlots]; 108 System.arraycopy(compFileNames, 0, 109 componentFileNames, 0, msCnt); 110 System.arraycopy(compFileNames, msCnt, 111 componentFileNames, msCnt+1, fbCnt); 112 } 113 components = new PhysicalFont[numSlots]; 114 components[msCnt] = fm.getEUDCFont(); 115 deferredInitialisation = new boolean[numSlots]; 116 if (defer) { 117 for (int i=0; i<numSlots-1; i++) { 118 deferredInitialisation[i] = true; 119 } 120 } 121 } else { 122 components = new PhysicalFont[numSlots]; 123 deferredInitialisation = new boolean[numSlots]; 124 if (defer) { 125 for (int i=0; i<numSlots; i++) { 126 deferredInitialisation[i] = true; 127 } 128 } 129 } 130 131 fontRank = Font2D.FONT_CONFIG_RANK; 132 133 int index = fullName.indexOf('.'); 134 if (index>0) { 135 familyName = fullName.substring(0, index); 136 /* composites don't call setStyle() as parsing the style 137 * takes place at the same time as parsing the family name. 138 * Do I really have to parse the style from the name? 139 * Need to look into having the caller provide this. */ 140 if (index+1 < fullName.length()) { 141 String styleStr = fullName.substring(index+1); 142 if ("plain".equals(styleStr)) { 143 style = Font.PLAIN; 144 } else if ("bold".equals(styleStr)) { 145 style = Font.BOLD; 146 } else if ("italic".equals(styleStr)) { 147 style = Font.ITALIC; 148 } else if ("bolditalic".equals(styleStr)) { 149 style = Font.BOLD | Font.ITALIC; 150 } 151 } 152 } else { 153 familyName = fullName; 154 } 155 } 156 157 /* 158 * Build a composite from a set of individual slot fonts. 159 */ 160 CompositeFont(PhysicalFont[] slotFonts) { 161 162 isStdComposite = false; 163 handle = new Font2DHandle(this); 164 fullName = slotFonts[0].fullName; 165 familyName = slotFonts[0].familyName; 166 style = slotFonts[0].style; 167 168 numMetricsSlots = 1; /* Only the physical Font */ 169 numSlots = slotFonts.length; 170 171 components = new PhysicalFont[numSlots]; 172 System.arraycopy(slotFonts, 0, components, 0, numSlots); 173 deferredInitialisation = new boolean[numSlots]; // all false. 174 } 175 176 /* This method is currently intended to be called only from 177 * FontManager.getCompositeFontUIResource(Font) 178 * It creates a new CompositeFont with the contents of the Physical 179 * one pre-pended as slot 0. 180 */ 181 CompositeFont(PhysicalFont physFont, CompositeFont compFont) { 182 183 isStdComposite = false; 184 handle = new Font2DHandle(this); 185 fullName = physFont.fullName; 186 familyName = physFont.familyName; 187 style = physFont.style; 188 189 numMetricsSlots = 1; /* Only the physical Font */ 190 numSlots = compFont.numSlots+1; 191 192 /* Ugly though it is, we synchronize here on the FontManager class 193 * because it is the lock used to do deferred initialisation. 194 * We need to ensure that the arrays have consistent information. 195 * But it may be possible to dispense with the synchronisation if 196 * it is harmless that we do not know a slot is already initialised 197 * and just need to discover that and mark it so. 198 */ 199 synchronized (FontManagerFactory.getInstance()) { 200 components = new PhysicalFont[numSlots]; 201 components[0] = physFont; 202 System.arraycopy(compFont.components, 0, 203 components, 1, compFont.numSlots); 204 205 if (compFont.componentNames != null) { 206 componentNames = new String[numSlots]; 207 componentNames[0] = physFont.fullName; 208 System.arraycopy(compFont.componentNames, 0, 209 componentNames, 1, compFont.numSlots); 210 } 211 if (compFont.componentFileNames != null) { 212 componentFileNames = new String[numSlots]; 213 componentFileNames[0] = null; 214 System.arraycopy(compFont.componentFileNames, 0, 215 componentFileNames, 1, compFont.numSlots); 216 } 217 deferredInitialisation = new boolean[numSlots]; 218 deferredInitialisation[0] = false; 219 System.arraycopy(compFont.deferredInitialisation, 0, 220 deferredInitialisation, 1, compFont.numSlots); 221 } 222 } 223 224 /* This is used for deferred initialisation, so that the components of 225 * a logical font are initialised only when the font is used. 226 * This can have a positive impact on start-up of most UI applications. 227 * Note that this technique cannot be used with a TTC font as it 228 * doesn't know which font in the collection is needed. The solution to 229 * this is that the initialisation checks if the returned font is 230 * really the one it wants by comparing the name against the name that 231 * was passed in (if none was passed in then you aren't using a TTC 232 * as you would have to specify the name in such a case). 233 * Assuming there's only two or three fonts in a collection then it 234 * may be sufficient to verify the returned name is the expected one. 235 * But half the time it won't be. However since initialisation of the 236 * TTC will initialise all its components then just do a findFont2D call 237 * to locate the right one. 238 * This code allows for initialisation of each slot on demand. 239 * There are two issues with this. 240 * 1) All metrics slots probably may be initialised anyway as many 241 * apps will query the overall font metrics. However this is not an 242 * absolute requirement 243 * 2) Some font configuration files on Solaris reference two versions 244 * of a TT font: a Latin-1 version, then a Pan-European version. 245 * One from /usr/openwin/lib/X11/fonts/TrueType, the other from 246 * a euro_fonts directory which is symlinked from numerous locations. 247 * This is difficult to avoid because the two do not share XLFDs so 248 * both will be consequently mapped by separate XLFDs needed by AWT. 249 * The difficulty this presents for lazy initialisation is that if 250 * all the components are not mapped at once, the smaller version may 251 * have been used only to be replaced later, and what is the consequence 252 * for a client that displayed the contents of this font already. 253 * After some thought I think this will not be a problem because when 254 * client tries to display a glyph only in the Euro font, the composite 255 * will ask all components of this font for that glyph and will get 256 * the euro one. Subsequent uses will all come from the 100% compatible 257 * euro one. 258 */ 259 private void doDeferredInitialisation(int slot) { 260 if (deferredInitialisation[slot] == false) { 261 return; 262 } 263 264 /* Synchronize on FontManager so that is the global lock 265 * to update its static set of deferred fonts. 266 * This global lock is rarely likely to be an issue as there 267 * are only going to be a few calls into this code. 268 */ 269 SunFontManager fm = SunFontManager.getInstance(); 270 synchronized (fm) { 271 if (componentNames == null) { 272 componentNames = new String[numSlots]; 273 } 274 if (components[slot] == null) { 275 /* Warning: it is possible that the returned component is 276 * not derived from the file name argument, this can happen if: 277 * - the file can't be found 278 * - the file has a bad font 279 * - the font in the file is superseded by a more complete one 280 * This should not be a problem for composite font as it will 281 * make no further use of this file, but code debuggers/ 282 * maintainers need to be conscious of this possibility. 283 */ 284 if (componentFileNames != null && 285 componentFileNames[slot] != null) { 286 components[slot] = 287 fm.initialiseDeferredFont(componentFileNames[slot]); 288 } 289 290 if (components[slot] == null) { 291 components[slot] = fm.getDefaultPhysicalFont(); 292 } 293 String name = components[slot].getFontName(null); 294 if (componentNames[slot] == null) { 295 componentNames[slot] = name; 296 } else if (!componentNames[slot].equalsIgnoreCase(name)) { 297 /* If a component specifies the file with a bad font, 298 * the corresponding slot will be initialized by 299 * default physical font. In such case findFont2D may 300 * return composite font which cannot be casted to 301 * physical font. 302 */ 303 try { 304 components[slot] = 305 (PhysicalFont) fm.findFont2D(componentNames[slot], 306 style, 307 FontManager.PHYSICAL_FALLBACK); 308 } catch (ClassCastException cce) { 309 /* Assign default physical font to the slot */ 310 components[slot] = fm.getDefaultPhysicalFont(); 311 } 312 } 313 } 314 deferredInitialisation[slot] = false; 315 } 316 } 317 318 /* To called only by FontManager.replaceFont */ 319 void replaceComponentFont(PhysicalFont oldFont, PhysicalFont newFont) { 320 if (components == null) { 321 return; 322 } 323 for (int slot=0; slot<numSlots; slot++) { 324 if (components[slot] == oldFont) { 325 components[slot] = newFont; 326 if (componentNames != null) { 327 componentNames[slot] = newFont.getFontName(null); 328 } 329 } 330 } 331 } 332 333 public boolean isExcludedChar(int slot, int charcode) { 334 335 if (exclusionRanges == null || maxIndices == null || 336 slot >= numMetricsSlots) { 337 return false; 338 } 339 340 int minIndex = 0; 341 int maxIndex = maxIndices[slot]; 342 if (slot > 0) { 343 minIndex = maxIndices[slot - 1]; 344 } 345 int curIndex = minIndex; 346 while (maxIndex > curIndex) { 347 if ((charcode >= exclusionRanges[curIndex]) 348 && (charcode <= exclusionRanges[curIndex+1])) { 349 return true; // excluded 350 } 351 curIndex += 2; 352 } 353 return false; 354 } 355 356 public void getStyleMetrics(float pointSize, float[] metrics, int offset) { 357 PhysicalFont font = getSlotFont(0); 358 if (font == null) { // possible? 359 super.getStyleMetrics(pointSize, metrics, offset); 360 } else { 361 font.getStyleMetrics(pointSize, metrics, offset); 362 } 363 } 364 365 public int getNumSlots() { 366 return numSlots; 367 } 368 369 public PhysicalFont getSlotFont(int slot) { 370 /* This is essentially the runtime overhead for deferred font 371 * initialisation: a boolean test on obtaining a slot font, 372 * which will happen per slot, on initialisation of a strike 373 * (as that is the only frequent call site of this method. 374 */ 375 if (deferredInitialisation[slot]) { 376 doDeferredInitialisation(slot); 377 } 378 SunFontManager fm = SunFontManager.getInstance(); 379 try { 380 PhysicalFont font = components[slot]; 381 if (font == null) { 382 try { 383 font = (PhysicalFont) fm. 384 findFont2D(componentNames[slot], style, 385 FontManager.PHYSICAL_FALLBACK); 386 components[slot] = font; 387 } catch (ClassCastException cce) { 388 font = fm.getDefaultPhysicalFont(); 389 } 390 } 391 return font; 392 } catch (Exception e) { 393 return fm.getDefaultPhysicalFont(); 394 } 395 } 396 397 FontStrike createStrike(FontStrikeDesc desc) { 398 return new CompositeStrike(this, desc); 399 } 400 401 /* This is set false when the composite is created using a specified 402 * physical font as the first slot and called by code which 403 * selects composites by locale preferences to know that this 404 * isn't a font which should be adjusted. 405 */ 406 public boolean isStdComposite() { 407 return isStdComposite; 408 } 409 410 /* This isn't very efficient but its infrequently used. 411 * StandardGlyphVector uses it when the client assigns the glyph codes. 412 * These may not be valid. This validates them substituting the missing 413 * glyph elsewhere. 414 */ 415 protected int getValidatedGlyphCode(int glyphCode) { 416 int slot = glyphCode >>> 24; 417 if (slot >= numSlots) { 418 return getMapper().getMissingGlyphCode(); 419 } 420 421 int slotglyphCode = glyphCode & CompositeStrike.SLOTMASK; 422 PhysicalFont slotFont = getSlotFont(slot); 423 if (slotFont.getValidatedGlyphCode(slotglyphCode) == 424 slotFont.getMissingGlyphCode()) { 425 return getMapper().getMissingGlyphCode(); 426 } else { 427 return glyphCode; 428 } 429 } 430 431 public CharToGlyphMapper getMapper() { 432 if (mapper == null) { 433 mapper = new CompositeGlyphMapper(this); 434 } 435 return mapper; 436 } 437 438 public boolean hasSupplementaryChars() { 439 for (int i=0; i<numSlots; i++) { 440 if (getSlotFont(i).hasSupplementaryChars()) { 441 return true; 442 } 443 } 444 return false; 445 } 446 447 public int getNumGlyphs() { 448 if (numGlyphs == 0) { 449 numGlyphs = getMapper().getNumGlyphs(); 450 } 451 return numGlyphs; 452 } 453 454 public int getMissingGlyphCode() { 455 return getMapper().getMissingGlyphCode(); 456 } 457 458 public boolean canDisplay(char c) { 459 return getMapper().canDisplay(c); 460 } 461 462 public boolean useAAForPtSize(int ptsize) { 463 /* Find the first slot that supports the default encoding and use 464 * that to decide the "gasp" behaviour of the composite font. 465 * REMIND "default encoding" isn't applicable to a Unicode locale 466 * and we need to replace this with a better mechanism for deciding 467 * if a font "supports" the user's language. See TrueTypeFont.java 468 */ 469 if (localeSlot == -1) { 470 /* Ordinarily check numMetricsSlots, but non-standard composites 471 * set that to "1" whilst not necessarily supporting the default 472 * encoding with that first slot. In such a case check all slots. 473 */ 474 int numCoreSlots = numMetricsSlots; 475 if (numCoreSlots == 1 && !isStdComposite()) { 476 numCoreSlots = numSlots; 477 } 478 for (int slot=0; slot<numCoreSlots; slot++) { 479 if (getSlotFont(slot).supportsEncoding(null)) { 480 localeSlot = slot; 481 break; 482 } 483 } 484 if (localeSlot == -1) { 485 localeSlot = 0; 486 } 487 } 488 return getSlotFont(localeSlot).useAAForPtSize(ptsize); 489 } 490 491 public String toString() { 492 String ls = System.lineSeparator(); 493 String componentsStr = ""; 494 for (int i=0; i<numSlots; i++) { 495 componentsStr += " Slot["+i+"]="+getSlotFont(i)+ls; 496 } 497 return "** Composite Font: Family=" + familyName + 498 " Name=" + fullName + " style=" + style + ls + componentsStr; 499 } 500} 501