/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id$ */ package org.apache.fop.fonts; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * The FontInfo holds font information for the layout and rendering of a fo document. * This stores the list of available fonts that are setup by * the renderer. The font name can be retrieved for the * family style and weight. *
* Currently font supported font-variant small-caps is not * implemented. */ public class FontInfo { private static final FontTriplet[] TRIPLETS_TYPE = new FontTriplet[1]; /** logging instance */ protected static Log log = LogFactory.getLog(FontInfo.class); /** Map containing fonts that have been used */ private Map/**/ usedFonts = null; //(String = font key) /** look up a font-triplet to find a font-name */ private Map/**/ triplets = null; //(String = font key) /** look up a font-triplet to find its priority * (only used inside addFontProperties()) */ private Map/**/ tripletPriorities = null; //Map /** look up a font-name to get a font (that implements FontMetrics at least) */ private Map/**/ fonts = null; //(String = font key) /** * a collection of missing fonts; used to make sure the user gets * a warning for a missing font only once (not every time the font is used) */ private Set/**/ loggedFontKeys = null; /** Cache for Font instances. */ private Map/**/ fontInstanceCache = null; /** Event listener for font events */ private FontEventListener eventListener = null; /** * Main constructor */ public FontInfo() { this.triplets = new java.util.HashMap/**/(); this.tripletPriorities = new java.util.HashMap/**/(); this.fonts = new java.util.HashMap/**/(); this.usedFonts = new java.util.HashMap/**/(); } /** * Sets the font event listener that can be used to receive events about particular events * in this class. * @param listener the font event listener */ public void setEventListener(FontEventListener listener) { this.eventListener = listener; } /** * Checks if the font setup is valid (At least the ultimate fallback font * must be registered.) * @return True if valid */ public boolean isSetupValid() { //We're only called when font setup is done: tripletPriorities = null; // candidate for garbage collection return triplets.containsKey(Font.DEFAULT_FONT); } /** * Adds a new font triplet. * @param name internal key * @param family font family name * @param style font style (normal, italic, oblique...) * @param weight font weight */ public void addFontProperties(String name, String family, String style, int weight) { addFontProperties(name, createFontKey(family, style, weight)); } /** * Adds a new font triplet. * @param internalFontKey internal font key * @param triplet the font triplet to associate with the internal key */ public void addFontProperties(String internalFontKey, FontTriplet triplet) { /* * add the given family, style and weight as a lookup for the font * with the given name */ if (log.isDebugEnabled()) { log.debug("Registering: " + triplet + " under " + internalFontKey); } String oldName = (String)triplets.get(triplet); int newPriority = triplet.getPriority(); if (oldName != null) { int oldPriority = ((Integer)tripletPriorities.get(triplet)).intValue(); if (oldPriority < newPriority) { logDuplicateFont(triplet, false, oldName, oldPriority, internalFontKey, newPriority); return; } else { logDuplicateFont(triplet, true, oldName, oldPriority, internalFontKey, newPriority); } } this.triplets.put(triplet, internalFontKey); this.tripletPriorities.put(triplet, new Integer(newPriority)); } /** * Log warning about duplicate font triplets. * * @param triplet the duplicate font triplet * @param replacing true iff the new font will replace the old one * @param oldKey the old internal font name * @param oldPriority the priority of the existing font mapping * @param newKey the new internal font name * @param newPriority the priority of the duplicate font mapping */ private void logDuplicateFont(FontTriplet triplet, boolean replacing, String oldKey, int oldPriority, String newKey, int newPriority) { if (log.isDebugEnabled()) { log.debug(triplet + (replacing ? ": Replacing " : ": Not replacing ") + ((FontMetrics)fonts.get(triplets.get(triplet))).getFullName() + " (priority=" + oldPriority + ") by " + ((FontMetrics)fonts.get(newKey)).getFullName() + " (priority=" + newPriority + ")"); } } /** * Adds font metrics for a specific font. * @param internalFontKey internal key * @param metrics metrics to register */ public void addMetrics(String internalFontKey, FontMetrics metrics) { // add the given metrics as a font with the given name if (metrics instanceof Typeface) { ((Typeface)metrics).setEventListener(this.eventListener); } this.fonts.put(internalFontKey, metrics); } /** * Lookup a font. *
* Locate the font name for a given family, style and weight. * The font name can then be used as a key as it is unique for * the associated document. * This also adds the font to the list of used fonts. * @param family font family * @param style font style * @param weight font weight * @param substFont true if the font may be substituted with the * default font if not found * @return internal font triplet key */ private FontTriplet fontLookup(String family, String style, int weight, boolean substFont) { if (log.isTraceEnabled()) { log.trace("Font lookup: " + family + " " + style + " " + weight); } FontTriplet startKey = createFontKey(family, style, weight); FontTriplet fontTriplet = startKey; // first try given parameters String internalFontKey = getInternalFontKey(fontTriplet); if (internalFontKey == null) { fontTriplet = fuzzyFontLookup(family, style, weight, startKey, substFont); } if (fontTriplet != null) { if (fontTriplet != startKey) { notifyFontReplacement(startKey, fontTriplet); } return fontTriplet; } else { return null; } } private FontTriplet fuzzyFontLookup(String family, String style, int weight, FontTriplet startKey, boolean substFont) { FontTriplet key; String internalFontKey; if (!family.equals(startKey.getName())) { key = createFontKey(family, style, weight); internalFontKey = getInternalFontKey(key); if (internalFontKey != null) { return key; } } // adjust weight, favouring normal or bold key = findAdjustWeight(family, style, weight); internalFontKey = getInternalFontKey(key); if (!substFont && internalFontKey == null) { return null; } // only if the font may be substituted // fallback 1: try the same font-family and weight with default style if (internalFontKey == null && style != Font.STYLE_NORMAL) { key = createFontKey(family, Font.STYLE_NORMAL, weight); internalFontKey = getInternalFontKey(key); } if (internalFontKey == null && weight != Font.WEIGHT_NORMAL) { int diffWeight = (Font.WEIGHT_NORMAL - weight) / 100; int direction = diffWeight > 0 ? 1 : -1; int tryWeight = weight; while (tryWeight != Font.WEIGHT_NORMAL) { tryWeight += 100 * direction; key = createFontKey(family, style, weight); internalFontKey = getInternalFontKey(key); if (internalFontKey == null) { key = createFontKey(family, Font.STYLE_NORMAL, weight); internalFontKey = getInternalFontKey(key); } if (internalFontKey != null) { break; } } } // fallback 2: try the same font-family with default style and weight /* obsolete: replaced by the loop above if (f == null) { key = createFontKey(family, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL); f = getInternalFontKey(key); }*/ // fallback 3: try any family with orig style/weight if (internalFontKey == null) { return fuzzyFontLookup("any", style, weight, startKey, false); } // last resort: use default if (internalFontKey == null) { key = Font.DEFAULT_FONT; internalFontKey = getInternalFontKey(key); } if (internalFontKey != null) { return key; } else { return null; } } /** * Tells this class that the font with the given internal name has been used. * @param internalName the internal font name (F1, F2 etc.) */ public void useFont(String internalName) { usedFonts.put(internalName, fonts.get(internalName)); } private Map/*>*/ getFontInstanceCache() { if (fontInstanceCache == null) { fontInstanceCache = new java.util.HashMap/*>*/(); } return fontInstanceCache; } /** * Retrieves a (possibly cached) Font instance based on a FontTriplet and a font size. * @param triplet the font triplet designating the requested font * @param fontSize the font size * @return the requested Font instance */ public Font getFontInstance(FontTriplet triplet, int fontSize) { Map/**/ sizes = (Map/**/)getFontInstanceCache().get(triplet); if (sizes == null) { sizes = new java.util.HashMap/**/(); getFontInstanceCache().put(triplet, sizes); } Integer size = new Integer(fontSize); Font font = (Font)sizes.get(size); if (font == null) { String fname = getInternalFontKey(triplet); useFont(fname); FontMetrics metrics = getMetricsFor(fname); font = new Font(fname, triplet, metrics, fontSize); sizes.put(size, font); } return font; } /** * Lookup a font. *
* Locate the font name for a given family, style and weight. * The font name can then be used as a key as it is unique for * the associated document. * This also adds the font to the list of used fonts. * @param family font family * @param style font style * @param weight font weight * @return the font triplet of the font chosen */ public FontTriplet fontLookup(String family, String style, int weight) { return fontLookup(family, style, weight, true); } /** * Looks up a set of fonts. *
* Locate the font name(s) for the given families, style and weight. * The font name(s) can then be used as a key as they are unique for * the associated document. * This also adds the fonts to the list of used fonts. * @param families font families (priority list) * @param style font style * @param weight font weight * @return the set of font triplets of all supported and chosen font-families * in the specified style and weight. */ public FontTriplet[] fontLookup(String[] families, String style, int weight) { if (families.length == 0) { throw new IllegalArgumentException("Specify at least one font family"); } FontTriplet triplet; List tmpTriplets = new java.util.ArrayList(); for (int i = 0, c = families.length; i < c; i++) { triplet = fontLookup(families[i], style, weight, (i >= families.length - 1)); if (triplet != null) { tmpTriplets.add(triplet); } } if (tmpTriplets.size() != 0) { return (FontTriplet[]) tmpTriplets.toArray(TRIPLETS_TYPE); } StringBuffer sb = new StringBuffer(); for (int i = 0, c = families.length; i < c; i++) { if (i > 0) { sb.append(", "); } sb.append(families[i]); } throw new IllegalStateException( "fontLookup must return an array with at least one " + "FontTriplet on the last call. Lookup: " + sb.toString()); } private Set/**/ getLoggedFontKeys() { if (loggedFontKeys == null) { loggedFontKeys = new java.util.HashSet/**/(); } return loggedFontKeys; } private void notifyFontReplacement(FontTriplet replacedKey, FontTriplet newKey) { if (!getLoggedFontKeys().contains(replacedKey)) { getLoggedFontKeys().add(replacedKey); if (this.eventListener != null) { this.eventListener.fontSubstituted(this, replacedKey, newKey); } else { log.warn("Font '" + replacedKey + "' not found. " + "Substituting with '" + newKey + "'."); } } } /** * Find a font with a given family and style by trying * different font weights according to the spec. * @param family font family * @param style font style * @param weight font weight * @return internal key */ public FontTriplet findAdjustWeight(String family, String style, int weight) { FontTriplet key = null; String f = null; int newWeight = weight; if (newWeight < 400) { while (f == null && newWeight > 100) { newWeight -= 100; key = createFontKey(family, style, newWeight); f = getInternalFontKey(key); } newWeight = weight; while (f == null && newWeight < 400) { newWeight += 100; key = createFontKey(family, style, newWeight); f = getInternalFontKey(key); } } else if (newWeight == 500) { key = createFontKey(family, style, 400); f = getInternalFontKey(key); } else if (newWeight > 500) { while (f == null && newWeight < 1000) { newWeight += 100; key = createFontKey(family, style, newWeight); f = getInternalFontKey(key); } newWeight = weight; while (f == null && newWeight > 400) { newWeight -= 100; key = createFontKey(family, style, newWeight); f = getInternalFontKey(key); } } if (f == null && weight != 400) { key = createFontKey(family, style, 400); f = getInternalFontKey(key); } if (f != null) { return key; } else { return null; } } /** * Determines if a particular font is available. * @param family font family * @param style font style * @param weight font weight * @return True if available */ public boolean hasFont(String family, String style, int weight) { FontTriplet key = createFontKey(family, style, weight); return this.triplets.containsKey(key); } /** * Returns the internal font key (F1, F2, F3 etc.) for a given triplet. * @param triplet the font triplet * @return the associated internal key or null, if not found */ public String getInternalFontKey(FontTriplet triplet) { return (String)triplets.get(triplet); } /** * Creates a key from the given strings. * @param family font family * @param style font style * @param weight font weight * @return internal key */ public static FontTriplet createFontKey(String family, String style, int weight) { return new FontTriplet(family, style, weight); } /** * Gets a Map of all registered fonts. * @return a read-only Map with font key/FontMetrics pairs */ public Map/**/ getFonts() { return java.util.Collections.unmodifiableMap(this.fonts); } /** * Gets a Map of all registered font triplets. * @return a Map with FontTriplet/font key pairs */ public Map/**/ getFontTriplets() { return this.triplets; } /** * This is used by the renderers to retrieve all the * fonts used in the document. * This is for embedded font or creating a list of used fonts. * @return a read-only Map with font key/FontMetrics pairs */ public Map/**/ getUsedFonts() { return this.usedFonts; } /** * Returns the FontMetrics for a particular font * @param fontName internal key * @return font metrics */ public FontMetrics getMetricsFor(String fontName) { FontMetrics metrics = (FontMetrics)fonts.get(fontName); usedFonts.put(fontName, metrics); return metrics; } /** * Returns all font triplet matching the given font name. * @param fontName The font name we are looking for * @return A list of matching font triplets */ public List/**/ getTripletsFor(String fontName) { List/**/ foundTriplets = new java.util.ArrayList(); for (Iterator iter = triplets.entrySet().iterator(); iter.hasNext();) { Map.Entry tripletEntry = (Map.Entry) iter.next(); if (fontName.equals(((String)tripletEntry.getValue()))) { foundTriplets.add(tripletEntry.getKey()); } } return foundTriplets; } /** * Returns the first triplet matching the given font name. * As there may be multiple triplets matching the font name * the result set is sorted first to guarantee consistent results. * @param fontName The font name we are looking for * @return The first triplet for the given font name */ public FontTriplet getTripletFor(String fontName) { List/**/ foundTriplets = getTripletsFor(fontName); if (foundTriplets.size() > 0) { Collections.sort(foundTriplets); return (FontTriplet)foundTriplets.get(0); } return null; } /** * Returns the font style for a particular font. * There may be multiple font styles matching this font. Only the first * found is returned. Searching is done on a sorted list to guarantee consistent * results. * @param fontName internal key * @return font style */ public String getFontStyleFor(String fontName) { FontTriplet triplet = getTripletFor(fontName); if (triplet != null) { return triplet.getStyle(); } else { return ""; } } /** * Returns the font weight for a particular font. * There may be multiple font weights matching this font. Only the first * found is returned. Searching is done on a sorted list to guarantee consistent * results. * @param fontName internal key * @return font weight */ public int getFontWeightFor(String fontName) { FontTriplet triplet = getTripletFor(fontName); if (triplet != null) { return triplet.getWeight(); } else { return 0; } } /** * Diagnostic method for logging all registered fonts to System.out. */ public void dumpAllTripletsToSystemOut() { System.out.print(toString()); } /** * {@inheritDoc} */ public String toString() { Collection entries = new java.util.TreeSet(); Iterator iter = this.triplets.keySet().iterator(); while (iter.hasNext()) { FontTriplet triplet = (FontTriplet)iter.next(); String key = getInternalFontKey(triplet); FontMetrics metrics = getMetricsFor(key); entries.add(triplet.toString() + " -> " + key + " -> " + metrics.getFontName() + "\n"); } StringBuffer stringBuffer = new StringBuffer(); for (iter = entries.iterator(); iter.hasNext();) { stringBuffer.append(iter.next()); } return stringBuffer.toString(); } }