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