459 lines · 18.5 KB · Java
RawDownload
/* * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.font; import java.awt.Font; /* Remind: need to enhance to extend component list with a fallback * list, which is not used in metrics or queries on the composite, but * is used in drawing primitives and queries which supply an actual string. * ie for a codepoint that is only in a fallback, font-wide queries such * as FontMetrics.getHeight() will not take it into account. * But getStringBounds(..) would take it into account. * Its fuzzier for queries such as "canDisplay". If this does not include * the fallback, then we probably want to add "canDisplayFallback()" * But its probably OK to include it so long as only composites include * fallbacks. If physicals do then it would be really confusing .. */public final class CompositeFont extends Font2D {     private boolean[] deferredInitialisation;    String[] componentFileNames;    String[] componentNames;    /* because components can be lazily initialised the components field is     * private, to ensure all clients call getSlotFont()     */    private PhysicalFont[] components;    int numSlots;    int numMetricsSlots;    int[] exclusionRanges;    int[] maxIndices;    int numGlyphs = 0;    int localeSlot = -1; // primary slot for this locale.     /* See isStdComposite() for when/how this is used */    boolean isStdComposite = true;     public CompositeFont(String name, String[] compFileNames,                         String[] compNames, int metricsSlotCnt,                         int[] exclRanges, int[] maxIndexes,                         boolean defer, SunFontManager fm) {         handle = new Font2DHandle(this);        fullName = name;        componentFileNames = compFileNames;        componentNames = compNames;        if (compNames == null) {            numSlots = componentFileNames.length;        } else {            numSlots = componentNames.length;        }         /* Only the first "numMetricsSlots" slots are used for font metrics.         * the rest are considered "fallback" slots".         */        numMetricsSlots = metricsSlotCnt;        exclusionRanges = exclRanges;        maxIndices = maxIndexes;         /*         * See if this is a windows locale which has a system EUDC font.         * If so add it as the final fallback component of the composite.         * The caller could be responsible for this, but for now it seems         * better that it is handled internally to the CompositeFont class.         */        if (fm.getEUDCFont() != null) {            numSlots++;            if (componentNames != null) {                componentNames = new String[numSlots];                System.arraycopy(compNames, 0, componentNames, 0, numSlots-1);                componentNames[numSlots-1] =                    fm.getEUDCFont().getFontName(null);            }            if (componentFileNames != null) {                componentFileNames = new String[numSlots];                System.arraycopy(compFileNames, 0,                                  componentFileNames, 0, numSlots-1);            }            components = new PhysicalFont[numSlots];            components[numSlots-1] = fm.getEUDCFont();            deferredInitialisation = new boolean[numSlots];            if (defer) {                for (int i=0; i<numSlots-1; i++) {                    deferredInitialisation[i] = true;                }            }        } else {            components = new PhysicalFont[numSlots];            deferredInitialisation = new boolean[numSlots];            if (defer) {                for (int i=0; i<numSlots; i++) {                    deferredInitialisation[i] = true;                }            }        }         fontRank = Font2D.FONT_CONFIG_RANK;         int index = fullName.indexOf('.');        if (index>0) {            familyName = fullName.substring(0, index);            /* composites don't call setStyle() as parsing the style             * takes place at the same time as parsing the family name.             * Do I really have to parse the style from the name?             * Need to look into having the caller provide this. */            if (index+1 < fullName.length()) {                String styleStr = fullName.substring(index+1);                if ("plain".equals(styleStr)) {                    style = Font.PLAIN;                } else if ("bold".equals(styleStr)) {                    style = Font.BOLD;                } else if ("italic".equals(styleStr)) {                    style = Font.ITALIC;                } else if ("bolditalic".equals(styleStr)) {                    style = Font.BOLD | Font.ITALIC;                }            }        } else {            familyName = fullName;        }    }     /* This method is currently intended to be called only from     * FontManager.getCompositeFontUIResource(Font)     * It creates a new CompositeFont with the contents of the Physical     * one pre-pended as slot 0.     */    CompositeFont(PhysicalFont physFont, CompositeFont compFont) {         isStdComposite = false;        handle = new Font2DHandle(this);        fullName = physFont.fullName;        familyName = physFont.familyName;        style = physFont.style;         numMetricsSlots = 1; /* Only the physical Font */        numSlots = compFont.numSlots+1;         /* Ugly though it is, we synchronize here on the FontManager class         * because it is the lock used to do deferred initialisation.         * We need to ensure that the arrays have consistent information.         * But it may be possible to dispense with the synchronisation if         * it is harmless that we do not know a slot is already initialised         * and just need to discover that and mark it so.         */        synchronized (FontManagerFactory.getInstance()) {            components = new PhysicalFont[numSlots];            components[0] = physFont;            System.arraycopy(compFont.components, 0,                             components, 1, compFont.numSlots);             if (compFont.componentNames != null) {                componentNames = new String[numSlots];                componentNames[0] = physFont.fullName;                System.arraycopy(compFont.componentNames, 0,                                 componentNames, 1, compFont.numSlots);            }            if (compFont.componentFileNames != null) {                componentFileNames = new String[numSlots];                componentFileNames[0] = null;                System.arraycopy(compFont.componentFileNames, 0,                                  componentFileNames, 1, compFont.numSlots);            }            deferredInitialisation = new boolean[numSlots];            deferredInitialisation[0] = false;            System.arraycopy(compFont.deferredInitialisation, 0,                             deferredInitialisation, 1, compFont.numSlots);        }    }     /* This is used for deferred initialisation, so that the components of     * a logical font are initialised only when the font is used.     * This can have a positive impact on start-up of most UI applications.     * Note that this technique cannot be used with a TTC font as it     * doesn't know which font in the collection is needed. The solution to     * this is that the initialisation checks if the returned font is     * really the one it wants by comparing the name against the name that     * was passed in (if none was passed in then you aren't using a TTC     * as you would have to specify the name in such a case).     * Assuming there's only two or three fonts in a collection then it     * may be sufficient to verify the returned name is the expected one.     * But half the time it won't be. However since initialisation of the     * TTC will initialise all its components then just do a findFont2D call     * to locate the right one.     * This code allows for initialisation of each slot on demand.     * There are two issues with this.     * 1) All metrics slots probably may be initialised anyway as many     * apps will query the overall font metrics. However this is not an     * absolute requirement     * 2) Some font configuration files on Solaris reference two versions     * of a TT font: a Latin-1 version, then a Pan-European version.     * One from /usr/openwin/lib/X11/fonts/TrueType, the other from     * a euro_fonts directory which is symlinked from numerous locations.     * This is difficult to avoid because the two do not share XLFDs so     * both will be consequently mapped by separate XLFDs needed by AWT.     * The difficulty this presents for lazy initialisation is that if     * all the components are not mapped at once, the smaller version may     * have been used only to be replaced later, and what is the consequence     * for a client that displayed the contents of this font already.     * After some thought I think this will not be a problem because when     * client tries to display a glyph only in the Euro font, the composite     * will ask all components of this font for that glyph and will get     * the euro one. Subsequent uses will all come from the 100% compatible     * euro one.     */    private void doDeferredInitialisation(int slot) {        if (deferredInitialisation[slot] == false) {            return;        }         /* Synchronize on FontManager so that is the global lock         * to update its static set of deferred fonts.         * This global lock is rarely likely to be an issue as there         * are only going to be a few calls into this code.         */        SunFontManager fm = SunFontManager.getInstance();        synchronized (fm) {            if (componentNames == null) {                componentNames = new String[numSlots];            }            if (components[slot] == null) {                /* Warning: it is possible that the returned component is                 * not derived from the file name argument, this can happen if:                 * - the file can't be found                 * - the file has a bad font                 * - the font in the file is superseded by a more complete one                 * This should not be a problem for composite font as it will                 * make no further use of this file, but code debuggers/                 * maintainers need to be conscious of this possibility.                 */                if (componentFileNames != null &&                    componentFileNames[slot] != null) {                    components[slot] =                        fm.initialiseDeferredFont(componentFileNames[slot]);                }                 if (components[slot] == null) {                    components[slot] = fm.getDefaultPhysicalFont();                }                String name = components[slot].getFontName(null);                if (componentNames[slot] == null) {                    componentNames[slot] = name;                } else if (!componentNames[slot].equalsIgnoreCase(name)) {                    components[slot] =                        (PhysicalFont) fm.findFont2D(componentNames[slot],                                                     style,                                                FontManager.PHYSICAL_FALLBACK);                }            }            deferredInitialisation[slot] = false;        }    }     /* To called only by FontManager.replaceFont */    void replaceComponentFont(PhysicalFont oldFont, PhysicalFont newFont) {        if (components == null) {            return;        }        for (int slot=0; slot<numSlots; slot++) {            if (components[slot] == oldFont) {                components[slot] = newFont;                if (componentNames != null) {                    componentNames[slot] = newFont.getFontName(null);                }            }        }    }     public boolean isExcludedChar(int slot, int charcode) {         if (exclusionRanges == null || maxIndices == null ||            slot >= numMetricsSlots) {            return false;        }         int minIndex = 0;        int maxIndex = maxIndices[slot];        if (slot > 0) {            minIndex = maxIndices[slot - 1];        }        int curIndex = minIndex;        while (maxIndex > curIndex) {            if ((charcode >= exclusionRanges[curIndex])                && (charcode <= exclusionRanges[curIndex+1])) {                return true;      // excluded            }            curIndex += 2;        }        return false;    }     public void getStyleMetrics(float pointSize, float[] metrics, int offset) {        PhysicalFont font = getSlotFont(0);        if (font == null) { // possible?            super.getStyleMetrics(pointSize, metrics, offset);        } else {            font.getStyleMetrics(pointSize, metrics, offset);        }    }     public int getNumSlots() {        return numSlots;    }     public PhysicalFont getSlotFont(int slot) {        /* This is essentially the runtime overhead for deferred font         * initialisation: a boolean test on obtaining a slot font,         * which will happen per slot, on initialisation of a strike         * (as that is the only frequent call site of this method.         */        if (deferredInitialisation[slot]) {            doDeferredInitialisation(slot);        }        SunFontManager fm = SunFontManager.getInstance();        try {            PhysicalFont font = components[slot];            if (font == null) {                try {                    font = (PhysicalFont) fm.                        findFont2D(componentNames[slot], style,                                   FontManager.PHYSICAL_FALLBACK);                    components[slot] = font;                } catch (ClassCastException cce) {                    font = fm.getDefaultPhysicalFont();                }            }            return font;        } catch (Exception e) {            return fm.getDefaultPhysicalFont();        }    }     FontStrike createStrike(FontStrikeDesc desc) {        return new CompositeStrike(this, desc);    }     /* This is set false when the composite is created using a specified     * physical font as the first slot and called by code which     * selects composites by locale preferences to know that this     * isn't a font which should be adjusted.     */    public boolean isStdComposite() {        return isStdComposite;    }     /* This isn't very efficient but its infrequently used.     * StandardGlyphVector uses it when the client assigns the glyph codes.     * These may not be valid. This validates them substituting the missing     * glyph elsewhere.     */    protected int getValidatedGlyphCode(int glyphCode) {        int slot = glyphCode >>> 24;        if (slot >= numSlots) {            return getMapper().getMissingGlyphCode();        }         int slotglyphCode = glyphCode & CompositeStrike.SLOTMASK;        PhysicalFont slotFont = getSlotFont(slot);        if (slotFont.getValidatedGlyphCode(slotglyphCode) ==            slotFont.getMissingGlyphCode()) {            return getMapper().getMissingGlyphCode();        } else {            return glyphCode;        }    }     public CharToGlyphMapper getMapper() {        if (mapper == null) {            mapper = new CompositeGlyphMapper(this);        }        return mapper;    }     public boolean hasSupplementaryChars() {        for (int i=0; i<numSlots; i++) {            if (getSlotFont(i).hasSupplementaryChars()) {                return true;            }        }        return false;    }     public int getNumGlyphs() {        if (numGlyphs == 0) {            numGlyphs = getMapper().getNumGlyphs();        }        return numGlyphs;    }     public int getMissingGlyphCode() {        return getMapper().getMissingGlyphCode();    }     public boolean canDisplay(char c) {        return getMapper().canDisplay(c);    }     public boolean useAAForPtSize(int ptsize) {        /* Find the first slot that supports the default encoding and use         * that to decide the "gasp" behaviour of the composite font.         * REMIND "default encoding" isn't applicable to a Unicode locale         * and we need to replace this with a better mechanism for deciding         * if a font "supports" the user's language. See TrueTypeFont.java         */        if (localeSlot == -1) {            /* Ordinarily check numMetricsSlots, but non-standard composites             * set that to "1" whilst not necessarily supporting the default             * encoding with that first slot. In such a case check all slots.             */            int numCoreSlots = numMetricsSlots;            if (numCoreSlots == 1 && !isStdComposite()) {                numCoreSlots = numSlots;            }            for (int slot=0; slot<numCoreSlots; slot++) {                 if (getSlotFont(slot).supportsEncoding(null)) {                     localeSlot = slot;                     break;                 }            }            if (localeSlot == -1) {                localeSlot = 0;            }        }        return getSlotFont(localeSlot).useAAForPtSize(ptsize);    }     public String toString() {        String ls = (String)java.security.AccessController.doPrivileged(                new sun.security.action.GetPropertyAction("line.separator"));        String componentsStr = "";        for (int i=0; i<numSlots; i++) {            componentsStr += "    Slot["+i+"]="+getSlotFont(i)+ls;        }        return "** Composite Font: Family=" + familyName +            " Name=" + fullName + " style=" + style + ls + componentsStr;    }}