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