| /* |
| * 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 (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 = triplets.get(triplet); |
| int newPriority = triplet.getPriority(); |
| if (oldName != null) { |
| int oldPriority = 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, Integer.valueOf(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 = Integer.valueOf(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 (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. |
| * <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.toString()); |
| |
| } |
| 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() { |
| System.out.print(toString()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String toString() { |
| 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); |
| } |
| return stringBuffer.toString(); |
| } |
| } |