From: Jeremias Maerki Date: Fri, 17 Feb 2006 10:57:15 +0000 (+0000) Subject: Initial support for XMP metadata (PDF 1.4) under fo:declarations. Both xmpmeta and... X-Git-Tag: fop-0_92-beta~119 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=c939529a9baf1a9ac1d16f935e8f11c89ca77be2;p=xmlgraphics-fop.git Initial support for XMP metadata (PDF 1.4) under fo:declarations. Both xmpmeta and RDF elements can be used as root elements for XMP metadata. Extracted DOM2SAX functionality from XMLXMLHandler into utility class since it is now reused elsewhere. New DOMBuilderContentHandlerFactory is used to create ContentHandlers that build generic DOMs. New DelegatingContentHandler is a convenience base class modelled after XMLFilterImpl but as passive SAX receiver. It is used by DOMBuilderContentHandlerFactory. Refactored FOTreeBuilder. FO tree building is now in a special ContentHandler which can be replaced temporarily when handling foreign XML like SVG or XMP. Extension Elements wanting the set their own ContentHandlers (instead of using the standard FO tree building mechanism) return a non-null value in getContentHandlerFactory(). The old mechanism is still supported (MathML, Plan and Barcode4J still use that). However, SVG support is changed to use a ContentHandlerFactory. Extension elements for xmpmeta and RDF elements making use of the new DOM build-up using ContentHandlerFactory. XMP metadata is passed to the renderer as ExtensionAttachment to the document. Only PDFRenderer uses the XMP extension attachment at this time. The PDFRenderer automatically builds XMP metadata based on the basic metadata information in the PDFInfo object if no explicit XMP metadata has been added to the XSL-FO document. XMP metadata merging is not implemented because this would involve a more sophisticated XMP infrastructure. That also means that XMP metadata is not validated. It's passed into the PDF as is. The PDF library now provides the PDFMetadata class to embed XMP metadata in PDFs version >=1.4. stream contents use a special filter list which is initially empty, so non-PDF-aware XMP readers can extract (and optionally modify) the XMP metadata. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@378482 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/fop/fo/ElementMapping.java b/src/java/org/apache/fop/fo/ElementMapping.java index 092a411f0..1142891b0 100644 --- a/src/java/org/apache/fop/fo/ElementMapping.java +++ b/src/java/org/apache/fop/fo/ElementMapping.java @@ -76,7 +76,7 @@ public abstract class ElementMapping { /** * @return the default DOMImplementation when no specialized DOM is necessary. */ - protected DOMImplementation getDefaultDOMImplementation() { + public static DOMImplementation getDefaultDOMImplementation() { DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance(); fact.setNamespaceAware(true); fact.setValidating(false); diff --git a/src/java/org/apache/fop/fo/ElementMappingRegistry.java b/src/java/org/apache/fop/fo/ElementMappingRegistry.java index 35224e7ae..ee8c7b86e 100644 --- a/src/java/org/apache/fop/fo/ElementMappingRegistry.java +++ b/src/java/org/apache/fop/fo/ElementMappingRegistry.java @@ -78,6 +78,8 @@ public class ElementMappingRegistry { addElementMapping("org.apache.fop.fo.extensions.svg.SVGElementMapping"); addElementMapping("org.apache.fop.fo.extensions.svg.BatikExtensionElementMapping"); addElementMapping("org.apache.fop.fo.extensions.ExtensionElementMapping"); + addElementMapping("org.apache.fop.fo.extensions.xmp.XMPElementMapping"); + addElementMapping("org.apache.fop.fo.extensions.xmp.RDFElementMapping"); addElementMapping("org.apache.fop.render.ps.extensions.PSExtensionElementMapping"); // add mappings from available services diff --git a/src/java/org/apache/fop/fo/FONode.java b/src/java/org/apache/fop/fo/FONode.java index 84d04b2c0..7663cb49b 100644 --- a/src/java/org/apache/fop/fo/FONode.java +++ b/src/java/org/apache/fop/fo/FONode.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fo.extensions.svg.SVGElementMapping; import org.apache.fop.fo.pagination.Root; import org.apache.fop.util.CharUtilities; +import org.apache.fop.util.ContentHandlerFactory; /** * Base class for nodes in the XML tree @@ -568,5 +569,15 @@ public abstract class FONode implements Cloneable { return null; } + /** + * This method is overridden by extension elements and allows the extension element to return + * a ContentHandlerFactory. This factory can create ContentHandler implementations that handle + * foreign XML content by either building up a specific DOM, a Java object or something else. + * @return the ContentHandlerFactory or null if not applicable + */ + public ContentHandlerFactory getContentHandlerFactory() { + return null; + } + } diff --git a/src/java/org/apache/fop/fo/FOTreeBuilder.java b/src/java/org/apache/fop/fo/FOTreeBuilder.java index f674d58c8..d5d03b7b7 100644 --- a/src/java/org/apache/fop/fo/FOTreeBuilder.java +++ b/src/java/org/apache/fop/fo/FOTreeBuilder.java @@ -29,7 +29,11 @@ import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.fo.ElementMapping.Maker; import org.apache.fop.fo.pagination.Root; import org.apache.fop.image.ImageFactory; +import org.apache.fop.util.ContentHandlerFactory; +import org.apache.fop.util.ContentHandlerFactory.ObjectSource; +import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener; import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; @@ -56,16 +60,12 @@ public class FOTreeBuilder extends DefaultHandler { */ protected Root rootFObj = null; - /** - * Current formatting object being handled - */ - protected FONode currentFObj = null; - - /** - * Current propertyList for the node being handled. - */ - protected PropertyList currentPropertyList; - + /** Main DefaultHandler that handles the FO namespace. */ + protected MainFOHandler mainFOHandler; + + /** Current delegate ContentHandler to receive the SAX events */ + protected ContentHandler delegate; + /** * The class that handles formatting and rendering to a stream * (mark-fop@inomial.com) @@ -80,6 +80,8 @@ public class FOTreeBuilder extends DefaultHandler { private boolean used = false; + private int depth; + /** * FOTreeBuilder constructor * @param outputFormat the MIME type of the output format to use (ex. "application/pdf"). @@ -135,11 +137,8 @@ public class FOTreeBuilder extends DefaultHandler { * @see org.xml.sax.ContentHandler#characters(char[], int, int) */ public void characters(char[] data, int start, int length) - throws FOPException { - if (currentFObj != null) { - currentFObj.addCharacters(data, start, start + length, - currentPropertyList, getEffectiveLocator()); - } + throws SAXException { + delegate.characters(data, start, length); } /** @@ -157,6 +156,9 @@ public class FOTreeBuilder extends DefaultHandler { log.debug("Building formatting object tree"); } foEventHandler.startDocument(); + this.mainFOHandler = new MainFOHandler(); + this.mainFOHandler.startDocument(); + this.delegate = this.mainFOHandler; } /** @@ -164,8 +166,8 @@ public class FOTreeBuilder extends DefaultHandler { * @see org.xml.sax.ContentHandler#endDocument() */ public void endDocument() throws SAXException { + this.delegate.endDocument(); rootFObj = null; - currentFObj = null; if (log.isDebugEnabled()) { log.debug("Parsing of document complete"); } @@ -181,55 +183,8 @@ public class FOTreeBuilder extends DefaultHandler { */ public void startElement(String namespaceURI, String localName, String rawName, Attributes attlist) throws SAXException { - - /* the node found in the FO document */ - FONode foNode; - PropertyList propertyList; - - // Check to ensure first node encountered is an fo:root - if (rootFObj == null) { - if (!namespaceURI.equals(FOElementMapping.URI) - || !localName.equals("root")) { - throw new SAXException(new ValidationException( - "Error: First element must be the fo:root formatting object. Found " - + FONode.getNodeString(namespaceURI, localName) + " instead." - + " Please make sure you're producing a valid XSL-FO document.")); - } - } else { // check that incoming node is valid for currentFObj - if (namespaceURI.equals(FOElementMapping.URI)) { - // currently no fox: elements to validate - // || namespaceURI.equals(ExtensionElementMapping.URI) */) { - try { - currentFObj.validateChildNode(locator, namespaceURI, localName); - } catch (ValidationException e) { - throw e; - } - } - } - - ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName); -// log.debug("found a " + fobjMaker.toString()); - - try { - foNode = fobjMaker.make(currentFObj); - propertyList = foNode.createPropertyList(currentPropertyList, foEventHandler); - foNode.processNode(localName, getEffectiveLocator(), attlist, propertyList); - foNode.startOfNode(); - } catch (IllegalArgumentException e) { - throw new SAXException(e); - } - - if (rootFObj == null) { - rootFObj = (Root) foNode; - rootFObj.setFOEventHandler(foEventHandler); - } else { - currentFObj.addChildNode(foNode); - } - - currentFObj = foNode; - if (propertyList != null) { - currentPropertyList = propertyList; - } + this.depth++; + delegate.startElement(namespaceURI, localName, rawName, attlist); } /** @@ -237,25 +192,17 @@ public class FOTreeBuilder extends DefaultHandler { * @see org.xml.sax.ContentHandler#endElement(String, String, String) */ public void endElement(String uri, String localName, String rawName) - throws FOPException { - if (currentFObj == null) { - throw new IllegalStateException( - "endElement() called for " + rawName + " where there is no current element."); - } else if (!currentFObj.getLocalName().equals(localName) - || !currentFObj.getNamespaceURI().equals(uri)) { - log.warn("Mismatch: " + currentFObj.getLocalName() - + " (" + currentFObj.getNamespaceURI() - + ") vs. " + localName + " (" + uri + ")"); - } - currentFObj.endOfNode(); - - if (currentPropertyList.getFObj() == currentFObj) { - currentPropertyList = currentPropertyList.getParentPropertyList(); - } - if (currentFObj.getParent() == null) { - log.debug("endElement for top-level " + currentFObj.getName()); + throws SAXException { + this.delegate.endElement(uri, localName, rawName); + this.depth--; + if (depth == 0) { + if (delegate != mainFOHandler) { + //Return from sub-handler back to main handler + delegate.endDocument(); + delegate = mainFOHandler; + delegate.endElement(uri, localName, rawName); + } } - currentFObj = currentFObj.getParent(); } /** @@ -309,5 +256,137 @@ public class FOTreeBuilder extends DefaultHandler { } } + /** + * Main DefaultHandler implementation which builds the FO tree. + */ + private class MainFOHandler extends DefaultHandler { + + /** + * Current formatting object being handled + */ + protected FONode currentFObj = null; + + /** + * Current propertyList for the node being handled. + */ + protected PropertyList currentPropertyList; + + /** + * SAX Handler for the start of an element + * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) + */ + public void startElement(String namespaceURI, String localName, String rawName, + Attributes attlist) throws SAXException { + + /* the node found in the FO document */ + FONode foNode; + PropertyList propertyList; + + // Check to ensure first node encountered is an fo:root + if (rootFObj == null) { + if (!namespaceURI.equals(FOElementMapping.URI) + || !localName.equals("root")) { + throw new SAXException(new ValidationException( + "Error: First element must be the fo:root formatting object. Found " + + FONode.getNodeString(namespaceURI, localName) + " instead." + + " Please make sure you're producing a valid XSL-FO document.")); + } + } else { // check that incoming node is valid for currentFObj + if (namespaceURI.equals(FOElementMapping.URI)) { + // currently no fox: elements to validate + // || namespaceURI.equals(ExtensionElementMapping.URI) */) { + try { + currentFObj.validateChildNode(locator, namespaceURI, localName); + } catch (ValidationException e) { + throw e; + } + } + } + + ElementMapping.Maker fobjMaker = findFOMaker(namespaceURI, localName); + + try { + foNode = fobjMaker.make(currentFObj); + propertyList = foNode.createPropertyList(currentPropertyList, foEventHandler); + foNode.processNode(localName, getEffectiveLocator(), attlist, propertyList); + foNode.startOfNode(); + } catch (IllegalArgumentException e) { + throw new SAXException(e); + } + + ContentHandlerFactory chFactory = foNode.getContentHandlerFactory(); + if (chFactory != null) { + ContentHandler subHandler = chFactory.createContentHandler(); + if (subHandler instanceof ObjectSource + && foNode instanceof ObjectBuiltListener) { + ((ObjectSource)subHandler).setObjectBuiltListener((ObjectBuiltListener)foNode); + } + + subHandler.startDocument(); + subHandler.startElement(namespaceURI, localName, rawName, attlist); + depth = 1; + delegate = subHandler; + } + + if (rootFObj == null) { + rootFObj = (Root) foNode; + rootFObj.setFOEventHandler(foEventHandler); + } else { + currentFObj.addChildNode(foNode); + } + + currentFObj = foNode; + if (propertyList != null) { + currentPropertyList = propertyList; + } + } + + /** + * SAX Handler for the end of an element + * @see org.xml.sax.ContentHandler#endElement(String, String, String) + */ + public void endElement(String uri, String localName, String rawName) + throws SAXException { + if (currentFObj == null) { + throw new IllegalStateException( + "endElement() called for " + rawName + + " where there is no current element."); + } else if (!currentFObj.getLocalName().equals(localName) + || !currentFObj.getNamespaceURI().equals(uri)) { + log.warn("Mismatch: " + currentFObj.getLocalName() + + " (" + currentFObj.getNamespaceURI() + + ") vs. " + localName + " (" + uri + ")"); + } + currentFObj.endOfNode(); + + if (currentPropertyList.getFObj() == currentFObj) { + currentPropertyList = currentPropertyList.getParentPropertyList(); + } + if (currentFObj.getParent() == null) { + log.debug("endElement for top-level " + currentFObj.getName()); + } + currentFObj = currentFObj.getParent(); + } + + /** + * SAX Handler for characters + * @see org.xml.sax.ContentHandler#characters(char[], int, int) + */ + public void characters(char[] data, int start, int length) + throws FOPException { + if (currentFObj != null) { + currentFObj.addCharacters(data, start, start + length, + currentPropertyList, getEffectiveLocator()); + } + } + + public void endDocument() throws SAXException { + currentFObj = null; + } + + + + } + } diff --git a/src/java/org/apache/fop/fo/XMLObj.java b/src/java/org/apache/fop/fo/XMLObj.java index 55d8c2988..87cb004c3 100644 --- a/src/java/org/apache/fop/fo/XMLObj.java +++ b/src/java/org/apache/fop/fo/XMLObj.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import org.apache.batik.dom.util.XMLSupport; import org.apache.fop.apps.FOPException; +import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.Attributes; @@ -35,7 +36,7 @@ import org.xml.sax.Locator; * Abstract class modelling generic, non-XSL-FO XML objects. Such objects are * stored in a DOM. */ -public abstract class XMLObj extends FONode { +public abstract class XMLObj extends FONode implements ObjectBuiltListener { // temp reference for attributes private Attributes attr = null; @@ -112,6 +113,12 @@ public abstract class XMLObj extends FONode { this.doc = doc; element = doc.createElementNS(getNamespaceURI(), name); + setAttributes(element, attr); + attr = null; + parent.appendChild(element); + } + + private static void setAttributes(Element element, Attributes attr) { for (int count = 0; count < attr.getLength(); count++) { String rf = attr.getValue(count); String qname = attr.getQName(count); @@ -128,10 +135,8 @@ public abstract class XMLObj extends FONode { } } } - attr = null; - parent.appendChild(element); } - + /** * Add the top-level element to the DOM document * @param doc DOM document @@ -139,22 +144,7 @@ public abstract class XMLObj extends FONode { */ public void buildTopLevel(Document doc, Element svgRoot) { // build up the info for the top level element - for (int count = 0; count < attr.getLength(); count++) { - String rf = attr.getValue(count); - String qname = attr.getQName(count); - int idx = qname.indexOf(":"); - if (idx == -1) { - element.setAttribute(qname, rf); - } else { - String pref = qname.substring(0, idx); - String tail = qname.substring(idx + 1); - if (pref.equals("xmlns")) { - ns.put(tail, rf); - } else { - element.setAttributeNS((String)ns.get(pref), tail, rf); - } - } - } + setAttributes(element, attr); } /** @@ -180,6 +170,7 @@ public abstract class XMLObj extends FONode { } } catch (Exception e) { + //TODO this is ugly because there may be subsequent failures like NPEs log.error("Error while trying to instantiate a DOM Document", e); } return doc; @@ -214,5 +205,10 @@ public abstract class XMLObj extends FONode { element.appendChild(text); } + /** @see org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener */ + public void notifyObjectBuilt(Object obj) { + this.doc = (Document)obj; + } + } diff --git a/src/java/org/apache/fop/fo/extensions/svg/SVGElement.java b/src/java/org/apache/fop/fo/extensions/svg/SVGElement.java index c5d15fde7..47cc66340 100644 --- a/src/java/org/apache/fop/fo/extensions/svg/SVGElement.java +++ b/src/java/org/apache/fop/fo/extensions/svg/SVGElement.java @@ -22,6 +22,8 @@ package org.apache.fop.fo.extensions.svg; import org.apache.fop.apps.FOPException; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; +import org.apache.fop.util.ContentHandlerFactory; +import org.apache.fop.util.DOMBuilderContentHandlerFactory; import org.apache.batik.dom.svg.SVGOMDocument; import org.apache.batik.dom.svg.SVGOMElement; @@ -58,6 +60,14 @@ public class SVGElement extends SVGObj { super(parent); } + /** + * @see org.apache.fop.fo.FONode#getContentHandlerFactory() + */ + public ContentHandlerFactory getContentHandlerFactory() { + return new DOMBuilderContentHandlerFactory(getNamespaceURI(), + SVGDOMImplementation.getDOMImplementation()); + } + /** * @see org.apache.fop.fo.FONode#processNode */ @@ -287,5 +297,6 @@ public class SVGElement extends SVGObj { return 100; } } + } diff --git a/src/java/org/apache/fop/fo/extensions/xmp/RDFElement.java b/src/java/org/apache/fop/fo/extensions/xmp/RDFElement.java new file mode 100644 index 000000000..ea8e48ba8 --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/xmp/RDFElement.java @@ -0,0 +1,82 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.fo.extensions.xmp; + +import org.apache.fop.fo.ElementMapping; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FObj; +import org.apache.fop.fo.XMLObj; +import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.util.ContentHandlerFactory; +import org.apache.fop.util.DOMBuilderContentHandlerFactory; + +/** + * Represents the top-level "RDF" element used by XMP metadata. + */ +public class RDFElement extends XMLObj { + + private XMPMetadata attachment; + + /** + * Main constructor. + * @param parent the parent formatting object + */ + public RDFElement(FONode parent) { + super(parent); + } + + /** @see org.apache.fop.fo.FONode#getNormalNamespacePrefix() */ + public String getNormalNamespacePrefix() { + return "rdf"; + } + + /** @see org.apache.fop.fo.FONode#getNamespaceURI() */ + public String getNamespaceURI() { + return XMPConstants.RDF_NAMESPACE; + } + + /** + * @see org.apache.fop.fo.FONode#getContentHandlerFactory() + */ + public ContentHandlerFactory getContentHandlerFactory() { + return new DOMBuilderContentHandlerFactory(getNamespaceURI(), + ElementMapping.getDefaultDOMImplementation()); + } + + /** @see org.apache.fop.fo.FONode#getExtensionAttachment() */ + public ExtensionAttachment getExtensionAttachment() { + if (parent instanceof FObj) { + if (attachment == null) { + attachment = new XMPMetadata(doc); + } + return attachment; + } else { + return super.getExtensionAttachment(); + } + } + + /** + * @see org.apache.fop.fo.XMLObj#notifyObjectBuilt(java.lang.Object) + */ + public void notifyObjectBuilt(Object obj) { + super.notifyObjectBuilt(obj); + attachment.setDocument(doc); + } + +} diff --git a/src/java/org/apache/fop/fo/extensions/xmp/RDFElementMapping.java b/src/java/org/apache/fop/fo/extensions/xmp/RDFElementMapping.java new file mode 100644 index 000000000..566a6c647 --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/xmp/RDFElementMapping.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.fo.extensions.xmp; + +import java.util.HashMap; + +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.ElementMapping; + +import org.w3c.dom.DOMImplementation; + +/** + * Setup the element mapping for XMP metadata. + */ +public class RDFElementMapping extends ElementMapping { + + /** Main constructor. */ + public RDFElementMapping() { + namespaceURI = XMPConstants.RDF_NAMESPACE; + } + + /** @see org.apache.fop.fo.ElementMapping#getDOMImplementation() */ + public DOMImplementation getDOMImplementation() { + return getDefaultDOMImplementation(); + } + + /** @see org.apache.fop.fo.ElementMapping#initialize() */ + protected void initialize() { + if (foObjs == null) { + foObjs = new HashMap(); + foObjs.put("RDF", new RDFElementMaker()); + } + } + + static class RDFElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new RDFElement(parent); + } + } + +} diff --git a/src/java/org/apache/fop/fo/extensions/xmp/XMPConstants.java b/src/java/org/apache/fop/fo/extensions/xmp/XMPConstants.java new file mode 100644 index 000000000..b8b29988c --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/xmp/XMPConstants.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.fo.extensions.xmp; + +/** + * Constants used in XMP metadata. + */ +public interface XMPConstants { + + /** Namespace URI for XMP */ + String XMP_NAMESPACE = "adobe:ns:meta/"; + + /** Namespace URI for RDF */ + String RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + + /** Namespace URI for Dublin Core */ + String DUBLIN_CORE_NAMESPACE = "http://purl.org/dc/elements/1.1/"; + + /** Namespace URI for the XMP Basic Schema */ + String XMP_BASIC_NAMESPACE = "http://ns.adobe.com/xap/1.0/"; + +} diff --git a/src/java/org/apache/fop/fo/extensions/xmp/XMPElementMapping.java b/src/java/org/apache/fop/fo/extensions/xmp/XMPElementMapping.java new file mode 100644 index 000000000..230ea8151 --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/xmp/XMPElementMapping.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.fo.extensions.xmp; + +import java.util.HashMap; + +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.ElementMapping; + +import org.w3c.dom.DOMImplementation; + +/** + * Setup the element mapping for XMP metadata. + */ +public class XMPElementMapping extends ElementMapping { + + /** Main constructor. */ + public XMPElementMapping() { + namespaceURI = XMPConstants.XMP_NAMESPACE; + } + + /** @see org.apache.fop.fo.ElementMapping#getDOMImplementation() */ + public DOMImplementation getDOMImplementation() { + return getDefaultDOMImplementation(); + } + + /** @see org.apache.fop.fo.ElementMapping#initialize() */ + protected void initialize() { + if (foObjs == null) { + foObjs = new HashMap(); + foObjs.put("xmpmeta", new XMPMetaElementMaker()); + } + } + + static class XMPMetaElementMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new XMPMetaElement(parent); + } + } + +} diff --git a/src/java/org/apache/fop/fo/extensions/xmp/XMPMetaElement.java b/src/java/org/apache/fop/fo/extensions/xmp/XMPMetaElement.java new file mode 100644 index 000000000..4f39fb13b --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/xmp/XMPMetaElement.java @@ -0,0 +1,82 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.fo.extensions.xmp; + +import org.apache.fop.fo.ElementMapping; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FObj; +import org.apache.fop.fo.XMLObj; +import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.util.ContentHandlerFactory; +import org.apache.fop.util.DOMBuilderContentHandlerFactory; + +/** + * Represents the top-level "xmpmeta" element used by XMP metadata. + */ +public class XMPMetaElement extends XMLObj { + + private XMPMetadata attachment; + + /** + * Main constructor. + * @param parent the parent formatting object + */ + public XMPMetaElement(FONode parent) { + super(parent); + } + + /** @see org.apache.fop.fo.FONode#getNormalNamespacePrefix() */ + public String getNormalNamespacePrefix() { + return "x"; + } + + /** @see org.apache.fop.fo.FONode#getNamespaceURI() */ + public String getNamespaceURI() { + return XMPConstants.XMP_NAMESPACE; + } + + /** + * @see org.apache.fop.fo.FONode#getContentHandlerFactory() + */ + public ContentHandlerFactory getContentHandlerFactory() { + return new DOMBuilderContentHandlerFactory(getNamespaceURI(), + ElementMapping.getDefaultDOMImplementation()); + } + + /** @see org.apache.fop.fo.FONode#getExtensionAttachment() */ + public ExtensionAttachment getExtensionAttachment() { + if (parent instanceof FObj) { + if (attachment == null) { + attachment = new XMPMetadata(doc); + } + return attachment; + } else { + return super.getExtensionAttachment(); + } + } + + /** + * @see org.apache.fop.fo.XMLObj#notifyObjectBuilt(java.lang.Object) + */ + public void notifyObjectBuilt(Object obj) { + super.notifyObjectBuilt(obj); + attachment.setDocument(doc); + } + +} diff --git a/src/java/org/apache/fop/fo/extensions/xmp/XMPMetadata.java b/src/java/org/apache/fop/fo/extensions/xmp/XMPMetadata.java new file mode 100644 index 000000000..fd84b2412 --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/xmp/XMPMetadata.java @@ -0,0 +1,92 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.fo.extensions.xmp; + +import java.io.Serializable; + +import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.util.DOM2SAX; +import org.apache.fop.util.XMLizable; +import org.w3c.dom.Document; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +/** + * This is the pass-through value object for the XMP metadata extension. + */ +public class XMPMetadata implements ExtensionAttachment, Serializable, XMLizable { + + /** The category URI for this extension attachment. */ + public static final String CATEGORY = XMPConstants.XMP_NAMESPACE; + + private Document doc; + private boolean readOnly = true; + + /** + * No-argument contructor. + */ + public XMPMetadata() { + //nop + } + + /** + * Default constructor. + * @param doc the DOM document containing the XMP metadata + */ + public XMPMetadata(Document doc) { + this.doc = doc; + } + + /** @return the DOM document containing the XMP metadata */ + public Document getDocument() { + return this.doc; + } + + /** + * Sets the DOM document containing the XMP metadata. + * @param document the DOM document + */ + public void setDocument(Document document) { + this.doc = document; + } + + /** @return true if the XMP metadata is marked read-only. */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Sets the flag that decides whether a metadata packet may be modified. + * @param readOnly true if the XMP metadata packet should be marked read-only. + */ + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + /** @see org.apache.fop.fo.extensions.ExtensionAttachment#getCategory() */ + public String getCategory() { + return CATEGORY; + } + + /** @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler) */ + public void toSAX(ContentHandler handler) throws SAXException { + DOM2SAX.writeDocument(getDocument(), handler); + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index 4d16dfa74..572e3be43 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2004,2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,13 @@ public class PDFDocument { * the version of PDF supported which is 1.4 */ protected static final String PDF_VERSION = "1.4"; + + /** Integer constant to represent PDF 1.3 */ + public static final int PDF_VERSION_1_3 = 3; + /** Integer constant to represent PDF 1.4 */ + public static final int PDF_VERSION_1_4 = 4; + /** * the encoding to use when converting strings to PDF commandos. */ @@ -98,6 +104,9 @@ public class PDFDocument { */ protected int xref; + /** Indicates what PDF version is active */ + protected int pdfVersion = PDF_VERSION_1_4; + /** * the /Root object */ @@ -234,6 +243,13 @@ public class PDFDocument { this.info = getFactory().makeInfo(prod); } + /** + * @return the integer representing the active PDF version (one of PDFDocument.PDF_VERSION_*) + */ + public int getPDFVersion() { + return this.pdfVersion; + } + /** * Returns the factory for PDF objects. * @return PDFFactory the factory diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 39b172417..6222cf1f0 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -29,6 +29,9 @@ import java.util.Map; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; +//W3C DOM +import org.w3c.dom.Document; + // Apache libs import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; @@ -55,7 +58,7 @@ public class PDFFactory { private PDFDocument document; - private Log log = LogFactory.getLog("org.apache.fop.pdf"); + private Log log = LogFactory.getLog(PDFFactory.class); /** * Creates a new PDFFactory. @@ -86,6 +89,7 @@ public class PDFFactory { public PDFRoot makeRoot(PDFPages pages) { //Make a /Pages object. This object is written in the trailer. PDFRoot pdfRoot = new PDFRoot(++this.document.objectcount, pages); + pdfRoot.setDocument(getDocument()); getDocument().addTrailerObject(pdfRoot); return pdfRoot; } @@ -97,6 +101,7 @@ public class PDFFactory { */ public PDFPages makePages() { PDFPages pdfPages = new PDFPages(++(this.document.objectcount)); + pdfPages.setDocument(getDocument()); getDocument().addTrailerObject(pdfPages); return pdfPages; } @@ -108,6 +113,7 @@ public class PDFFactory { */ public PDFResources makeResources() { PDFResources pdfResources = new PDFResources(++this.document.objectcount); + pdfResources.setDocument(getDocument()); getDocument().addTrailerObject(pdfResources); return pdfResources; } @@ -131,6 +137,18 @@ public class PDFFactory { return pdfInfo; } + /** + * Make a Metadata object. + * @param doc the DOM Document containing the XMP metadata. + * @param readOnly true if the metadata packet should be marked read-only + * @return the newly created Metadata object + */ + public PDFMetadata makeMetadata(Document doc, boolean readOnly) { + PDFMetadata pdfMetadata = new PDFMetadata(doc, readOnly); + getDocument().registerObject(pdfMetadata); + return pdfMetadata; + } + /** * Make a /Page object. The page is assigned an object number immediately * so references can already be made. The page must be added to the @@ -1302,6 +1320,4 @@ public class PDFFactory { return obj; } - - } diff --git a/src/java/org/apache/fop/pdf/PDFFilterList.java b/src/java/org/apache/fop/pdf/PDFFilterList.java index 4554ea6c6..6ffda9da7 100644 --- a/src/java/org/apache/fop/pdf/PDFFilterList.java +++ b/src/java/org/apache/fop/pdf/PDFFilterList.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,8 @@ public class PDFFilterList { public static final String TIFF_FILTER = "tiff"; /** Key for the filter used for fonts */ public static final String FONT_FILTER = "font"; + /** Key for the filter used for metadata */ + public static final String METADATA_FILTER = "metadata"; private List filters = new java.util.ArrayList(); diff --git a/src/java/org/apache/fop/pdf/PDFInfo.java b/src/java/org/apache/fop/pdf/PDFInfo.java index ce926d4da..7bf346753 100644 --- a/src/java/org/apache/fop/pdf/PDFInfo.java +++ b/src/java/org/apache/fop/pdf/PDFInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2005 The Apache Software Foundation. + * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,11 @@ public class PDFInfo extends PDFObject { this.producer = producer; } + /** @return the creator of the document or null if not set */ + public String getCreator() { + return this.creator; + } + /** * set the creator string * @@ -76,6 +81,11 @@ public class PDFInfo extends PDFObject { this.title = t; } + /** @return the author of the document or null if not set */ + public String getAuthor() { + return this.author; + } + /** * set the author string * @@ -85,6 +95,11 @@ public class PDFInfo extends PDFObject { this.author = a; } + /** @return the subject of the document or null if not set */ + public String getSubject() { + return this.subject; + } + /** * set the subject string * @@ -94,6 +109,11 @@ public class PDFInfo extends PDFObject { this.subject = s; } + /** @return the keywords for the document or null if not set */ + public String getKeyword() { + return this.keywords; + } + /** * set the keywords string * diff --git a/src/java/org/apache/fop/pdf/PDFMetadata.java b/src/java/org/apache/fop/pdf/PDFMetadata.java new file mode 100644 index 000000000..d162ba032 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFMetadata.java @@ -0,0 +1,205 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.pdf; + +import java.io.IOException; +import java.io.OutputStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.ElementMapping; +import org.apache.fop.fo.extensions.xmp.XMPConstants; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Special PDFStream for Metadata. + * @since PDF 1.4 + */ +public class PDFMetadata extends PDFStream { + + private Document xmpMetadata; + private boolean readOnly = true; + + /** @see org.apache.fop.pdf.PDFObject#PDFObject() */ + public PDFMetadata(Document xmp, boolean readOnly) { + super(); + if (xmp == null) { + throw new NullPointerException( + "DOM Document representing the metadata must no be null"); + } + this.xmpMetadata = xmp; + this.readOnly = readOnly; + } + + /** @see org.apache.fop.pdf.AbstractPDFStream#setupFilterList() */ + protected void setupFilterList() { + if (!getFilterList().isInitialized()) { + getFilterList().addDefaultFilters( + getDocumentSafely().getFilterMap(), + PDFFilterList.METADATA_FILTER); + } + super.setupFilterList(); + } + + /** @see org.apache.fop.pdf.AbstractPDFStream#allowEncryption() */ + protected boolean allowEncryption() { + return false; //XMP metadata packet must be scannable by non PDF-compatible readers + } + + /** + * overload the base object method so we don't have to copy + * byte arrays around so much + * @see org.apache.fop.pdf.PDFObject#output(OutputStream) + */ + protected int output(java.io.OutputStream stream) + throws java.io.IOException { + int length = super.output(stream); + this.xmpMetadata = null; //Release DOM when it's not used anymore + return length; + } + + /** @see org.apache.fop.pdf.AbstractPDFStream#outputRawStreamData(java.io.OutputStream) */ + protected void outputRawStreamData(OutputStream out) throws IOException { + final String encoding = "UTF-8"; + out.write("\n" + .getBytes(encoding)); + try { + TransformerFactory tFactory = TransformerFactory.newInstance(); + Transformer transformer = tFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty(OutputKeys.INDENT, "no"); + DOMSource src = new DOMSource(this.xmpMetadata); + StreamResult res = new StreamResult(out); + transformer.transform(src, res); + } catch (TransformerConfigurationException e) { + throw new IOException("Error setting up Transformer for XMP stream serialization: " + + e.getMessage()); + } catch (TransformerException e) { + throw new IOException("Error while serializing XMP stream: " + + e.getMessage()); + } + if (readOnly) { + out.write("\n".getBytes(encoding)); + } else { + //Create padding string (40 * 101 characters is more or less the recommended 4KB) + StringBuffer sb = new StringBuffer(101); + sb.append('\n'); + for (int i = 0; i < 100; i++) { + sb.append(" "); + } + byte[] padding = sb.toString().getBytes(encoding); + for (int i = 0; i < 40; i++) { + out.write(padding); + } + out.write("\n".getBytes(encoding)); + } + } + + /** @see org.apache.fop.pdf.AbstractPDFStream#buildStreamDict(String) */ + protected String buildStreamDict(String lengthEntry) { + final String filterEntry = getFilterList().buildFilterDictEntries(); + final StringBuffer sb = new StringBuffer(128); + sb.append(getObjectID()); + sb.append("<< "); + sb.append("/Type /Metadata"); + sb.append("\n/Subtype /XML"); + sb.append("\n/Length " + lengthEntry); + sb.append("\n" + filterEntry); + sb.append("\n>>\n"); + return sb.toString(); + } + + /** + * Creates an XMP document based on the settings on the PDF Document. + * @param pdfDoc the PDF Document + * @return a DOM document representing the requested XMP metadata + */ + public static Document createXMPFromUserAgent(PDFDocument pdfDoc) { + DOMImplementation domImplementation = ElementMapping.getDefaultDOMImplementation(); + Document doc = domImplementation.createDocument( + XMPConstants.XMP_NAMESPACE, "x:xmpmeta", null); + Element rdf = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:RDF"); + doc.getDocumentElement().appendChild(rdf); + + Element desc, el; + PDFInfo info = pdfDoc.getInfo(); + DateFormat pseudoISO8601DateFormat = new SimpleDateFormat( + "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS"); + + //Set creation date if not available, yet + if (info.getCreationDate() == null) { + Date d = new Date(); + info.setCreationDate(d); + } + + //Dublin Core + desc = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:Description"); + desc.setAttribute("about", ""); + rdf.appendChild(desc); + if (info.getAuthor() != null) { + el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:creator"); + desc.appendChild(el); + el.appendChild(doc.createTextNode(info.getAuthor())); + } + if (info.getTitle() != null) { + el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:title"); + desc.appendChild(el); + el.appendChild(doc.createTextNode(info.getTitle())); + } + if (info.getSubject() != null) { + el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:subject"); + desc.appendChild(el); + el.appendChild(doc.createTextNode(info.getSubject())); + } + el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:date"); + desc.appendChild(el); + el.appendChild(doc.createTextNode(pseudoISO8601DateFormat.format(info.getCreationDate()))); + + //XMP Basic Schema + desc = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:Description"); + desc.setAttribute("about", ""); + rdf.appendChild(desc); + el = doc.createElementNS(XMPConstants.XMP_BASIC_NAMESPACE, "xmp:createDate"); + desc.appendChild(el); + el.appendChild(doc.createTextNode(pseudoISO8601DateFormat.format(info.getCreationDate()))); + if (info.getCreator() != null) { + el = doc.createElementNS(XMPConstants.XMP_BASIC_NAMESPACE, "xmp:creatorTool"); + desc.appendChild(el); + el.appendChild(doc.createTextNode(info.getCreator())); + } + + + return doc; + } + + +} diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java index 2ac00cdf1..3d483f587 100644 --- a/src/java/org/apache/fop/pdf/PDFRoot.java +++ b/src/java/org/apache/fop/pdf/PDFRoot.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. + * Copyright 1999-2004,2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,9 @@ public class PDFRoot extends PDFObject { */ private PDFOutline outline; + /** Optional Metadata object */ + private PDFMetadata metadata; + private int pageMode = PAGEMODE_USENONE; /** @@ -115,6 +118,23 @@ public class PDFRoot extends PDFObject { public PDFOutline getRootOutline() { return outline; } + + /** + * Set the optional Metadata object. + * @param meta the Metadata object + * @since PDF 1.4 + */ + public void setMetadata(PDFMetadata meta) { + this.metadata = meta; + } + + /** + * @return the Metadata object if set, null otherwise. + * @since PDF 1.4 + */ + public PDFMetadata getMetadata() { + return this.metadata; + } /** * @see org.apache.fop.pdf.PDFObject#toPDFString() @@ -144,6 +164,10 @@ public class PDFRoot extends PDFObject { break; } } + if (getMetadata() != null + && getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) { + p.append("/Metadata " + getMetadata().referencePDF() + "\n"); + } p.append(">>\nendobj\n"); return p.toString(); } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index ab2ae245b..d0f65d16f 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -41,7 +41,7 @@ import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.MimeConstants; import org.apache.fop.area.CTM; import org.apache.fop.area.LineArea; -import org.apache.fop.area.Page; +import org.apache.fop.area.OffDocumentExtensionAttachment; import org.apache.fop.area.PageViewport; import org.apache.fop.area.RegionViewport; import org.apache.fop.area.Trait; @@ -69,6 +69,7 @@ import org.apache.fop.pdf.PDFEncryptionManager; import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFInfo; import org.apache.fop.pdf.PDFLink; +import org.apache.fop.pdf.PDFMetadata; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFOutline; import org.apache.fop.pdf.PDFPage; @@ -82,6 +83,8 @@ import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; import org.apache.fop.fo.Constants; +import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.fo.extensions.xmp.XMPMetadata; /* todo: @@ -296,6 +299,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { // render Bookmark-Tree if (odi instanceof BookmarkData) { renderBookmarkTree((BookmarkData) odi); + } else if (odi instanceof OffDocumentExtensionAttachment) { + ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment(); + if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) { + renderXMPMetadata((XMPMetadata)attachment); + } } } @@ -340,6 +348,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } } + private void renderXMPMetadata(XMPMetadata metadata) { + PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( + metadata.getDocument(), metadata.isReadOnly()); + pdfDoc.getRoot().setMetadata(pdfMetadata); + } + /** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */ public Graphics2DAdapter getGraphics2DAdapter() { return new PDFGraphics2DAdapter(this); @@ -400,6 +414,14 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { info.setTitle(str); } } + if (pdfDoc.getRoot().getMetadata() == null) { + //If at this time no XMP metadata for the overall document has been set, create it + //from the PDFInfo object. + Document xmp = PDFMetadata.createXMPFromUserAgent(pdfDoc); + PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( + xmp, true); + pdfDoc.getRoot().setMetadata(pdfMetadata); + } } /** diff --git a/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java b/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java index e22951236..401202dc0 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java @@ -20,8 +20,8 @@ package org.apache.fop.render.ps.extensions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.area.AreaTreeParser; import org.apache.fop.util.ContentHandlerFactory; +import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -39,6 +39,7 @@ public class PSExtensionHandler extends DefaultHandler private Attributes lastAttributes; private PSSetupCode returnedObject; + private ObjectBuiltListener listener; /** @see org.xml.sax.helpers.DefaultHandler */ public void startElement(String uri, String localName, String qName, Attributes attributes) @@ -80,9 +81,27 @@ public class PSExtensionHandler extends DefaultHandler content.append(ch, start, length); } - /** @see org.apache.fop.area.AreaTreeParser.ObjectSource#getObject() */ + /** + * @see org.xml.sax.helpers.DefaultHandler#endDocument() + */ + public void endDocument() throws SAXException { + if (listener != null) { + listener.notifyObjectBuilt(getObject()); + } + } + + /** + * @see org.apache.fop.util.ContentHandlerFactory.ObjectSource#getObject() + */ public Object getObject() { return returnedObject; } + /** + * @see org.apache.fop.util.ContentHandlerFactory.ObjectSource + */ + public void setObjectBuiltListener(ObjectBuiltListener listener) { + this.listener = listener; + } + } diff --git a/src/java/org/apache/fop/render/xml/XMLXMLHandler.java b/src/java/org/apache/fop/render/xml/XMLXMLHandler.java index eb1f4946b..da44bc725 100644 --- a/src/java/org/apache/fop/render/xml/XMLXMLHandler.java +++ b/src/java/org/apache/fop/render/xml/XMLXMLHandler.java @@ -18,20 +18,12 @@ package org.apache.fop.render.xml; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.fop.render.Renderer; import org.apache.fop.render.XMLHandler; import org.apache.fop.render.RendererContext; +import org.apache.fop.util.DOM2SAX; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Attr; -import org.xml.sax.SAXException; import org.xml.sax.ContentHandler; -import org.xml.sax.ext.LexicalHandler; -import org.xml.sax.helpers.AttributesImpl; /** * XML handler for the XML renderer. @@ -41,104 +33,12 @@ public class XMLXMLHandler implements XMLHandler { /** Key for getting the TransformerHandler from the RendererContext */ public static final String HANDLER = "handler"; - /** Logging instance */ - private static Log log = LogFactory.getLog(XMLXMLHandler.class); - - private AttributesImpl atts = new AttributesImpl(); - /** @see org.apache.fop.render.XMLHandler */ public void handleXML(RendererContext context, org.w3c.dom.Document doc, String ns) throws Exception { ContentHandler handler = (ContentHandler) context.getProperty(HANDLER); - writeDocument(doc, handler); - } - - /** - * Writes the given document using the given TransformerHandler. - * @param doc DOM document - * @param handler TransformerHandler to write to - * @throws SAXException In case of a problem while writing XML - */ - public void writeDocument(Document doc, - ContentHandler handler) throws SAXException { - for (Node n = doc.getFirstChild(); n != null; - n = n.getNextSibling()) { - writeNode(n, handler); - } - } - - /** - * Writes a node using the given writer. - * @param node node to serialize - * @param handler ContentHandler to write to - * @throws SAXException In case of a problem while writing XML - */ - public void writeNode(Node node, ContentHandler handler) throws SAXException { - char[] ca; - switch (node.getNodeType()) { - case Node.ELEMENT_NODE: - atts.clear(); - - if (node.hasAttributes()) { - NamedNodeMap attr = node.getAttributes(); - int len = attr.getLength(); - for (int i = 0; i < len; i++) { - Attr a = (Attr) attr.item(i); - atts.addAttribute("", a.getNodeName(), a.getNodeName(), - "CDATA", a.getNodeValue()); - } - } - handler.startElement(node.getNamespaceURI(), - node.getLocalName(), node.getLocalName(), atts); - - Node c = node.getFirstChild(); - if (c != null) { - for (; c != null; c = c.getNextSibling()) { - writeNode(c, handler); - } - } - handler.endElement(node.getNamespaceURI(), node.getNodeName(), node.getNodeName()); - break; - case Node.TEXT_NODE: - ca = node.getNodeValue().toCharArray(); - handler.characters(ca, 0, ca.length); - break; - case Node.CDATA_SECTION_NODE: - ca = node.getNodeValue().toCharArray(); - if (handler instanceof LexicalHandler) { - LexicalHandler lh = (LexicalHandler)handler; - lh.startCDATA(); - handler.characters(ca, 0, ca.length); - lh.endCDATA(); - } else { - handler.characters(ca, 0, ca.length); - } - break; - case Node.ENTITY_REFERENCE_NODE: - log.warn("Ignoring ENTITY_REFERENCE_NODE. NYI"); - /* - writer.write("&"); - writer.write(); - writer.write(";"); - */ - break; - case Node.PROCESSING_INSTRUCTION_NODE: - handler.processingInstruction(node.getNodeName(), node.getNodeValue()); - break; - case Node.COMMENT_NODE: - ca = node.getNodeValue().toCharArray(); - if (handler instanceof LexicalHandler) { - LexicalHandler lh = (LexicalHandler)handler; - lh.comment(ca, 0, ca.length); - } - break; - case Node.DOCUMENT_TYPE_NODE: - break; - default: - throw new IllegalArgumentException("Unexpected node type (" - + node.getNodeType() + ")"); - } + DOM2SAX.writeDocument(doc, handler); } /** @see org.apache.fop.render.XMLHandler#supportsRenderer(org.apache.fop.render.Renderer) */ diff --git a/src/java/org/apache/fop/util/ContentHandlerFactory.java b/src/java/org/apache/fop/util/ContentHandlerFactory.java index da615cc54..a17d7a60f 100644 --- a/src/java/org/apache/fop/util/ContentHandlerFactory.java +++ b/src/java/org/apache/fop/util/ContentHandlerFactory.java @@ -18,7 +18,10 @@ package org.apache.fop.util; +import java.util.EventListener; + import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; /** * Factory interface implemented by classes that can instantiate ContentHandler subclasses which @@ -33,8 +36,9 @@ public interface ContentHandlerFactory { /** * @return a new ContentHandler to handle a SAX stream + * @throws SAXException if there's an error while preparing the ContentHandler */ - ContentHandler createContentHandler(); + ContentHandler createContentHandler() throws SAXException; /** * Interface that ContentHandler implementations that parse Java objects from XML can implement @@ -46,6 +50,25 @@ public interface ContentHandlerFactory { * @return the object parsed from the SAX stream (call valid after parsing) */ Object getObject(); + + /** + * Set a listener which gets notified when the object is fully built. + * @param listener the listener which gets notified + */ + void setObjectBuiltListener(ObjectBuiltListener listener); + } + + /** + * EventListener interface for objects which want to get notified when ContentHandler + * implementing the ObjectSource interface has finished parsing. + */ + public interface ObjectBuiltListener extends EventListener { + + /** + * Notifies the listener when the object is fully built. + * @param obj the newly built object + */ + void notifyObjectBuilt(Object obj); } diff --git a/src/java/org/apache/fop/util/DOM2SAX.java b/src/java/org/apache/fop/util/DOM2SAX.java new file mode 100644 index 000000000..a26e71429 --- /dev/null +++ b/src/java/org/apache/fop/util/DOM2SAX.java @@ -0,0 +1,133 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; + +/** + * Helper class that produces a SAX stream from a DOM Document. + */ +public class DOM2SAX { + + /** Logging instance */ + private static Log log = LogFactory.getLog(DOM2SAX.class); + + /** + * Writes the given document using the given TransformerHandler. + * @param doc DOM document + * @param handler TransformerHandler to write to + * @throws SAXException In case of a problem while writing XML + */ + public static void writeDocument(Document doc, + ContentHandler handler) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + for (Node n = doc.getFirstChild(); n != null; + n = n.getNextSibling()) { + writeNode(n, handler, atts); + } + } + + /** + * Writes a node using the given writer. + * @param node node to serialize + * @param handler ContentHandler to write to + * @param atts AttributesImpl instance that is reused during SAX event generation + * @throws SAXException In case of a problem while writing XML + */ + private static void writeNode(Node node, ContentHandler handler, AttributesImpl atts) + throws SAXException { + char[] ca; + switch (node.getNodeType()) { + case Node.ELEMENT_NODE: + atts.clear(); + + if (node.hasAttributes()) { + NamedNodeMap attr = node.getAttributes(); + int len = attr.getLength(); + for (int i = 0; i < len; i++) { + Attr a = (Attr) attr.item(i); + atts.addAttribute("", a.getNodeName(), a.getNodeName(), + "CDATA", a.getNodeValue()); + } + } + handler.startElement(node.getNamespaceURI(), + node.getLocalName(), node.getLocalName(), atts); + + Node c = node.getFirstChild(); + if (c != null) { + for (; c != null; c = c.getNextSibling()) { + writeNode(c, handler, atts); + } + } + handler.endElement(node.getNamespaceURI(), node.getNodeName(), node.getNodeName()); + break; + case Node.TEXT_NODE: + ca = node.getNodeValue().toCharArray(); + handler.characters(ca, 0, ca.length); + break; + case Node.CDATA_SECTION_NODE: + ca = node.getNodeValue().toCharArray(); + if (handler instanceof LexicalHandler) { + LexicalHandler lh = (LexicalHandler)handler; + lh.startCDATA(); + handler.characters(ca, 0, ca.length); + lh.endCDATA(); + } else { + handler.characters(ca, 0, ca.length); + } + break; + case Node.ENTITY_REFERENCE_NODE: + log.warn("Ignoring ENTITY_REFERENCE_NODE. NYI"); + /* + writer.write("&"); + writer.write(); + writer.write(";"); + */ + break; + case Node.PROCESSING_INSTRUCTION_NODE: + handler.processingInstruction(node.getNodeName(), node.getNodeValue()); + break; + case Node.COMMENT_NODE: + ca = node.getNodeValue().toCharArray(); + if (handler instanceof LexicalHandler) { + LexicalHandler lh = (LexicalHandler)handler; + lh.comment(ca, 0, ca.length); + } + break; + case Node.DOCUMENT_TYPE_NODE: + break; + default: + throw new IllegalArgumentException("Unexpected node type (" + + node.getNodeType() + ")"); + } + } + + +} diff --git a/src/java/org/apache/fop/util/DOMBuilderContentHandlerFactory.java b/src/java/org/apache/fop/util/DOMBuilderContentHandlerFactory.java new file mode 100644 index 000000000..5b5dd3b48 --- /dev/null +++ b/src/java/org/apache/fop/util/DOMBuilderContentHandlerFactory.java @@ -0,0 +1,138 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.util; + +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +/** + * ContentHandlerFactory which constructs ContentHandlers that build DOM Documents. + */ +public class DOMBuilderContentHandlerFactory implements ContentHandlerFactory { + + private static SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + private String namespaceURI; + private DOMImplementation domImplementation; + + /** + * Main Constructor + * @param namespaceURI the main namespace URI for the DOM to be parsed + * @param domImplementation the DOMImplementation to use for build the DOM + */ + public DOMBuilderContentHandlerFactory(String namespaceURI, + DOMImplementation domImplementation) { + this.namespaceURI = namespaceURI; + this.domImplementation = domImplementation; + } + + /** @see org.apache.fop.util.ContentHandlerFactory#getSupportedNamespaces() */ + public String[] getSupportedNamespaces() { + return new String[] {namespaceURI}; + } + + /** @see org.apache.fop.util.ContentHandlerFactory#createContentHandler() */ + public ContentHandler createContentHandler() throws SAXException { + return new Handler(); + } + + private class Handler extends DelegatingContentHandler + implements ContentHandlerFactory.ObjectSource { + + private Document doc; + private ObjectBuiltListener obListener; + + public Handler() throws SAXException { + super(); + } + + public Document getDocument() { + return this.doc; + } + + /** + * @see org.apache.fop.util.ContentHandlerFactory.ObjectSource#getObject() + */ + public Object getObject() { + return getDocument(); + } + + /** + * @see org.apache.fop.util.ContentHandlerFactory.ObjectSource + */ + public void setObjectBuiltListener(ObjectBuiltListener listener) { + this.obListener = listener; + } + + /** + * @see org.apache.fop.util.DelegatingContentHandler#startDocument() + */ + public void startDocument() throws SAXException { + //Suppress startDocument() call if doc has not been set, yet. It will be done later. + if (doc != null) { + super.startDocument(); + } + } + + /** + * @see org.apache.fop.util.DelegatingContentHandler + */ + public void startElement(String uri, String localName, String qName, Attributes atts) + throws SAXException { + if (doc == null) { + TransformerHandler handler; + try { + handler = tFactory.newTransformerHandler(); + } catch (TransformerConfigurationException e) { + throw new SAXException("Error creating a new TransformerHandler", e); + } + doc = domImplementation.createDocument(namespaceURI, qName, null); + //It's easier to work with an empty document, so remove the root element + doc.removeChild(doc.getDocumentElement()); + handler.setResult(new DOMResult(doc)); + setDelegateContentHandler(handler); + setDelegateLexicalHandler(handler); + setDelegateDTDHandler(handler); + handler.startDocument(); + } + super.startElement(uri, localName, qName, atts); + } + + /** + * @see org.apache.fop.util.DelegatingContentHandler#endDocument() + */ + public void endDocument() throws SAXException { + super.endDocument(); + if (obListener != null) { + obListener.notifyObjectBuilt(getObject()); + } + } + + } + +} diff --git a/src/java/org/apache/fop/util/DelegatingContentHandler.java b/src/java/org/apache/fop/util/DelegatingContentHandler.java new file mode 100644 index 000000000..86cfb1e41 --- /dev/null +++ b/src/java/org/apache/fop/util/DelegatingContentHandler.java @@ -0,0 +1,314 @@ +/* + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.util; + +import java.io.IOException; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.ext.LexicalHandler; + +/** + * SAX 2 Event Handler which simply delegates all calls to another ContentHandler. Subclasses can + * do additional processing. This class is the passive counterpart to XMLFilterImpl. + *

