| /* | |
| * Copyright (c) 2003, 2014, 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; | |
| } | |
| /* We will limit the number of slots to 254. | |
| * We store the slot for a glyph id in a byte and we may use one slot | |
| * for an EUDC font, and we may also create a composite | |
| * using this composite as a backup for a physical font. | |
| * So we want to leave space for the two additional slots. | |
| */ | |
| numSlots = (numSlots <= 254) ? numSlots : 254; | |
| /* 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) { | |
| int msCnt = numMetricsSlots; | |
| int fbCnt = numSlots - msCnt; | |
| numSlots++; | |
| if (componentNames != null) { | |
| componentNames = new String[numSlots]; | |
| System.arraycopy(compNames, 0, componentNames, 0, msCnt); | |
| componentNames[msCnt] = fm.getEUDCFont().getFontName(null); | |
| System.arraycopy(compNames, msCnt, | |
| componentNames, msCnt+1, fbCnt); | |
| } | |
| if (componentFileNames != null) { | |
| componentFileNames = new String[numSlots]; | |
| System.arraycopy(compFileNames, 0, | |
| componentFileNames, 0, msCnt); | |
| System.arraycopy(compFileNames, msCnt, | |
| componentFileNames, msCnt+1, fbCnt); | |
| } | |
| components = new PhysicalFont[numSlots]; | |
| components[msCnt] = 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; | |
| } | |
| } | |
| /* | |
| * Build a composite from a set of individual slot fonts. | |
| */ | |
| CompositeFont(PhysicalFont[] slotFonts) { | |
| isStdComposite = false; | |
| handle = new Font2DHandle(this); | |
| fullName = slotFonts[0].fullName; | |
| familyName = slotFonts[0].familyName; | |
| style = slotFonts[0].style; | |
| numMetricsSlots = 1; /* Only the physical Font */ | |
| numSlots = slotFonts.length; | |
| components = new PhysicalFont[numSlots]; | |
| System.arraycopy(slotFonts, 0, components, 0, numSlots); | |
| deferredInitialisation = new boolean[numSlots]; // all false. | |
| } | |
| /* 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)) { | |
| /* If a component specifies the file with a bad font, | |
| * the corresponding slot will be initialized by | |
| * default physical font. In such case findFont2D may | |
| * return composite font which cannot be casted to | |
| * physical font. | |
| */ | |
| try { | |
| components[slot] = | |
| (PhysicalFont) fm.findFont2D(componentNames[slot], | |
| style, | |
| FontManager.PHYSICAL_FALLBACK); | |
| } catch (ClassCastException cce) { | |
| /* Assign default physical font to the slot */ | |
| components[slot] = fm.getDefaultPhysicalFont(); | |
| } | |
| } | |
| } | |
| 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 = System.lineSeparator(); | |
| 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; | |
| } | |
| } |