org.docx4j.fonts.fop.fonts.FontInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of docx4j Show documentation
Show all versions of docx4j Show documentation
docx4j is a library which helps you to work with the Office Open
XML file format as used in docx
documents, pptx presentations, and xlsx spreadsheets.
The newest version!
/* NOTICE: This file has been changed by Plutext Pty Ltd for use in docx4j.
* The package name has been changed; there may also be other changes.
*
* This notice is included to meet the condition in clause 4(b) of the License.
*/
/*
* 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: FontInfo.java 721430 2008-11-28 11:13:12Z acumiskey $ */
package org.docx4j.fonts.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 {
/** logging instance */
protected static Logger log = LoggerFactory.getLogger(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 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 (int i = 0; i < families.length; i++) {
addFontProperties(name, families[i], 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));
}
/**
* Logger 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 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 != 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 (internalFontKey == null) {
key = createFontKey(family, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL);
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/*>*/ 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 fontKey = getInternalFontKey(triplet);
useFont(fontKey);
FontMetrics metrics = getMetricsFor(fontKey);
font = new Font(fontKey, triplet, metrics, fontSize);
sizes.put(size, font);
}
return font;
}
private List/**/ getTripletsForName(String fontName) {
List/**/ matchedTriplets = new java.util.ArrayList/**/();
Iterator it = triplets.keySet().iterator();
while (it.hasNext()) {
FontTriplet triplet = (FontTriplet)it.next();
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/**/ triplets = getTripletsForName(awtFontName);
if (!triplets.isEmpty()) {
Iterator it = triplets.iterator();
while (it.hasNext()) {
FontTriplet triplet = (FontTriplet)it.next();
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.
*
* 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/**/ fontLookup(String[] families, String style,
int weight, boolean substitutable) {
List/**/ matchingTriplets = new java.util.ArrayList/**/();
FontTriplet triplet = null;
for (int i = 0; i < families.length; i++) {
triplet = fontLookup(families[i], style, weight, substitutable);
if (triplet != null) {
matchingTriplets.add(triplet);
}
}
return matchingTriplets;
}
/**
* 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");
}
// try matching without substitutions
List/**/ 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.toString());
}
FontTriplet[] fontTriplets = new FontTriplet[matchedTriplets.size()];
matchedTriplets.toArray(fontTriplets);
// found some matching fonts so return them
return fontTriplets;
}
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((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();
}
}