View Javadoc
1   /*
2    * Copyright (c) 2003, 2006, 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  
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 }