blob: 218734f1575bcac705bfaaa6ad6f5513ddb249df [file] [log] [blame]
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* The FontInfo 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.
* <br>
* 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; //Map<String,FontMetrics> (String = font key)
/** look up a font-triplet to find a font-name */
private Map triplets; //Map<FontTriplet,String> (String = font key)
/** look up a font-triplet to find its priority
* (only used inside addFontProperties()) */
private Map tripletPriorities; //Map<FontTriplet,Integer>
/** look up a font-name to get a font (that implements FontMetrics at least) */
private Map fonts; //Map<String,FontMetrics> (String = font key)
/** 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 Collection loggedFontKeys;
/** Cache for Font instances. */
private Map fontInstanceCache = new java.util.HashMap();
/**
* 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();
}
/**
* 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 name internal key
* @param triplet the font triplet to associate with the internal key
*/
public void addFontProperties(String name, 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 " + name);
}
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,
name, newPriority);
return;
} else {
logDuplicateFont(triplet, true, oldName, oldPriority,
name, newPriority);
}
}
this.triplets.put(triplet, name);
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()
+ " (" + oldPriority + ") by "
+ ((FontMetrics)fonts.get(newKey)).getFullName()
+ " (" + newPriority + ")");
}
}
/**
* Adds font metrics for a specific font.
* @param name internal key
* @param metrics metrics to register
*/
public void addMetrics(String name, FontMetrics metrics) {
// add the given metrics as a font with the given name
this.fonts.put(name, metrics);
}
/**
* Lookup a font.
* <br>
* 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 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 key = startKey;
// first try given parameters
String f = getInternalFontKey(key);
if (f == null) {
key = doAdjustedLookup(family, style, weight, startKey, substFont);
}
if (key != null) {
if (key != startKey) {
notifyFontReplacement(startKey, key);
}
return key;
} else {
return null;
}
}
private FontTriplet doAdjustedLookup(String family, String style,
int weight, FontTriplet startKey, boolean substFont) {
FontTriplet key;
String f;
if (!family.equals(startKey.getName())) {
key = createFontKey(family, style, weight);
f = getInternalFontKey(key);
if (f != null) {
return key;
}
}
// adjust weight, favouring normal or bold
key = findAdjustWeight(family, style, weight);
f = getInternalFontKey(key);
if (!substFont && f == null) {
return null;
}
// only if the font may be substituted
// fallback 1: try the same font-family and weight with default style
if (f == null && style != Font.STYLE_NORMAL) {
key = createFontKey(family, Font.STYLE_NORMAL, weight);
f = getInternalFontKey(key);
}
if (f == 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);
f = getInternalFontKey(key);
if (f == null) {
key = createFontKey(family, Font.STYLE_NORMAL, weight);
f = getInternalFontKey(key);
}
if (f != 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 (f == null) {
return doAdjustedLookup("any", style, weight, startKey, false);
}
// last resort: use default
if (f == null) {
key = Font.DEFAULT_FONT;
f = getInternalFontKey(key);
}
if (f != 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));
}
/**
* 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)fontInstanceCache.get(triplet);
if (sizes == null) {
sizes = new java.util.HashMap();
fontInstanceCache.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.
* <br>
* 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.
* <br>
* 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 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 void notifyFontReplacement(FontTriplet replacedKey, FontTriplet newKey) {
if (loggedFontKeys == null) {
loggedFontKeys = new java.util.HashSet();
}
if (!loggedFontKeys.contains(replacedKey)) {
loggedFontKeys.add(replacedKey);
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 read-only Map with FontTriplet/font key pairs
*/
public Map getFontTriplets() {
return java.util.Collections.unmodifiableMap(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 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 = new 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());
}
}
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() {
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());
}
iter = entries.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}