+ * The ContentHandler is the only instance that is required. All others (DTDHandler, + * EntityResolver, LexicalHandler and ErrorHandler) may be ignored. + * + */ +public class DelegatingContentHandler + implements EntityResolver, DTDHandler, ContentHandler, LexicalHandler, ErrorHandler { + + private ContentHandler delegate; + private EntityResolver entityResolver; + private DTDHandler dtdHandler; + private LexicalHandler lexicalHandler; + private ErrorHandler errorHandler; + + /** + * Main constructor. + */ + public DelegatingContentHandler() { + //nop + } + + /** + * @return the delegate that all ContentHandler events are forwarded to + */ + public ContentHandler getDelegateContentHandler() { + return this.delegate; + } + + /** + * Sets the delegate ContentHandler that all events are forwarded to. + * @param handler the delegate instance + */ + public void setDelegateContentHandler(ContentHandler handler) { + this.delegate = handler; + } + + /** + * Sets the delegate EntityResolver. + * @param resolver the delegate instance + */ + public void setDelegateEntityResolver(EntityResolver resolver) { + this.entityResolver = resolver; + } + + /** + * Sets the delegate DTDHandler. + * @param handler the delegate instance + */ + public void setDelegateDTDHandler(DTDHandler handler) { + this.dtdHandler = handler; + } + + /** + * Sets the delegate LexicalHandler. + * @param handler the delegate instance + */ + public void setDelegateLexicalHandler(LexicalHandler handler) { + this.lexicalHandler = handler; + } + + /** + * Sets the delegate ErrorHandler. + * @param handler the delegate instance + */ + public void setDelegateErrorHandler(ErrorHandler handler) { + this.errorHandler = handler; + } + + // ==== EntityResolver + + /** + * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String) + */ + public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { + if (entityResolver != null) { + return entityResolver.resolveEntity(publicId, systemId); + } else { + return null; + } + } + + // ==== DTDHandler + + /** + * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String) + */ + public void notationDecl(String name, String publicId, String systemId) throws SAXException { + if (dtdHandler != null) { + dtdHandler.notationDecl(name, publicId, systemId); + } + } + + /** + * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + public void unparsedEntityDecl(String name, String publicId, String systemId, + String notationName) throws SAXException { + if (dtdHandler != null) { + dtdHandler.unparsedEntityDecl(name, publicId, systemId, notationName); + } + } + + // ==== ContentHandler + + /** + * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator) + */ + public void setDocumentLocator(Locator locator) { + delegate.setDocumentLocator(locator); + } + + /** + * @see org.xml.sax.ContentHandler#startDocument() + */ + public void startDocument() throws SAXException { + delegate.startDocument(); + } + + /** + * @see org.xml.sax.ContentHandler#endDocument() + */ + public void endDocument() throws SAXException { + delegate.endDocument(); + } + + /** + * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String) + */ + public void startPrefixMapping(String prefix, String uri) throws SAXException { + delegate.startPrefixMapping(prefix, uri); + } + + /** + * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String) + */ + public void endPrefixMapping(String prefix) throws SAXException { + delegate.endPrefixMapping(prefix); + } + + /** + * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) + */ + public void startElement(String uri, String localName, String qName, + Attributes atts) throws SAXException { + delegate.startElement(uri, localName, qName, atts); + } + + /** + * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String) + */ + public void endElement(String uri, String localName, String qName) throws SAXException { + delegate.endElement(uri, localName, qName); + } + + /** + * @see org.xml.sax.ContentHandler#characters(char[], int, int) + */ + public void characters(char[] ch, int start, int length) throws SAXException { + delegate.characters(ch, start, length); + } + + /** + * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int) + */ + public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { + delegate.ignorableWhitespace(ch, start, length); + } + + /** + * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String) + */ + public void processingInstruction(String target, String data) throws SAXException { + delegate.processingInstruction(target, data); + } + + /** + * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String) + */ + public void skippedEntity(String name) throws SAXException { + delegate.skippedEntity(name); + } + + // ==== LexicalHandler + + /** + * @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String, java.lang.String, java.lang.String) + */ + public void startDTD(String name, String publicId, String systemId) throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startDTD(name, publicId, systemId); + } + + } + + /** + * @see org.xml.sax.ext.LexicalHandler#endDTD() + */ + public void endDTD() throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endDTD(); + } + } + + /** + * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String) + */ + public void startEntity(String name) throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startEntity(name); + } + } + + /** + * @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String) + */ + public void endEntity(String name) throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endEntity(name); + } + } + + /** + * @see org.xml.sax.ext.LexicalHandler#startCDATA() + */ + public void startCDATA() throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startCDATA(); + } + } + + /** + * @see org.xml.sax.ext.LexicalHandler#endCDATA() + */ + public void endCDATA() throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endCDATA(); + } + } + + /** + * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int) + */ + public void comment(char[] ch, int start, int length) throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.comment(ch, start, length); + } + } + + // ==== ErrorHandler + + /** + * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException) + */ + public void warning(SAXParseException exception) throws SAXException { + if (errorHandler != null) { + errorHandler.warning(exception); + } + } + + /** + * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException) + */ + public void error(SAXParseException exception) throws SAXException { + if (errorHandler != null) { + errorHandler.error(exception); + } + } + + /** + * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException) + */ + public void fatalError(SAXParseException exception) throws SAXException { + if (errorHandler != null) { + errorHandler.fatalError(exception); + } + } + +} diff --git a/status.xml b/status.xml index fa085c710..561f78414 100644 --- a/status.xml +++ b/status.xml @@ -27,6 +27,9 @@ + + Initial support for XMP metadata (PDF 1.4) under fo:declarations. + Added support for the background-position shorthand property.