/* |
|
* 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; |
|
} |
|
/* 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 = (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; |
|
} |
|
} |