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