From: Adrian Cumiskey
+ Document metadata is an important tool for categorizing and finding documents.
+ Various formats support different kinds of metadata representation and to
+ different levels. One of the more popular and flexible means of representing
+ document or object metadata is
+ XMP (eXtensible Metadata Platform, specified by Adobe).
+ PDF 1.4 introduced the use of XMP. The XMP specification lists recommendation for
+ embedding XMP metdata in other document and image formats. Given its flexibility it makes
+ sense to make use this approach in the XSL-FO context. Unfortunately, unlike SVG which
+ also refers to XMP, XSL-FO doesn't recommend a preferred way of specifying document and
+ object metadata. Therefore, there's no portable way to represent metadata in XSL-FO
+ documents. Each implementation does it differently.
+
+ As noted above, there's no officially recommended way to embed metadata in XSL-FO.
+ Apache FOP supports embedding XMP in XSL-FO. Currently, only support for document-level
+ metadata is implemented. Object-level metadata will be implemented when there's
+ interest.
+
+ Document-level metadata can be specified in the
+ Currently, XMP support is only available for PDF output.
+
+ Originally, you could set some metadata information through FOP's FOUserAgent by
+ using its set*() methods (like setTitle(String) or setAuthor(String). These values are
+ directly used to set value in the PDF Info object. Since PDF 1.4, adding metadata as an
+ XMP document to a PDF is possible. That means that there are now two mechanisms in PDF
+ that hold metadata.
+
+ Apache FOP now synchronizes the Info and the Metadata object in PDF, i.e. when you
+ set the title and the author through the FOUserAgent, the two values will end up in
+ the (old) Info object and in the new Metadata object as XMP content. If instead of
+ FOUserAgent, you embed XMP metadata in the XSL-FO document (as shown above), the
+ XMP metadata will be used as-is in the PDF Metadata object and some values from the
+ XMP metadata will be copied to the Info object to maintain backwards-compatibility
+ for PDF readers that don't support XMP metadata.
+
+ The mapping between the Info and the Metadata object used by Apache FOP comes from
+ the PDF/A-1 specification.
+ For convenience, here's the mapping table:
+
+ Metadata is made of property sets where each property set uses a different namespace URI.
+
+ The following is a listing of namespaces that Apache FOP recognizes and acts upon,
+ mostly to synchronize the XMP metadata with the PDF Info dictionary:
+
+ Please refer to the XMP Specification
+ for information on other metadata namespaces.
+
+ Property sets (Namespaces) not listed here are simply passed through to the final
+ document (if supported). That is useful if you want to specify a custom metadata
+ schema.
+
PDF/A is a standard which turns PDF into an "electronic document file
format for long-term preservation". PDF/A-1 is the first part of the
diff --git a/src/documentation/content/xdocs/faq.xml b/src/documentation/content/xdocs/faq.xml
index a75cd41e0..ac2693d8b 100644
--- a/src/documentation/content/xdocs/faq.xml
+++ b/src/documentation/content/xdocs/faq.xml
@@ -385,6 +385,47 @@
+ The full exception usually looks similar to this:
+
+ This exception is usually a follow-up error after another exception. Sometimes
+ the original exception gets swallowed by Xalan's default
+ The work-around is to set an explicit fo:declarations
element.
+ XMP specification recommends to use x:xmpmeta
, rdf:RDF
, and
+ rdf:Description
elements as shown in example below. Both
+ x:xmpmeta
and rdf:RDF
elements are recognized as the top-level
+ element introducing an XMP fragment (as per the XMP specification).
+ fo:declarations
must be declared after
+ fo:layout-master-set
and before the first page-sequence
.
+
+
+
+
+ Document information dictionary
+ XMP
+
+
+ Entry
+ PDF type
+ Property
+ XMP type
+ Category
+
+
+ Title
+ text string
+ dc:title
+ Text
+ External
+
+
+ Author
+ text string
+ dc:creator
+ seq Text
+ External
+
+
+ Subject
+ text string
+ dc:description["x-default"]
+ Text
+ External
+
+
+ Keywords
+ text string
+ pdf:Keywords
+ Text
+ External
+
+
+ Creator
+ text string
+ xmp:CreatorTool
+ Text
+ External
+
+
+ Producer
+ text string
+ pdf:Producer
+ Text
+ Internal
+
+
+ CreationDate
+ date
+ xmp:CreationDate
+ Date
+ Internal
+
+
+ ModDate
+ date
+ xmp:ModifyDate
+ Date
+ Internal
+ dc:subject
in the initial publication of
+ PDF/A-1 (ISO 19005-1). In the
+ Technical Corrigendum 1
+ this was changed to map to dc:description["x-default"]
.
+
+
+
+
+ Set/Schema
+ Namespace Prefix
+ Namespace URI
+
+
+ Dublin Core
+ dc
+ http://purl.org/dc/elements/1.1/
+
+
+ XMP Basic
+ xmp
+ http://ns.adobe.com/xap/1.0/
+
+
+ Adobe PDF Schema
+ pdf
+ http://ns.adobe.com/pdf/1.3/
+ ErrorListener
+ (should be fixed in the latest Xalan release).
+ ErrorListener
on the
+ Transformer
. The ErrorListener
can be as simple as this:
+
- Clipping as specified by the overflow="hidden"
is not yet
- implemented. If you have long words overflowing table cells, try to
+ Since the overflow
property doesn't apply to table-cell, you
+ can wrap the cell content in a block-container and specify
+ overflow="hidden"
there. Alternatively,
+ if you have long words overflowing table cells, try to
get them hyphenated. Artificial names like product identifications or
long numbers usually aren't hyphenated. You can try special processing
at XSLT level, like
@@ -574,6 +617,15 @@ Check the following:
+ If your text is not hyphenated at all and overflows the cell, please check
+ if you've specified keep-together="always"
on the table-cell
+ or one of its parent elements. keep-together="always"
implicitely
+ also sets keep-together.within-line="always"
which forbids FOP
+ to break the text into multiple lines. This is important as FOP supports inline-level
+ keeps since version 0.94. It's a good idea not to use the shorthand
+ keep-together="always"
at all!
+
+ Document metadata is an important tool for categorizing and finding documents. + Various formats support different kinds of metadata representation and to + different levels. One of the more popular and flexible means of representing + document or object metadata is + XMP (eXtensible Metadata Platform, specified by Adobe). + PDF 1.4 introduced the use of XMP. The XMP specification lists recommendation for + embedding XMP metdata in other document and image formats. Given its flexibility it makes + sense to make use this approach in the XSL-FO context. Unfortunately, unlike SVG which + also refers to XMP, XSL-FO doesn't recommend a preferred way of specifying document and + object metadata. Therefore, there's no portable way to represent metadata in XSL-FO + documents. Each implementation does it differently. +
++ As noted above, there's no officially recommended way to embed metadata in XSL-FO. + Apache FOP supports embedding XMP in XSL-FO. Currently, only support for document-level + metadata is implemented. Object-level metadata will be implemented when there's + interest. +
+
+ Document-level metadata can be specified in the fo:declarations
element.
+ XMP specification recommends to use x:xmpmeta
, rdf:RDF
, and
+ rdf:Description
elements as shown in example below. Both
+ x:xmpmeta
and rdf:RDF
elements are recognized as the top-level
+ element introducing an XMP fragment (as per the XMP specification).
+
fo:declarations
must be declared after
+ fo:layout-master-set
and before the first page-sequence
.
+ + Currently, XMP support is only available for PDF output. +
++ Originally, you could set some metadata information through FOP's FOUserAgent by + using its set*() methods (like setTitle(String) or setAuthor(String). These values are + directly used to set value in the PDF Info object. Since PDF 1.4, adding metadata as an + XMP document to a PDF is possible. That means that there are now two mechanisms in PDF + that hold metadata. +
++ Apache FOP now synchronizes the Info and the Metadata object in PDF, i.e. when you + set the title and the author through the FOUserAgent, the two values will end up in + the (old) Info object and in the new Metadata object as XMP content. If instead of + FOUserAgent, you embed XMP metadata in the XSL-FO document (as shown above), the + XMP metadata will be used as-is in the PDF Metadata object and some values from the + XMP metadata will be copied to the Info object to maintain backwards-compatibility + for PDF readers that don't support XMP metadata. +
++ The mapping between the Info and the Metadata object used by Apache FOP comes from + the PDF/A-1 specification. + For convenience, here's the mapping table: +
+Document information dictionary | +XMP | +|||
---|---|---|---|---|
Entry | +PDF type | +Property | +XMP type | +Category | +
Title | +text string | +dc:title | +Text | +External | +
Author | +text string | +dc:creator | +seq Text | +External | +
Subject | +text string | +dc:description["x-default"] | +Text | +External | +
Keywords | +text string | +pdf:Keywords | +Text | +External | +
Creator | +text string | +xmp:CreatorTool | +Text | +External | +
Producer | +text string | +pdf:Producer | +Text | +Internal | +
CreationDate | +date | +xmp:CreationDate | +Date | +Internal | +
ModDate | +date | +xmp:ModifyDate | +Date | +Internal | +
dc:subject
in the initial publication of
+ PDF/A-1 (ISO 19005-1). In the
+ Technical Corrigendum 1
+ this was changed to map to dc:description["x-default"]
.
+ + Metadata is made of property sets where each property set uses a different namespace URI. +
++ The following is a listing of namespaces that Apache FOP recognizes and acts upon, + mostly to synchronize the XMP metadata with the PDF Info dictionary: +
+Set/Schema | +Namespace Prefix | +Namespace URI | +
---|---|---|
Dublin Core | +dc | +http://purl.org/dc/elements/1.1/ | +
XMP Basic | +xmp | +http://ns.adobe.com/xap/1.0/ | +
Adobe PDF Schema | +http://ns.adobe.com/pdf/1.3/ | +
+ Please refer to the XMP Specification + for information on other metadata namespaces. +
++ Property sets (Namespaces) not listed here are simply passed through to the final + document (if supported). That is useful if you want to specify a custom metadata + schema. +
+
PDF/A is a standard which turns PDF into an "electronic document file
format for long-term preservation". PDF/A-1 is the first part of the
diff --git a/src/java/org/apache/fop/apps/FOURIResolver.java b/src/java/org/apache/fop/apps/FOURIResolver.java
index 1f4425a95..58ae6e8e6 100644
--- a/src/java/org/apache/fop/apps/FOURIResolver.java
+++ b/src/java/org/apache/fop/apps/FOURIResolver.java
@@ -34,14 +34,12 @@ import javax.xml.transform.stream.StreamSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.xmlgraphics.util.io.Base64EncodeStream;
-
-import org.apache.fop.util.DataURIResolver;
+import org.apache.xmlgraphics.util.uri.CommonURIResolver;
/**
* Provides FOP specific URI resolution. This is the default URIResolver
- * {@link FOUserAgent} will use unless overidden.
+ * {@link FOUserAgent} will use unless overridden.
*
* @see javax.xml.transform.URIResolver
*/
@@ -50,8 +48,8 @@ public class FOURIResolver implements javax.xml.transform.URIResolver {
// log
private Log log = LogFactory.getLog("FOP");
- /** URIResolver for RFC 2397 data URLs */
- private URIResolver dataURIResolver = new DataURIResolver();
+ /** Common URIResolver */
+ private CommonURIResolver commonURIResolver = new CommonURIResolver();
/** A user settable URI Resolver */
private URIResolver uriResolver = null;
@@ -152,7 +150,7 @@ public class FOURIResolver implements javax.xml.transform.URIResolver {
// data URLs can be quite long so evaluate early and don't try to build a File
// (can lead to problems)
- source = dataURIResolver.resolve(href, base);
+ source = commonURIResolver.resolve(href, base);
// Custom uri resolution
if (source == null && uriResolver != null) {
diff --git a/src/java/org/apache/fop/fo/properties/PropertyCache.java b/src/java/org/apache/fop/fo/properties/PropertyCache.java
index f834a78ae..d472b574c 100644
--- a/src/java/org/apache/fop/fo/properties/PropertyCache.java
+++ b/src/java/org/apache/fop/fo/properties/PropertyCache.java
@@ -33,9 +33,12 @@ import java.lang.ref.WeakReference;
*/
public final class PropertyCache {
+ private static final int SEGMENT_COUNT = 32; //0x20
+ private static final int INITIAL_BUCKET_COUNT = SEGMENT_COUNT;
+
/** bitmask to apply to the hash to get to the
* corresponding cache segment */
- private static final int SEGMENT_MASK = 0x1F;
+ private static final int SEGMENT_MASK = SEGMENT_COUNT - 1; //0x1F
/**
* Indicates whether the cache should be used at all
* Can be controlled by the system property:
@@ -44,13 +47,13 @@ public final class PropertyCache {
private final boolean useCache;
/** the segments array (length = 32) */
- private CacheSegment[] segments = new CacheSegment[SEGMENT_MASK + 1];
+ private CacheSegment[] segments = new CacheSegment[SEGMENT_COUNT];
/** the table of hash-buckets */
- private CacheEntry[] table = new CacheEntry[8];
+ private CacheEntry[] table = new CacheEntry[INITIAL_BUCKET_COUNT];
private Class runtimeType;
- final boolean[] votesForRehash = new boolean[SEGMENT_MASK + 1];
+ private boolean[] votesForRehash = new boolean[SEGMENT_COUNT];
/* same hash function as used by java.util.HashMap */
private static int hash(Object x) {
@@ -72,53 +75,61 @@ public final class PropertyCache {
}
/* Class modeling a cached entry */
- private final class CacheEntry extends WeakReference {
- volatile CacheEntry nextEntry;
- final int hash;
+ private static class CacheEntry extends WeakReference {
+ private volatile CacheEntry nextEntry;
+ private final int hash;
/* main constructor */
public CacheEntry(Object p, CacheEntry nextEntry, ReferenceQueue refQueue) {
super(p, refQueue);
this.nextEntry = nextEntry;
- this.hash = p.hashCode();
+ this.hash = hash(p);
+ }
+
+ /* main constructor */
+ public CacheEntry(Object p, CacheEntry nextEntry) {
+ super(p);
+ this.nextEntry = nextEntry;
+ this.hash = hash(p);
}
}
/* Wrapper objects to synchronize on */
- private final class CacheSegment {
+ private static class CacheSegment {
private int count = 0;
- private volatile ReferenceQueue staleEntries = new ReferenceQueue();
}
private void cleanSegment(int segmentIndex) {
- CacheEntry entry;
CacheSegment segment = segments[segmentIndex];
- int bucketIndex;
+
int oldCount = segment.count;
- while ((entry = (CacheEntry) segment.staleEntries.poll()) != null) {
- bucketIndex = hash(entry.hash) & (table.length - 1);
- /* remove obsolete entry */
- /* 1. move to the corresponding entry */
+ /* clean all buckets in this segment */
+ for (int bucketIndex = segmentIndex;
+ bucketIndex < table.length;
+ bucketIndex += SEGMENT_COUNT) {
CacheEntry prev = null;
- CacheEntry e = table[bucketIndex];
- while (e != null
- && e.nextEntry != null
- && e.hash != entry.hash) {
- prev = e;
- e = e.nextEntry;
+ CacheEntry entry = table[bucketIndex];
+ if (entry == null) {
+ continue;
}
- if (e != null) {
- /* 2. remove reference from the chain */
- if (prev == null) {
- table[bucketIndex] = e.nextEntry;
+ do {
+ if (entry.get() == null) {
+ if (prev == null) {
+ table[bucketIndex] = entry.nextEntry;
+ } else {
+ prev.nextEntry = entry.nextEntry;
+ }
+ segment.count--;
+ assert segment.count >= 0;
} else {
- prev.nextEntry = e.nextEntry;
+ prev = entry;
}
- segment.count--;
- }
+ entry = entry.nextEntry;
+ } while (entry != null);
}
+
synchronized (votesForRehash) {
if (oldCount > segment.count) {
votesForRehash[segmentIndex] = false;
@@ -129,7 +140,7 @@ public final class PropertyCache {
/* first time for this segment */
votesForRehash[segmentIndex] = true;
int voteCount = 0;
- for (int i = SEGMENT_MASK + 1; --i >= 0; ) {
+ for (int i = SEGMENT_MASK + 1; --i >= 0;) {
if (votesForRehash[i]) {
voteCount++;
}
@@ -156,14 +167,15 @@ public final class PropertyCache {
private void put(Object o) {
int hash = hash(o);
- CacheSegment segment = segments[hash & SEGMENT_MASK];
+ int segmentIndex = hash & SEGMENT_MASK;
+ CacheSegment segment = segments[segmentIndex];
synchronized (segment) {
int index = hash & (table.length - 1);
CacheEntry entry = table[index];
if (entry == null) {
- entry = new CacheEntry(o, null, segment.staleEntries);
+ entry = new CacheEntry(o, null);
table[index] = entry;
segment.count++;
} else {
@@ -171,14 +183,14 @@ public final class PropertyCache {
if (eq(p, o)) {
return;
} else {
- CacheEntry newEntry = new CacheEntry(o, entry, segment.staleEntries);
+ CacheEntry newEntry = new CacheEntry(o, entry);
table[index] = newEntry;
segment.count++;
}
}
if (segment.count > (2 * table.length)) {
- cleanSegment(hash & SEGMENT_MASK);
+ cleanSegment(segmentIndex);
}
}
}
@@ -195,7 +207,7 @@ public final class PropertyCache {
/* try non-synched first */
for (CacheEntry e = entry; e != null; e = e.nextEntry) {
- if (e.hash == o.hashCode()
+ if (e.hash == hash
&& (q = e.get()) != null
&& eq(q, o)) {
return q;
@@ -209,7 +221,7 @@ public final class PropertyCache {
synchronized (segment) {
entry = table[index];
for (CacheEntry e = entry; e != null; e = e.nextEntry) {
- if (e.hash == o.hashCode()
+ if (e.hash == hash
&& (q = e.get()) != null
&& eq(q, o)) {
return q;
@@ -235,7 +247,7 @@ public final class PropertyCache {
/* double the amount of buckets */
int newLength = table.length << 1;
if (newLength > 0) { //no overflow?
- /* reset segmentcounts */
+ /* reset segment counts */
for (int i = segments.length; --i >= 0;) {
segments[i].count = 0;
}
@@ -250,8 +262,7 @@ public final class PropertyCache {
if ((o = c.get()) != null) {
hash = c.hash;
idx = hash & newLength;
- newTable[idx] = new CacheEntry(o, newTable[idx],
- segments[hash & SEGMENT_MASK].staleEntries);
+ newTable[idx] = new CacheEntry(o, newTable[idx]);
segments[hash & SEGMENT_MASK].count++;
}
}
@@ -313,7 +324,7 @@ public final class PropertyCache {
* @param prop the Property instance to check for
* @return the cached instance
*/
- public final Property fetch(Property prop) {
+ public Property fetch(Property prop) {
return (Property) fetch((Object) prop);
}
@@ -326,7 +337,7 @@ public final class PropertyCache {
* @param chy the CommonHyphenation instance to check for
* @return the cached instance
*/
- public final CommonHyphenation fetch(CommonHyphenation chy) {
+ public CommonHyphenation fetch(CommonHyphenation chy) {
return (CommonHyphenation) fetch((Object) chy);
}
@@ -339,7 +350,7 @@ public final class PropertyCache {
* @param cf the CommonFont instance to check for
* @return the cached instance
*/
- public final CommonFont fetch(CommonFont cf) {
+ public CommonFont fetch(CommonFont cf) {
return (CommonFont) fetch((Object) cf);
}
@@ -352,21 +363,21 @@ public final class PropertyCache {
* @param cbpb the CommonBorderPaddingBackground instance to check for
* @return the cached instance
*/
- public final CommonBorderPaddingBackground fetch(CommonBorderPaddingBackground cbpb) {
+ public CommonBorderPaddingBackground fetch(CommonBorderPaddingBackground cbpb) {
return (CommonBorderPaddingBackground) fetch((Object) cbpb);
}
/**
- * Checks if the given {@link CommonBorderPaddingBackground.BorderInfo} is present in the cache -
- * if so, returns a reference to the cached instance.
+ * Checks if the given {@link CommonBorderPaddingBackground.BorderInfo} is present
+ * in the cache - if so, returns a reference to the cached instance.
* Otherwise the given object is added to the cache and returned.
*
* @param bi the BorderInfo instance to check for
* @return the cached instance
*/
- public final CommonBorderPaddingBackground.BorderInfo fetch(CommonBorderPaddingBackground.BorderInfo bi) {
-
+ public CommonBorderPaddingBackground.BorderInfo fetch(
+ CommonBorderPaddingBackground.BorderInfo bi) {
return (CommonBorderPaddingBackground.BorderInfo) fetch((Object) bi);
}
diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java
index ca2245a12..cd0a4133b 100644
--- a/src/java/org/apache/fop/svg/PDFGraphics2D.java
+++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java
@@ -285,7 +285,7 @@ public class PDFGraphics2D extends AbstractGraphics2D {
/**
* Get the string containing all the commands written into this
- * Grpahics.
+ * Graphics.
* @return the string containing the PDF markup
*/
public String getString() {
@@ -294,7 +294,7 @@ public class PDFGraphics2D extends AbstractGraphics2D {
/**
* Get the string buffer from the currentStream, containing all
- * the commands written into this Grpahics so far.
+ * the commands written into this Graphics so far.
* @return the StringBuffer containing the PDF markup
*/
public StringBuffer getBuffer() {
@@ -872,7 +872,7 @@ public class PDFGraphics2D extends AbstractGraphics2D {
if (paint instanceof RadialGradientPaint) {
RadialGradientPaint rgp = (RadialGradientPaint)paint;
- // There is essentially no way to support repeate
+ // There is essentially no way to support repeats
// in PDF for radial gradients (the one option would
// be to 'grow' the outer circle until it fully covered
// the bounds and then grow the stops accordingly, the
diff --git a/src/java/org/apache/fop/svg/PDFTextPainter.java b/src/java/org/apache/fop/svg/PDFTextPainter.java
index d8123c4fb..06fea54cc 100644
--- a/src/java/org/apache/fop/svg/PDFTextPainter.java
+++ b/src/java/org/apache/fop/svg/PDFTextPainter.java
@@ -147,7 +147,9 @@ public class PDFTextPainter extends StrokingTextPainter {
textUtil.beginTextObject();
textUtil.setFonts(fonts);
- textUtil.setTextRenderingMode(tpi.fillPaint != null, tpi.strokePaint != null, false);
+ boolean stroke = (tpi.strokePaint != null)
+ && (tpi.strokeStroke != null);
+ textUtil.setTextRenderingMode(tpi.fillPaint != null, stroke, false);
AffineTransform localTransform = new AffineTransform();
Point2D prevPos = null;
diff --git a/src/java/org/apache/fop/util/DataURIResolver.java b/src/java/org/apache/fop/util/DataURIResolver.java
index 89db6dc9d..99a8318c8 100644
--- a/src/java/org/apache/fop/util/DataURIResolver.java
+++ b/src/java/org/apache/fop/util/DataURIResolver.java
@@ -19,60 +19,25 @@
package org.apache.fop.util;
-import java.io.ByteArrayInputStream;
-
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
-import javax.xml.transform.stream.StreamSource;
-
-// base64 support for "data" urls
-import org.apache.xmlgraphics.util.io.Base64DecodeStream;
/**
- * Resolves data URLs (described in RFC 2397) returning its data as a StreamSource.
- *
- * @see javax.xml.transform.URIResolver
- * @see RFC 2397
+ * @deprecated
+ * @see org.apache.xmlgraphics.util.uri.DataURIResolver
*/
public class DataURIResolver implements URIResolver {
- /**
- * {@inheritDoc}
- */
- public Source resolve(String href, String base) throws TransformerException {
- if (href.startsWith("data:")) {
- return parseDataURI(href);
- } else {
- return null;
- }
- }
+ private final URIResolver newResolver = new org.apache.xmlgraphics.util.uri.DataURIResolver();
/**
- * Parses inline data URIs as generated by MS Word's XML export and FO
- * stylesheet.
- *
- * @see RFC 2397
+ * @deprecated
+ * @see org.apache.xmlgraphics.util.uri.DataURIResolver#resolve(String,
+ * String)
*/
- private Source parseDataURI(String href) {
- int commaPos = href.indexOf(',');
- // header is of the form data:[