blob: 7e44d56aea9d1a48c3a4cdc5da1e55c26a424bb5 [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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
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.
* <br>
* Currently font supported font-variant small-caps is not
* implemented.
*/
public class FontInfo {
/** logging instance */
protected static final Log log = LogFactory.getLog(FontInfo.class);
/** Map containing fonts that have been used */
private Map<String, Typeface> usedFonts; //(String = font key)
/** look up a font-triplet to find a font-name */
private Map<FontTriplet, String> triplets; //(String = font key)
/** look up a font-triplet to find its priority
* (only used inside addFontProperties()) */
private Map<FontTriplet, Integer> tripletPriorities; //Map<FontTriplet,Integer>
/** look up a font-name to get a font (that implements FontMetrics at least) */
private Map<String, Typeface> fonts; //(String = font key)
/** Cache for Font instances. */
private Map<FontTriplet, Map<Integer, Font>> fontInstanceCache;
/** Event listener for font events */
private FontEventListener eventListener;
/**
* Main constructor
*/
public FontInfo() {
this.triplets = new HashMap<FontTriplet, String>();
this.tripletPriorities = new HashMap<FontTriplet, Integer>();
this.fonts = new HashMap<String, Typeface>();
this.usedFonts = new HashMap<String, Typeface>();
}
/**
* 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 series of new font triplets given an array of font family names.
* @param name internal key
* @param families an array of font family names
* @param style font style (normal, italic, oblique...)
* @param weight font weight
*/
public void addFontProperties(String name, String[] families, String style, int weight) {
for (String family : families) {
addFontProperties(name, 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 = triplets.get(triplet);
int newPriority = triplet.getPriority();
if (oldName != null) {
int oldPriority = tripletPriorities.get(triplet);
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, 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 ")
+ fonts.get(triplets.get(triplet)).getFullName()
+ " (priority=" + oldPriority + ") by "
+ 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, (Typeface)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 substitutable 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 substitutable) {
if (log.isTraceEnabled()) {
log.trace("Font lookup: " + family + " " + style + " " + weight
+ (substitutable ? " substitutable" : ""));
}
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, substitutable);
}
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 substitutable) {
FontTriplet key;
String internalFontKey = null;
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);
if (key != null) {
internalFontKey = getInternalFontKey(key);
}
// return null if not found and not substitutable
if (!substitutable && 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.equals(Font.STYLE_NORMAL)) {
key = createFontKey(family, Font.STYLE_NORMAL, weight);
internalFontKey = getInternalFontKey(key);
}
// fallback 2: try the same font-family with default style and try to adjust weight
if (internalFontKey == null && !style.equals(Font.STYLE_NORMAL)) {
key = findAdjustWeight(family, Font.STYLE_NORMAL, weight);
if (key != null) {
internalFontKey = getInternalFontKey(key);
}
}
// fallback 3: try any family with original style/weight
if (internalFontKey == null) {
return fuzzyFontLookup("any", style, weight, startKey, false);
}
// last resort: use default
if (key == null && 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<FontTriplet, Map<Integer, Font>> getFontInstanceCache() {
if (fontInstanceCache == null) {
fontInstanceCache = new HashMap<FontTriplet, Map<Integer, Font>>();
}
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<Integer, Font> sizes = getFontInstanceCache().get(triplet);
if (sizes == null) {
sizes = new HashMap<Integer, Font>();
getFontInstanceCache().put(triplet, sizes);
}
Integer size = fontSize;
Font font = sizes.get(size);
if (font == null) {
String fontKey = getInternalFontKey(triplet);
useFont(fontKey);
FontMetrics metrics = getMetricsFor(fontKey);
font = new Font(fontKey, triplet, metrics, fontSize);
sizes.put(size, font);
}
return font;
}
private List<FontTriplet> getTripletsForName(String fontName) {
List<FontTriplet> matchedTriplets = new ArrayList<FontTriplet>();
for (FontTriplet triplet : triplets.keySet()) {
String tripletName = triplet.getName();
if (tripletName.toLowerCase().equals(fontName.toLowerCase())) {
matchedTriplets.add(triplet);
}
}
return matchedTriplets;
}
/**
* Returns a suitable internal font given an AWT Font instance.
*
* @param awtFont the AWT font
* @return a best matching internal Font
*/
public Font getFontInstanceForAWTFont(java.awt.Font awtFont) {
String awtFontName = awtFont.getName();
String awtFontFamily = awtFont.getFamily();
String awtFontStyle = awtFont.isItalic() ? Font.STYLE_ITALIC : Font.STYLE_NORMAL;
int awtFontWeight = awtFont.isBold() ? Font.WEIGHT_BOLD : Font.WEIGHT_NORMAL;
FontTriplet matchedTriplet = null;
List<FontTriplet> triplets = getTripletsForName(awtFontName);
if (!triplets.isEmpty()) {
for (FontTriplet triplet : triplets) {
boolean styleMatched = triplet.getStyle().equals(awtFontStyle);
boolean weightMatched = triplet.getWeight() == awtFontWeight;
if (styleMatched && weightMatched) {
matchedTriplet = triplet;
break;
}
}
}
// not matched on font name so do a lookup using family
if (matchedTriplet == null) {
if (awtFontFamily.equals("sanserif")) {
awtFontFamily = "sans-serif";
}
matchedTriplet = fontLookup(awtFontFamily, awtFontStyle, awtFontWeight);
}
int fontSize = Math.round(awtFont.getSize2D() * 1000);
return getFontInstance(matchedTriplet, fontSize);
}
/**
* 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);
}
private List<FontTriplet> fontLookup(String[] families, String style, int weight, boolean substitutable) {
List<FontTriplet> matchingTriplets = new ArrayList<FontTriplet>();
FontTriplet triplet = null;
for (String family : families) {
triplet = fontLookup(family, style, weight, substitutable);
if (triplet != null) {
matchingTriplets.add(triplet);
}
}
return matchingTriplets;
}
/**
* 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");
}
// try matching without substitutions
List<FontTriplet> matchedTriplets = fontLookup(families, style, weight, false);
// if there are no matching font triplets found try with substitutions
if (matchedTriplets.size() == 0) {
matchedTriplets = fontLookup(families, style, weight, true);
}
// no matching font triplets found!
if (matchedTriplets.size() == 0) {
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);
}
FontTriplet[] fontTriplets = new FontTriplet[matchedTriplets.size()];
matchedTriplets.toArray(fontTriplets);
// found some matching fonts so return them
return fontTriplets;
}
private void notifyFontReplacement(FontTriplet replacedKey, FontTriplet newKey) {
if (this.eventListener != null) {
this.eventListener.fontSubstituted(this, replacedKey, newKey);
}
}
/**
* Notify listeners that the SVG text for the given font will be stroked as shapes.
* @param fontFamily a SVG font family
*/
public void notifyStrokingSVGTextAsShapes(String fontFamily) {
if (this.eventListener != null) {
this.eventListener.svgTextStrokedAsShapes(this, fontFamily);
}
}
/**
* 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 == 400 || 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 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<String, Typeface> getFonts() {
return Collections.unmodifiableMap(this.fonts);
}
/**
* Gets a Map of all registered font triplets.
* @return a Map with FontTriplet/font key pairs
*/
public Map<FontTriplet, String> 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<String, Typeface> getUsedFonts() {
return this.usedFonts;
}
/**
* Returns the FontMetrics for a particular font
* @param fontName internal key
* @return font metrics
*/
public FontMetrics getMetricsFor(String fontName) {
Typeface metrics = 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<FontTriplet> getTripletsFor(String fontName) {
List<FontTriplet> foundTriplets = new ArrayList<FontTriplet>();
for (Map.Entry<FontTriplet, String> tripletEntry : triplets.entrySet()) {
if (fontName.equals((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<FontTriplet> foundTriplets = getTripletsFor(fontName);
if (foundTriplets.size() > 0) {
Collections.sort(foundTriplets);
return 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() {
SortedSet<String> entries = new TreeSet<String>();
for (FontTriplet triplet : this.triplets.keySet()) {
String key = getInternalFontKey(triplet);
FontMetrics metrics = getMetricsFor(key);
entries.add(triplet.toString() + " -> " + key + " -> " + metrics.getFontName() + "\n");
}
StringBuffer stringBuffer = new StringBuffer();
for (String str : entries) {
stringBuffer.append(str);
}
System.out.println(stringBuffer.toString());
}
}