<source><![CDATA[ <!-- This is an example of mapping actual IBM raster fonts / code pages to a FOP font -->
<font>
<!-- The afp-font element defines the IBM code page, the matching Java encoding and the
- path to the font -->
- <afp-font type="raster" codepage="T1V10500" encoding="Cp500" path="fonts/ibm">
+ base URI for the font -->
+ <afp-font type="raster" codepage="T1V10500" encoding="Cp500" base-uri="fonts/ibm/">
<!-- For a raster font a separate element for each font size is required providing
the font size and the corresponding IBM Character set name -->
<afp-raster-font size="7" characterset="C0N20070"/>
However, the characterset definition is now required within the afp-font element.</p>
<source><![CDATA[ <font>
<afp-font type="outline" codepage="T1V10500" encoding="Cp500" characterset="CZH200 "
- path="fonts/ibm" />
+ base-uri="file:/fonts/ibm" />
<font-triplet name="sans-serif" style="normal" weight="normal"/>
<font-triplet name="Helvetica" style="normal" weight="normal"/>
<font-triplet name="any" style="normal" weight="normal"/>
</font>
]]></source>
+ <p>
+ If "base-uri" is missing or a relative URI, the fonts are resolved relative to
+ the font base URI specified in the configuration (or on the FopFactory).
+ </p>
+ <note>
+ Previously, the location of the font files was given by the "path" attribute. This is still
+ supported for the time being, but you should move to using the more flexible "base-uri"
+ attribute so you can profit from the power of URI resolvers.
+ </note>
<p>Experimentation has shown that the font metrics for the FOP built-in Base14 fonts are actually
very similar to some of the IBM outline and raster fonts. In cases were the IBM font files are not
- available the path attribute in the afp-font element can be replaced by a base14-font attribute
+ available the base-uri attribute in the afp-font element can be replaced by a base14-font attribute
giving the name of the matching Base14 font. In this case the AFP Renderer will take the
font metrics from the built-in font.</p>
<source><![CDATA[ <!-- The following are examples of defining outline fonts based on FOP built-in
import org.apache.fop.afp.modca.ResourceGroup;
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.afp.util.ResourceAccessor;
-import org.apache.fop.afp.util.SimpleResourceAccessor;
/**
* Manages the creation and storage of document resources
/**
* Creates an included resource object by loading the contained object from a file.
* @param resourceName the name of the resource
- * @param basePath the base path in which to look for the resource files
+ * @param accessor resource accessor to access the resource with
* @param resourceObjectType the resource object type ({@link ResourceObject}.*)
* @throws IOException if an I/O error occurs while loading the resource
*/
- public void createIncludedResource(String resourceName, String basePath,
+ public void createIncludedResource(String resourceName, ResourceAccessor accessor,
byte resourceObjectType) throws IOException {
AFPResourceLevel resourceLevel = new AFPResourceLevel(AFPResourceLevel.PRINT_FILE);
URI uri;
if (log.isDebugEnabled()) {
log.debug("Adding included resource: " + resourceName);
}
- //TODO This works with local filenames only. In the long term, this
- //should work through FOP's URI resolver.
- ResourceAccessor accessor = new SimpleResourceAccessor(basePath);
IncludedResourceObject resourceContent = new IncludedResourceObject(
resourceName, accessor, uri);
num++;
}
}
- if (!fontInfo.hasFont("any", Font.STYLE_NORMAL, Font.WEIGHT_NORMAL)) {
- eventProducer.warnMissingDefaultFont(this, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL);
- }
- if (!fontInfo.hasFont("any", Font.STYLE_ITALIC, Font.WEIGHT_NORMAL)) {
- eventProducer.warnMissingDefaultFont(this, Font.STYLE_ITALIC, Font.WEIGHT_NORMAL);
- }
- if (!fontInfo.hasFont("any", Font.STYLE_NORMAL, Font.WEIGHT_BOLD)) {
- eventProducer.warnMissingDefaultFont(this, Font.STYLE_ITALIC, Font.WEIGHT_BOLD);
- }
- if (!fontInfo.hasFont("any", Font.STYLE_ITALIC, Font.WEIGHT_BOLD)) {
- eventProducer.warnMissingDefaultFont(this, Font.STYLE_ITALIC, Font.WEIGHT_BOLD);
- }
+ checkDefaultFontAvailable(fontInfo, eventProducer,
+ Font.STYLE_NORMAL, Font.WEIGHT_NORMAL);
+ checkDefaultFontAvailable(fontInfo, eventProducer,
+ Font.STYLE_ITALIC, Font.WEIGHT_NORMAL);
+ checkDefaultFontAvailable(fontInfo, eventProducer,
+ Font.STYLE_NORMAL, Font.WEIGHT_BOLD);
+ checkDefaultFontAvailable(fontInfo, eventProducer,
+ Font.STYLE_ITALIC, Font.WEIGHT_BOLD);
} else {
eventProducer.warnDefaultFontSetup(this);
return num;
}
+ private void checkDefaultFontAvailable(FontInfo fontInfo, AFPEventProducer eventProducer,
+ String style, int weight) {
+ if (!fontInfo.hasFont("any", style, weight)) {
+ eventProducer.warnMissingDefaultFont(this, style, weight);
+ }
+ }
+
}
package org.apache.fop.afp.fonts;
-import java.io.File;
import java.io.FileNotFoundException;
-import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
-import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.afp.AFPConstants;
+import org.apache.fop.afp.util.ResourceAccessor;
import org.apache.fop.afp.util.StructuredFieldReader;
/**
/**
* Static logging instance
*/
- protected static final Log log = LogFactory.getLog("org.apache.xmlgraphics.afp.fonts");
+ protected static final Log log = LogFactory.getLog(AFPFontReader.class);
/**
* Template used to convert lists to arrays.
/**
* The collection of code pages
*/
- private final Map/*<String, Map<String, String>>*/ codePages
+ private final Map/*<String, Map<String, String>>*/ codePagesCache
= new java.util.HashMap/*<String, Map<String, String>>*/();
/**
*
* @throws IOException in the event that an I/O exception of some sort has occurred
*/
- private InputStream openInputStream(String path, String filename) throws IOException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- if (classLoader == null) {
- classLoader = AFPFontReader.class.getClassLoader();
- }
-
- URL url = classLoader.getResource(path);
-
- if (url == null) {
- try {
- File file = new File(path);
- url = file.toURI().toURL();
- if (url == null) {
- String msg = "file not found " + filename + " in classpath: " + path;
- log.error(msg);
- throw new FileNotFoundException(msg);
- }
- } catch (MalformedURLException ex) {
- String msg = "file not found " + filename + " in classpath: " + path;
- log.error(msg);
- throw new FileNotFoundException(msg);
- }
- }
-
- File directory = FileUtils.toFile(url);
- if (!directory.canRead()) {
- String msg = "Failed to read directory " + url.getPath();
- log.error(msg);
- throw new FileNotFoundException(msg);
- }
-
- final String filterpattern = filename.trim();
- FilenameFilter filter = new FilenameFilter() {
- public boolean accept(File dir, String name) {
- return name.startsWith(filterpattern);
- }
- };
-
- File[] files = directory.listFiles(filter);
-
- if (files.length < 1) {
- String msg = "file search for " + filename + " located "
- + files.length + " files";
- log.error(msg);
- throw new FileNotFoundException(msg);
- } else if (files.length > 1) {
- String msg = "file search for " + filename + " located "
- + files.length + " files";
- log.warn(msg);
- }
-
- InputStream inputStream = files[0].toURI().toURL().openStream();
-
- if (inputStream == null) {
- String msg = "AFPFontReader:: getInputStream():: file not found for " + filename;
- log.error(msg);
- throw new FileNotFoundException(msg);
+ private InputStream openInputStream(ResourceAccessor accessor, String filename)
+ throws IOException {
+ URI uri;
+ try {
+ uri = new URI(filename.trim());
+ } catch (URISyntaxException e) {
+ throw new FileNotFoundException("Invalid filename: "
+ + filename + " (" + e.getMessage() + ")");
}
-
+ InputStream inputStream = accessor.createInputStream(uri);
return inputStream;
}
* chracter global identifier.
*/
String codePageId = new String(characterSet.getCodePage());
- String path = characterSet.getPath();
+ ResourceAccessor accessor = characterSet.getResourceAccessor();
- Map/*<String,String>*/ codePage = (Map/*<String,String>*/)codePages.get(codePageId);
+ Map/*<String,String>*/ codePage
+ = (Map/*<String,String>*/)codePagesCache.get(codePageId);
if (codePage == null) {
- codePage = loadCodePage(codePageId, characterSet.getEncoding(), path);
- codePages.put(codePageId, codePage);
+ codePage = loadCodePage(codePageId, characterSet.getEncoding(), accessor);
+ codePagesCache.put(codePageId, codePage);
}
/**
*/
final String characterSetName = characterSet.getName();
- inputStream = openInputStream(path, characterSetName);
+ inputStream = openInputStream(accessor, characterSetName);
StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
}
//process D3AC89 Font Position
- processFontPosition(structuredFieldReader, characterSetOrientations, metricNormalizationFactor);
+ processFontPosition(structuredFieldReader, characterSetOrientations,
+ metricNormalizationFactor);
//process D38C89 Font Index (per orientation)
for (int i = 0; i < characterSetOrientations.length; i++) {
* the code page identifier
* @param encoding
* the encoding to use for the character decoding
+ * @param accessor the resource accessor
* @returns a code page mapping
*/
private Map/*<String,String>*/ loadCodePage(String codePage, String encoding,
- String path) throws IOException {
+ ResourceAccessor accessor) throws IOException {
// Create the HashMap to store code page information
Map/*<String,String>*/ codePages = new java.util.HashMap/*<String,String>*/();
InputStream inputStream = null;
try {
- inputStream = openInputStream(path, codePage.trim());
+ inputStream = openInputStream(accessor, codePage.trim());
StructuredFieldReader structuredFieldReader = new StructuredFieldReader(inputStream);
byte[] data = structuredFieldReader.getNext(CHARACTER_TABLE_SF);
package org.apache.fop.afp.fonts;
+import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.afp.AFPConstants;
+import org.apache.fop.afp.util.ResourceAccessor;
+import org.apache.fop.afp.util.SimpleResourceAccessor;
import org.apache.fop.afp.util.StringUtils;
/**
protected String name;
/** The path to the installed fonts */
- protected String path;
+ private ResourceAccessor accessor;
/** Indicator as to whether to metrics have been loaded */
private boolean isMetricsLoaded = false;
* @param encoding the encoding of the font
* @param name the character set name
* @param path the path to the installed afp fonts
+ * @deprecated Please use {@link #CharacterSet(String, String, String, URI)} instead.
*/
public CharacterSet(String codePage, String encoding, String name, String path) {
+ this(codePage, encoding, name,
+ new SimpleResourceAccessor(path != null ? new File(path) : null));
+ }
+
+ /**
+ * Constructor for the CharacterSetMetric object, the character set is used
+ * to load the font information from the actual AFP font.
+ *
+ * @param codePage the code page identifier
+ * @param encoding the encoding of the font
+ * @param name the character set name
+ * @param accessor the resource accessor to load resource with
+ */
+ public CharacterSet(String codePage, String encoding, String name, ResourceAccessor accessor) {
if (name.length() > MAX_NAME_LEN) {
String msg = "Character set name '" + name + "' must be a maximum of "
+ MAX_NAME_LEN + " characters";
//This may happen with "Cp500" on Sun Java 1.4.2
this.encoder = null;
}
- this.path = path;
+ this.accessor = accessor;
this.characterSetOrientations = new java.util.HashMap(4);
}
}
/**
- * Returns the path where the font resources are installed
- *
- * @return the path where the font resources are installed
+ * Returns the resource accessor to load the font resources with.
+ * @return the resource accessor to load the font resources with
*/
- public String getPath() {
- return path;
+ public ResourceAccessor getResourceAccessor() {
+ return this.accessor;
}
/**
package org.apache.fop.afp.fonts;
+import org.apache.fop.afp.util.ResourceAccessor;
import org.apache.fop.fonts.Typeface;
/**
String name,
Typeface charSet) {
- super(codePage, encoding, name, null);
+ super(codePage, encoding, name, (ResourceAccessor)null);
this.charSet = charSet;
}
package org.apache.fop.afp.util;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
+import java.net.URL;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
+import org.apache.commons.io.IOUtils;
+
import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.fonts.FontManager;
/**
* Default implementation of the {@link ResourceAccessor} interface for use inside FOP.
*/
-public class DefaultFOPResourceAccessor implements ResourceAccessor {
+public class DefaultFOPResourceAccessor extends SimpleResourceAccessor {
private FOUserAgent userAgent;
+ private String categoryBaseURI;
/**
- * Main constructor.
+ * Constructor for resource to be accessed via the {@link FOUserAgent}. This contructor
+ * can take two base URIs: the category base URI is the one to use when differentiating between
+ * normal resources (ex. images) and font resources. So, if fonts need to be accessed, you can
+ * set the {@link FontManager}'s base URI instead of the one on the {@link FopFactory}.
* @param userAgent the FO user agent
+ * @param categoryBaseURI the category base URI (may be null)
+ * @param baseURI the custom base URI to resolve relative URIs against (may be null)
*/
- public DefaultFOPResourceAccessor(FOUserAgent userAgent) {
+ public DefaultFOPResourceAccessor(FOUserAgent userAgent, String categoryBaseURI, URI baseURI) {
+ super(baseURI);
this.userAgent = userAgent;
+ this.categoryBaseURI = categoryBaseURI;
}
/** {@inheritDoc} */
public InputStream createInputStream(URI uri) throws IOException {
- Source src = userAgent.resolveURI(uri.toASCIIString());
+ //Step 1: resolve against local base URI --> URI
+ URI resolved = resolveAgainstBase(uri);
+
+ //Step 2: resolve against the user agent --> stream
+ Source src;
+ src = userAgent.resolveURI(resolved.toASCIIString(), this.categoryBaseURI);
+
if (src == null) {
- return null;
+ throw new FileNotFoundException("Resource not found: " + uri.toASCIIString());
} else if (src instanceof StreamSource) {
StreamSource ss = (StreamSource)src;
InputStream in = ss.getInputStream();
- return in;
- } else {
- return null;
+ if (in != null) {
+ return in;
+ }
+ if (ss.getReader() != null) {
+ //Don't support reader, retry using system ID below
+ IOUtils.closeQuietly(ss.getReader());
+ }
}
+ URL url = new URL(src.getSystemId());
+ return url.openStream();
}
}
import java.net.URL;
/**
- * Simple implementation of the {@link ResourceAccessor} interface for access via files.
+ * Simple implementation of the {@link ResourceAccessor} interface for access relative to a
+ * base URI.
*/
public class SimpleResourceAccessor implements ResourceAccessor {
/**
* Creates a new simple resource accessor.
- * @param basePath the base path to resolve relative URIs to
+ * @param baseURI the base URI to resolve relative URIs against (may be null)
*/
- public SimpleResourceAccessor(File basePath) {
- this.baseURI = basePath.toURI();
+ public SimpleResourceAccessor(URI baseURI) {
+ this.baseURI = baseURI;
}
/**
* Creates a new simple resource accessor.
- * @param basePath the base path to resolve relative URIs to
+ * @param baseDir the base directory to resolve relative filenames against (may be null)
*/
- public SimpleResourceAccessor(String basePath) {
- this(new File(basePath));
+ public SimpleResourceAccessor(File baseDir) {
+ this(baseDir != null ? baseDir.toURI() : null);
+ }
+
+ /**
+ * Returns the base URI.
+ * @return the base URI (or null if no base URI was set)
+ */
+ public URI getBaseURI() {
+ return this.baseURI;
+ }
+
+ /**
+ * Resolve the given URI against the baseURI.
+ * @param uri the URI to resolve
+ * @return the resolved URI
+ */
+ protected URI resolveAgainstBase(URI uri) {
+ return (getBaseURI() != null ? getBaseURI().resolve(uri) : uri);
}
/** {@inheritDoc} */
public InputStream createInputStream(URI uri) throws IOException {
- URI resolved = this.baseURI.resolve(uri);
+ URI resolved = resolveAgainstBase(uri);
URL url = resolved.toURL();
return url.openStream();
}
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.afp.ptoca.PtocaBuilder;
import org.apache.fop.afp.ptoca.PtocaProducer;
+import org.apache.fop.afp.util.ResourceAccessor;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontTriplet;
try {
//Embed fonts (char sets and code pages)
//TODO This should be moved to a place where it has less performance impact
- if (charSet.getPath() != null) {
+ if (charSet.getResourceAccessor() != null) {
+ ResourceAccessor accessor = charSet.getResourceAccessor();
documentHandler.getResourceManager().createIncludedResource(
- charSet.getName(), charSet.getPath(),
+ charSet.getName(), accessor,
ResourceObject.TYPE_FONT_CHARACTER_SET);
documentHandler.getResourceManager().createIncludedResource(
- charSet.getCodePage(), charSet.getPath(),
+ charSet.getCodePage(), accessor,
ResourceObject.TYPE_CODE_PAGE);
}
} catch (IOException ioe) {
package org.apache.fop.render.afp;
import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.List;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.fop.afp.fonts.FopCharacterSet;
import org.apache.fop.afp.fonts.OutlineFont;
import org.apache.fop.afp.fonts.RasterFont;
+import org.apache.fop.afp.util.DefaultFOPResourceAccessor;
+import org.apache.fop.afp.util.ResourceAccessor;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.fonts.FontCollection;
log.error("Mandatory font configuration element '<afp-font...' is missing");
return null;
}
- String path = afpFontCfg.getAttribute("path", fontPath);
+
+ URI baseURI = null;
+ String uri = afpFontCfg.getAttribute("base-uri", fontPath);
+ if (uri == null) {
+ //Fallback for old attribute which only supports local filenames
+ String path = afpFontCfg.getAttribute("path", fontPath);
+ if (path != null) {
+ File f = new File(path);
+ baseURI = f.toURI();
+ }
+ } else {
+ try {
+ baseURI = new URI(uri);
+ } catch (URISyntaxException e) {
+ log.error("Invalid URI: " + e.getMessage());
+ return null;
+ }
+ }
+ ResourceAccessor accessor = new DefaultFOPResourceAccessor(
+ this.userAgent,
+ this.userAgent.getFactory().getFontManager().getFontBaseURL(),
+ baseURI);
+
String type = afpFontCfg.getAttribute("type");
if (type == null) {
log.error("Mandatory afp-font configuration attribute 'type=' is missing");
}
} else {
font.addCharacterSet(size, new CharacterSet(
- codepage, encoding, characterset, path));
+ codepage, encoding, characterset, accessor));
}
}
return new AFPFontInfo(font, tripletList);
log.error(msg);
}
} else {
- characterSet = new CharacterSet(codepage, encoding, characterset, path);
+ characterSet = new CharacterSet(codepage, encoding, characterset, accessor);
}
// Create a new font object
OutlineFont font = new OutlineFont(name, characterSet);
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="FOP Trunk" date="TBD">
+ <action context="Fonts" dev="JM" type="add">
+ AFP Fonts: Added support for full URI resolution on configured AFP fonts.
+ </action>
<action context="Renderers" dev="JM" type="add">
AFP Output: Tag Logical Element (TLE) is now also allowed on fo:page-sequence
(page group level).