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