blob: e0d52ad43956d759312b1bce56409c60d28e16ef [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.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.apps.FOPException;
import org.apache.fop.fonts.autodetect.FontFileFinder;
import org.apache.fop.fonts.autodetect.FontInfoFinder;
import org.apache.fop.util.LogUtil;
/**
* An abstract FontInfo configurator
*/
public class FontInfoConfigurator {
/** logger instance */
protected static Log log = LogFactory.getLog(FontInfoConfigurator.class);
private Configuration cfg;
private FontManager fontManager;
private FontResolver fontResolver;
private FontEventListener listener;
private boolean strict;
/**
* Main constructor
* @param cfg the configuration object
* @param fontManager the font manager
* @param fontResolver the font resolver
* @param listener the font event listener
* @param strict true if an Exception should be thrown if an error is found.
*/
public FontInfoConfigurator(Configuration cfg, FontManager fontManager,
FontResolver fontResolver, FontEventListener listener, boolean strict) {
this.cfg = cfg;
this.fontManager = fontManager;
this.fontResolver = fontResolver;
this.listener = listener;
this.strict = strict;
}
/**
* Initializes font info settings from the user configuration
* @param fontInfoList a font info list
* @throws FOPException if an exception occurs while processing the configuration
*/
public void configure(List/*<EmbedFontInfo>*/ fontInfoList) throws FOPException {
Configuration fonts = cfg.getChild("fonts", false);
if (fonts != null) {
long start = 0;
if (log.isDebugEnabled()) {
log.debug("Starting font configuration...");
start = System.currentTimeMillis();
}
FontAdder fontAdder = new FontAdder(fontManager, fontResolver, listener);
// native o/s search (autodetect) configuration
boolean autodetectFonts = (fonts.getChild("auto-detect", false) != null);
if (autodetectFonts) {
FontDetector fontDetector = new FontDetector(fontManager, fontAdder, strict);
fontDetector.detect(fontInfoList);
}
// Add configured directories to FontInfo
addDirectories(fonts, fontAdder, fontInfoList);
// Add configured fonts to FontInfo
FontCache fontCache = fontManager.getFontCache();
addFonts(fonts, fontCache, fontInfoList);
// Update referenced fonts (fonts which are not to be embedded)
fontManager.updateReferencedFonts(fontInfoList);
// Renderer-specific referenced fonts
Configuration referencedFontsCfg = fonts.getChild("referenced-fonts", false);
if (referencedFontsCfg != null) {
FontTriplet.Matcher matcher = FontManagerConfigurator.createFontsMatcher(
referencedFontsCfg, strict);
fontManager.updateReferencedFonts(fontInfoList, matcher);
}
// Update font cache if it has changed
if (fontCache != null && fontCache.hasChanged()) {
fontCache.save();
}
if (log.isDebugEnabled()) {
log.debug("Finished font configuration in "
+ (System.currentTimeMillis() - start) + "ms");
}
}
}
private void addDirectories(Configuration fontsCfg,
FontAdder fontAdder, List/*<URL>*/ fontInfoList) throws FOPException {
// directory (multiple font) configuration
Configuration[] directories = fontsCfg.getChildren("directory");
for (int i = 0; i < directories.length; i++) {
boolean recursive = directories[i].getAttributeAsBoolean("recursive", false);
String directory = null;
try {
directory = directories[i].getValue();
} catch (ConfigurationException e) {
LogUtil.handleException(log, e, strict);
continue;
}
if (directory == null) {
LogUtil.handleException(log,
new FOPException("directory defined without value"), strict);
continue;
}
// add fonts found in directory
FontFileFinder fontFileFinder = new FontFileFinder(recursive ? -1 : 1);
List/*<URL>*/ fontURLList;
try {
fontURLList = fontFileFinder.find(directory);
fontAdder.add(fontURLList, fontInfoList);
} catch (IOException e) {
LogUtil.handleException(log, e, strict);
}
}
}
/**
* Populates the font info list from the fonts configuration
* @param fontsCfg a fonts configuration
* @param fontCache a font cache
* @param fontInfoList a font info list
* @throws FOPException if an exception occurs while processing the configuration
*/
protected void addFonts(Configuration fontsCfg, FontCache fontCache,
List/*<EmbedFontInfo>*/ fontInfoList) throws FOPException {
// font file (singular) configuration
Configuration[] font = fontsCfg.getChildren("font");
for (int i = 0; i < font.length; i++) {
EmbedFontInfo embedFontInfo = getFontInfo(
font[i], fontCache);
if (embedFontInfo != null) {
fontInfoList.add(embedFontInfo);
}
}
}
private static void closeSource(Source src) {
if (src instanceof StreamSource) {
StreamSource streamSource = (StreamSource)src;
IOUtils.closeQuietly(streamSource.getInputStream());
IOUtils.closeQuietly(streamSource.getReader());
}
}
/**
* Returns a font info from a font node Configuration definition
*
* @param fontCfg Configuration object (font node)
* @param fontCache the font cache (or null if it is disabled)
* @return the embedded font info
* @throws FOPException if something's wrong with the config data
*/
protected EmbedFontInfo getFontInfo(
Configuration fontCfg, FontCache fontCache)
throws FOPException {
String metricsUrl = fontCfg.getAttribute("metrics-url", null);
String embedUrl = fontCfg.getAttribute("embed-url", null);
String subFont = fontCfg.getAttribute("sub-font", null);
if (metricsUrl == null && embedUrl == null) {
LogUtil.handleError(log,
"Font configuration without metric-url or embed-url attribute",
strict);
return null;
}
if (strict) {
//This section just checks early whether the URIs can be resolved
//Stream are immediately closed again since they will never be used anyway
if (embedUrl != null) {
Source source = fontResolver.resolve(embedUrl);
closeSource(source);
if (source == null) {
LogUtil.handleError(log,
"Failed to resolve font with embed-url '" + embedUrl + "'", strict);
return null;
}
}
if (metricsUrl != null) {
Source source = fontResolver.resolve(metricsUrl);
closeSource(source);
if (source == null) {
LogUtil.handleError(log,
"Failed to resolve font with metric-url '" + metricsUrl + "'", strict);
return null;
}
}
}
Configuration[] tripletCfg = fontCfg.getChildren("font-triplet");
// no font triplet info
if (tripletCfg.length == 0) {
LogUtil.handleError(log, "font without font-triplet", strict);
File fontFile = FontCache.getFileFromUrls(new String[] {embedUrl, metricsUrl});
URL fontUrl;
try {
fontUrl = fontFile.toURI().toURL();
} catch (MalformedURLException e) {
// Should never happen
log.debug("Malformed Url: " + e.getMessage());
return null;
}
if (fontFile != null) {
FontInfoFinder finder = new FontInfoFinder();
finder.setEventListener(listener);
EmbedFontInfo[] infos = finder.find(fontUrl, fontResolver, fontCache);
return infos[0]; //When subFont is set, only one font is returned
} else {
return null;
}
}
List/*<FontTriplet>*/ tripletList = new java.util.ArrayList/*<FontTriplet>*/();
for (int j = 0; j < tripletCfg.length; j++) {
FontTriplet fontTriplet = getFontTriplet(tripletCfg[j]);
tripletList.add(fontTriplet);
}
boolean useKerning = fontCfg.getAttributeAsBoolean("kerning", true);
EncodingMode encodingMode = EncodingMode.valueOf(
fontCfg.getAttribute("encoding-mode", EncodingMode.AUTO.getName()));
EmbedFontInfo embedFontInfo
= new EmbedFontInfo(metricsUrl, useKerning, tripletList, embedUrl, subFont);
embedFontInfo.setEncodingMode(encodingMode);
if (fontCache != null) {
if (!fontCache.containsFont(embedFontInfo)) {
fontCache.addFont(embedFontInfo);
}
}
if (log.isDebugEnabled()) {
String embedFile = embedFontInfo.getEmbedFile();
log.debug("Adding font " + (embedFile != null ? embedFile + ", " : "")
+ "metric file " + embedFontInfo.getMetricsFile());
for (int j = 0; j < tripletList.size(); ++j) {
FontTriplet triplet = (FontTriplet) tripletList.get(j);
log.debug(" Font triplet "
+ triplet.getName() + ", "
+ triplet.getStyle() + ", "
+ triplet.getWeight());
}
}
return embedFontInfo;
}
/**
* Creates a new FontTriplet given a triple Configuration
*
* @param tripletCfg a triplet configuration
* @return a font triplet font key
* @throws FOPException thrown if a FOP exception occurs
*/
private FontTriplet getFontTriplet(Configuration tripletCfg) throws FOPException {
try {
String name = tripletCfg.getAttribute("name");
if (name == null) {
LogUtil.handleError(log, "font-triplet without name", strict);
return null;
}
String weightStr = tripletCfg.getAttribute("weight");
if (weightStr == null) {
LogUtil.handleError(log, "font-triplet without weight", strict);
return null;
}
int weight = FontUtil.parseCSS2FontWeight(FontUtil.stripWhiteSpace(weightStr));
String style = tripletCfg.getAttribute("style");
if (style == null) {
LogUtil.handleError(log, "font-triplet without style", strict);
return null;
} else {
style = FontUtil.stripWhiteSpace(style);
}
return FontInfo.createFontKey(name, style, weight);
} catch (ConfigurationException e) {
LogUtil.handleException(log, e, strict);
}
return null;
}
}