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 }