/* * 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.render; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; 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.FileUtils; 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.apps.FOUserAgent; import org.apache.fop.apps.FopFactory; import org.apache.fop.fonts.CachedFontInfo; import org.apache.fop.fonts.EmbedFontInfo; import org.apache.fop.fonts.FontCache; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontResolver; import org.apache.fop.fonts.FontSetup; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.FontUtil; import org.apache.fop.fonts.autodetect.FontFileFinder; import org.apache.fop.fonts.autodetect.FontInfoFinder; import org.apache.fop.util.LogUtil; import org.apache.xmlgraphics.util.ClasspathResource; /** * Base Print renderer configurator (mostly handles font configuration) */ public class PrintRendererConfigurator extends AbstractRendererConfigurator implements RendererConfigurator { /** logger instance */ protected static Log log = LogFactory.getLog(PrintRendererConfigurator.class); /** * Default constructor * @param userAgent user agent */ public PrintRendererConfigurator(FOUserAgent userAgent) { super(userAgent); } /** * Builds a list of EmbedFontInfo objects for use with the setup() method. * * @param renderer print renderer * @throws FOPException if something's wrong with the config data */ public void configure(Renderer renderer) throws FOPException { Configuration cfg = getRendererConfig(renderer); if (cfg == null) { return; } PrintRenderer printRenderer = (PrintRenderer)renderer; FontResolver fontResolver = printRenderer.getFontResolver(); if (fontResolver == null) { //Ensure that we have minimal font resolution capabilities fontResolver = FontSetup.createMinimalFontResolver(); } FopFactory factory = userAgent.getFactory(); boolean strict = factory.validateUserConfigStrictly(); FontCache fontCache = factory.getFontCache(); List fontInfoList = buildFontListFromConfiguration(cfg, userAgent.getFontBaseURL(), fontResolver, strict, fontCache); if (fontCache != null && fontCache.hasChanged()) { fontCache.save(); } printRenderer.addFontList(fontInfoList); } /** * Builds a list of EmbedFontInfo objects for use with the setup() method. * * @param cfg Configuration object * @param fontBaseURL the base URL to resolve relative font URLs with * @param fontResolver the FontResolver to use * @param strict true if an Exception should be thrown if an error is found. * @param fontCache the font cache (or null if it is disabled) * @return a List of EmbedFontInfo objects. * @throws FOPException If an error occurs while processing the configuration */ public static List buildFontListFromConfiguration(Configuration cfg, String fontBaseURL, FontResolver fontResolver, boolean strict, FontCache fontCache) throws FOPException { List fontInfoList = new java.util.ArrayList(); Configuration fonts = cfg.getChild("fonts", false); if (fonts != null) { long start = 0; if (log.isDebugEnabled()) { log.debug("Starting font configuration..."); start = System.currentTimeMillis(); } // native o/s search (autodetect) configuration boolean autodetectFonts = (fonts.getChild("auto-detect", false) != null); if (autodetectFonts) { // search in font base if it is defined and // is a directory but don't recurse FontFileFinder fontFileFinder = new FontFileFinder(); if (fontBaseURL != null) { try { File fontBase = FileUtils.toFile(new URL(fontBaseURL)); if (fontBase != null) { //Can only use the font base URL if it's a file URL addFontInfoListFromFileList( fontFileFinder.find(fontBase.getAbsolutePath()), fontInfoList, fontResolver, fontCache ); } } catch (IOException e) { LogUtil.handleException(log, e, strict); } } // native o/s font directory finder try { addFontInfoListFromFileList( fontFileFinder.find(), fontInfoList, fontResolver, fontCache ); } catch (IOException e) { LogUtil.handleException(log, e, strict); } // load fonts from classpath addFontInfoListFromFileList(ClasspathResource.getInstance() .listResourcesOfMimeType("application/x-font"), fontInfoList, fontResolver, fontCache); addFontInfoListFromFileList( ClasspathResource.getInstance() .listResourcesOfMimeType( "application/x-font-truetype"), fontInfoList, fontResolver, fontCache); } // directory (multiple font) configuration Configuration[] directories = fonts.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; } FontFileFinder fontFileFinder = new FontFileFinder(recursive ? -1 : 1); try { addFontInfoListFromFileList( fontFileFinder.find(directory), fontInfoList, fontResolver, fontCache ); } catch (IOException e) { LogUtil.handleException(log, e, strict); } } // font file (singular) configuration Configuration[] font = fonts.getChildren("font"); for (int i = 0; i < font.length; i++) { EmbedFontInfo fontInfo = getFontInfoFromConfiguration( font[i], fontResolver, strict, fontCache); if (fontInfo != null) { fontInfoList.add(fontInfo); } } if (log.isDebugEnabled()) { log.debug("Finished font configuration in " + (System.currentTimeMillis() - start) + "ms"); } } return fontInfoList; } /** * Iterates over font file list adding font info to list * @param fontFileList font file list * @param fontInfoList font info list * @param resolver font resolver */ private static void addFontInfoListFromFileList( List fontFileList, List fontInfoList, FontResolver resolver, FontCache fontCache) { for (Iterator iter = fontFileList.iterator(); iter.hasNext();) { URL fontUrl = (URL)iter.next(); // parse font to ascertain font info FontInfoFinder finder = new FontInfoFinder(); EmbedFontInfo fontInfo = finder.find(fontUrl, resolver, fontCache); if (fontInfo != null) { fontInfoList.add(fontInfo); } } } 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 fontResolver font resolver used to resolve font * @param strict validate configuration strictly * @param fontCache the font cache (or null if it is disabled) * @return font info * @throws FOPException if something's wrong with the config data */ public static EmbedFontInfo getFontInfoFromConfiguration( Configuration fontCfg, FontResolver fontResolver, boolean strict, FontCache fontCache) throws FOPException { String metricsUrl = fontCfg.getAttribute("metrics-url", null); String embedUrl = fontCfg.getAttribute("embed-url", null); if (metricsUrl == null && embedUrl == null) { LogUtil.handleError(log, "Font configuration without metric-url or embed-url", 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; } } } boolean useKerning = fontCfg.getAttributeAsBoolean("kerning", true); EmbedFontInfo fontInfo = null; Configuration[] tripletCfg = fontCfg.getChildren("font-triplet"); // no font triplet info if (tripletCfg.length == 0) { LogUtil.handleError(log, "font without font-triplet", strict); // if not strict try to determine font info from the embed/metrics url File fontFile = CachedFontInfo.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(); return finder.find(fontUrl, fontResolver, fontCache); } else { return null; } } else { List tripleList = new java.util.ArrayList(); for (int j = 0; j < tripletCfg.length; j++) { try { String name = tripletCfg[j].getAttribute("name"); if (name == null) { LogUtil.handleError(log, "font-triplet without name", strict); continue; } String weightStr = tripletCfg[j].getAttribute("weight"); if (weightStr == null) { LogUtil.handleError(log, "font-triplet without weight", strict); continue; } int weight = FontUtil.parseCSS2FontWeight(weightStr); String style = tripletCfg[j].getAttribute("style"); if (style == null) { LogUtil.handleError(log, "font-triplet without style", strict); continue; } tripleList.add(FontInfo.createFontKey(name, style, weight)); } catch (ConfigurationException e) { LogUtil.handleException(log, e, strict); } } fontInfo = new EmbedFontInfo(metricsUrl, useKerning, tripleList, embedUrl); if (fontCache != null) { if (!fontCache.containsFont(fontInfo)) { fontCache.addFont(fontInfo); } } if (log.isDebugEnabled()) { log.debug("Adding font " + fontInfo.getEmbedFile() + ", metric file " + fontInfo.getMetricsFile()); for (int j = 0; j < tripleList.size(); ++j) { FontTriplet triplet = (FontTriplet) tripleList.get(j); log.debug(" Font triplet " + triplet.getName() + ", " + triplet.getStyle() + ", " + triplet.getWeight()); } } } return fontInfo; } }