]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-3127: Allow XMP at PDF page level
authorSimon Steiner <ssteiner@apache.org>
Tue, 4 Apr 2023 10:07:14 +0000 (11:07 +0100)
committerSimon Steiner <ssteiner@apache.org>
Tue, 4 Apr 2023 10:07:14 +0000 (11:07 +0100)
fop-core/src/main/java/org/apache/fop/fo/extensions/xmp/AbstractMetadataElement.java
fop-core/src/main/java/org/apache/fop/pdf/PDFPage.java
fop-core/src/main/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
fop-core/src/main/java/org/apache/fop/render/pdf/extensions/PDFDictionaryAttachment.java
fop-core/src/main/java/org/apache/fop/render/pdf/extensions/PDFDictionaryElement.java
fop-core/src/main/java/org/apache/fop/render/pdf/extensions/PDFDictionaryExtension.java
fop-core/src/main/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java
fop-core/src/test/java/org/apache/fop/pdf/PDFPageXMPTestCase.java [new file with mode: 0644]
fop-core/src/test/resources/org/apache/fop/pdf/PDFPageXMP.fo [new file with mode: 0644]
fop/lib/xmlgraphics-commons-svn-trunk.jar
fop/test/layoutengine/standard-testcases/pdf-page-xmp.xml [new file with mode: 0644]

index 8004be01b5e9c4cf2e396e062887172b440aed12..c2af237c0a8e7d774ce5b2218e4927b3d76f7c88 100644 (file)
@@ -24,6 +24,7 @@ import org.apache.xmlgraphics.xmp.Metadata;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.render.pdf.extensions.PDFPageElement;
 import org.apache.fop.util.ContentHandlerFactory;
 import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener;
 
@@ -51,7 +52,7 @@ public abstract class AbstractMetadataElement extends FONode implements ObjectBu
 
     /** {@inheritDoc} */
     public ExtensionAttachment getExtensionAttachment() {
-        if (parent instanceof FObj) {
+        if (parent instanceof FObj || parent instanceof PDFPageElement) {
             if (attachment == null) {
                 attachment = new XMPMetadata();
             }
index 156bf0354e1d54ec463bf94e045bcedc4299da4f..8950d373bff857ecb1c29794cdfd6b71a1d0606f 100644 (file)
@@ -183,4 +183,7 @@ public class PDFPage extends PDFResourceContext {
         put("Tabs", value);
     }
 
+    public void setMetadata(PDFMetadata meta) {
+        put("Metadata", meta.makeReference());
+    }
 }
index 1f383a29e2173aef293dba7e159858afa1268946..f2cf5beff8e8ffb4fa7b2f969eb712725b11b43d 100644 (file)
@@ -47,6 +47,7 @@ import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
 import org.apache.fop.accessibility.Accessibility;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.apps.io.InternalResourceResolver;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.fo.extensions.xmp.XMPMetadata;
 import org.apache.fop.pdf.PDFAMode;
 import org.apache.fop.pdf.PDFArray;
@@ -467,6 +468,7 @@ class PDFRenderingUtil {
             assert extension instanceof PDFPageExtension;
             if (((PDFPageExtension) extension).matchesPageNumber(currentPage.getPageIndex() + 1)) {
                 augmentDictionary(currentPage, extension);
+                renderExtension(currentPage, extension.getExtension());
             }
         } else if (type == PDFDictionaryType.Info) {
             PDFInfo info = pdfDoc.getInfo();
@@ -490,6 +492,15 @@ class PDFRenderingUtil {
         }
     }
 
+    private void renderExtension(PDFPage currentPage, ExtensionAttachment extension) {
+        if (extension instanceof XMPMetadata) {
+            XMPMetadata metadata = (XMPMetadata) extension;
+            Metadata docXMP = metadata.getMetadata();
+            PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(docXMP, metadata.isReadOnly());
+            currentPage.setMetadata(pdfMetadata);
+        }
+    }
+
     private PDFDictionary augmentDictionary(PDFDictionary dictionary, PDFDictionaryExtension extension) {
         for (PDFCollectionEntryExtension entry : extension.getEntries()) {
             if (entry instanceof PDFDictionaryExtension) {
index e275bfbdc0f878ab6bed158079381da86b3308a0..9ea179337877b654851bcff80600328e7f5d1f95 100644 (file)
@@ -23,6 +23,8 @@ import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.AttributesImpl;
 
+import org.apache.xmlgraphics.util.XMLizable;
+
 import org.apache.fop.render.intermediate.IFContext;
 import org.apache.fop.util.GenerationHelperContentHandler;
 
@@ -71,6 +73,9 @@ public class PDFDictionaryAttachment extends PDFExtensionAttachment {
         for (PDFCollectionEntryExtension entry : dictionary.getEntries()) {
             toSAX(handler, entry);
         }
+        if (extension.getExtension() != null) {
+            ((XMLizable) extension.getExtension()).toSAX(handler);
+        }
         handler.endElement(CATEGORY, ln, qn);
     }
 
index d7b155e23d3ac7f765272de95751777041663f75..50fd319af3f0e6bd25b5f2f6c0a9757992fd13d2 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.fo.extensions.xmp.XMPMetaElement;
 
 // CSOFF: LineLengthCheck
 
@@ -132,6 +133,8 @@ public class PDFDictionaryElement extends PDFCollectionEntryElement {
         } else if (child instanceof PDFCollectionEntryElement) {
             PDFCollectionEntryExtension entry = ((PDFCollectionEntryElement) child).getExtension();
             extension.addEntry(entry);
+        } else if (child instanceof XMPMetaElement) {
+            extension.setExtension(child.getExtensionAttachment());
         }
     }
 
index d1367413ebf2cbf385d448bc2b32dcc2f397d1dd..8c96ef110965e14e4c7064e45c9d07173e130c96 100644 (file)
@@ -22,6 +22,8 @@ package org.apache.fop.render.pdf.extensions;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+
 // CSOFF: LineLengthCheck
 
 public class PDFDictionaryExtension extends PDFCollectionExtension {
@@ -34,6 +36,7 @@ public class PDFDictionaryExtension extends PDFCollectionExtension {
     private PDFDictionaryType dictionaryType;
     private Map<String, String> properties;
     private List<PDFCollectionEntryExtension> entries;
+    private ExtensionAttachment extension;
 
     PDFDictionaryExtension() {
         this(PDFDictionaryType.Dictionary);
@@ -81,6 +84,14 @@ public class PDFDictionaryExtension extends PDFCollectionExtension {
         return entries;
     }
 
+    public void setExtension(ExtensionAttachment entry) {
+        extension = entry;
+    }
+
+    public ExtensionAttachment getExtension() {
+        return extension;
+    }
+
     public PDFCollectionEntryExtension findEntry(String key) {
         for (PDFCollectionEntryExtension entry : entries) {
             String entryKey = entry.getKey();
index 9579acc848bd25af126fb068215f7eb41be050e2..3aa8b47448ff19ce6d0e2907485c6a7a4c33ecf0 100644 (file)
@@ -29,6 +29,11 @@ import org.xml.sax.helpers.DefaultHandler;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.xmlgraphics.xmp.XMPConstants;
+import org.apache.xmlgraphics.xmp.XMPHandler;
+
+import org.apache.fop.fo.extensions.xmp.XMPContentHandlerFactory;
+import org.apache.fop.fo.extensions.xmp.XMPMetadata;
 import org.apache.fop.util.ContentHandlerFactory;
 import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener;
 
@@ -50,6 +55,7 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle
     private Stack<PDFCollectionExtension> collections = new Stack<PDFCollectionExtension>();
     private boolean captureContent;
     private StringBuffer characters;
+    private XMPHandler xmpHandler;
 
     @Override
     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
@@ -141,6 +147,11 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle
             } else {
                 throw new SAXException("Unhandled element " + localName + " in namespace: " + uri);
             }
+        } else if (XMPConstants.XMP_NAMESPACE.equals(uri) || xmpHandler != null) {
+            if (xmpHandler == null) {
+                xmpHandler = (XMPHandler) new XMPContentHandlerFactory().createContentHandler();
+            }
+            xmpHandler.startElement(uri, localName, qName, attributes);
         } else {
             log.warn("Unhandled element " + localName + " in namespace: " + uri);
         }
@@ -154,11 +165,15 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle
             }
             characters.append(data, start, length);
         }
+        if (xmpHandler != null) {
+            xmpHandler.characters(data, start, length);
+        }
     }
 
     @Override
     public void endElement(String uri, String localName, String qName) throws SAXException {
         if (PDFExtensionAttachment.CATEGORY.equals(uri)) {
+            setExtension();
             if (PDFEmbeddedFileAttachment.ELEMENT.equals(localName)) {
                 String name = lastAttributes.getValue("filename");
                 String src = lastAttributes.getValue("src");
@@ -209,9 +224,21 @@ public class PDFExtensionHandler extends DefaultHandler implements ContentHandle
                 }
             }
         }
+        if (xmpHandler != null) {
+            xmpHandler.endElement(uri, localName, qName);
+        }
         captureContent = false;
     }
 
+    private void setExtension() {
+        if (xmpHandler != null) {
+            PDFPageExtension pdfPageExtension = (PDFPageExtension) collections.peek();
+            XMPMetadata wrapper = new XMPMetadata(xmpHandler.getMetadata());
+            pdfPageExtension.setExtension(wrapper);
+            xmpHandler = null;
+        }
+    }
+
     @Override
     public void endDocument() throws SAXException {
         if (listener != null) {
diff --git a/fop-core/src/test/java/org/apache/fop/pdf/PDFPageXMPTestCase.java b/fop-core/src/test/java/org/apache/fop/pdf/PDFPageXMPTestCase.java
new file mode 100644 (file)
index 0000000..870e859
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.fop.pdf;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.Fop;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.render.intermediate.IFContext;
+import org.apache.fop.render.intermediate.IFDocumentHandler;
+import org.apache.fop.render.intermediate.IFParser;
+import org.apache.fop.render.intermediate.IFSerializer;
+import org.apache.fop.render.intermediate.IFUtil;
+
+public class PDFPageXMPTestCase {
+    private static final String XMP = "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n"
+        + "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
+        + "<rdf:Description xmlns:abc=\"http://www.abc.de/abc/\" abc:def=\"val\" rdf:about=\"\"/>\n"
+        + "<rdf:Description xmlns:pdfaExtension=\"http://www.aiim.org/pdfa/ns/extension/\" rdf:about=\"\">\n"
+        + "<pdfaExtension:schemas>\n"
+        + "<rdf:Bag>\n"
+        + "<rdf:li rdf:parseType=\"Resource\">\n"
+        + "<pdfaSchema:property xmlns:pdfaSchema=\"http://www.aiim.org/pdfa/ns/schema#\">\n"
+        + "<rdf:Seq>\n"
+        + "<rdf:li rdf:parseType=\"Resource\">\n"
+        + "<pdfaProperty:name xmlns:pdfaProperty=\"http://www.aiim.org/pdfa/ns/property#\">split</pdfaProperty:name>\n"
+        + "</rdf:li>\n"
+        + "</rdf:Seq>\n"
+        + "</pdfaSchema:property>\n"
+        + "</rdf:li>\n"
+        + "</rdf:Bag>\n"
+        + "</pdfaExtension:schemas>\n"
+        + "</rdf:Description>\n"
+        + "</rdf:RDF>\n"
+        + "</x:xmpmeta>";
+
+    @Test
+    public void textFO() throws Exception {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        foToOutput(out, MimeConstants.MIME_PDF);
+        Assert.assertTrue(out.toString().replace("\r", "").contains(XMP));
+    }
+
+    @Test
+    public void textIF() throws Exception {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        foToOutput(out, MimeConstants.MIME_FOP_IF);
+        out = iFToPDF(new ByteArrayInputStream(out.toByteArray()));
+        Assert.assertTrue(out.toString().replace("\r", "").contains(XMP));
+    }
+
+    private ByteArrayOutputStream iFToPDF(InputStream is) throws Exception {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        FOUserAgent userAgent = getFopFactory().newFOUserAgent();
+        Transformer transformer = TransformerFactory.newInstance().newTransformer();
+        Source src = new StreamSource(is);
+        IFDocumentHandler documentHandler
+                = userAgent.getRendererFactory().createDocumentHandler(userAgent, MimeConstants.MIME_PDF);
+        documentHandler.setResult(new StreamResult(out));
+        IFUtil.setupFonts(documentHandler);
+        IFParser parser = new IFParser();
+        Result res = new SAXResult(parser.getContentHandler(documentHandler, userAgent));
+        transformer.transform(src, res);
+        return out;
+    }
+
+    private void foToOutput(ByteArrayOutputStream out, String mimeFopIf) throws Exception {
+        FopFactory fopFactory = getFopFactory();
+        FOUserAgent userAgent = fopFactory.newFOUserAgent();
+        if (mimeFopIf.equals(MimeConstants.MIME_FOP_IF)) {
+            IFSerializer serializer = new IFSerializer(new IFContext(userAgent));
+            IFDocumentHandler targetHandler
+                    = userAgent.getRendererFactory().createDocumentHandler(userAgent, MimeConstants.MIME_PDF);
+            serializer.mimicDocumentHandler(targetHandler);
+            userAgent.setDocumentHandlerOverride(serializer);
+        }
+        Fop fop = fopFactory.newFop(mimeFopIf, userAgent, out);
+        Transformer transformer = TransformerFactory.newInstance().newTransformer();
+        Source src = new StreamSource(PDFPageXMPTestCase.class.getResource("PDFPageXMP.fo").openStream());
+        Result res = new SAXResult(fop.getDefaultHandler());
+        transformer.transform(src, res);
+    }
+
+    private FopFactory getFopFactory() {
+        return FopFactory.newInstance(new File(".").toURI());
+    }
+}
diff --git a/fop-core/src/test/resources/org/apache/fop/pdf/PDFPageXMP.fo b/fop-core/src/test/resources/org/apache/fop/pdf/PDFPageXMP.fo
new file mode 100644 (file)
index 0000000..6261862
--- /dev/null
@@ -0,0 +1,38 @@
+<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:pdf="http://xmlgraphics.apache.org/fop/extensions/pdf">
+  <fo:layout-master-set>
+    <fo:simple-page-master master-name="simple">
+      <fo:region-body/>
+      <fo:region-before/>
+      <fo:region-after/>
+      <pdf:page page-numbers="*">
+        <x:xmpmeta xmlns:x="adobe:ns:meta/">
+          <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:abc="http://www.abc.de/abc/">
+            <rdf:Description rdf:about="" abc:def="val"/>
+            <rdf:Description rdf:about=""  xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
+                             xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
+                             xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">
+              <pdfaExtension:schemas>
+                <rdf:Bag>
+                  <rdf:li rdf:parseType="Resource">
+                    <pdfaSchema:property>
+                      <rdf:Seq>
+                        <rdf:li rdf:parseType="Resource">
+                          <pdfaProperty:name>split</pdfaProperty:name>
+                        </rdf:li>
+                      </rdf:Seq>
+                    </pdfaSchema:property>
+                  </rdf:li>
+                </rdf:Bag>
+              </pdfaExtension:schemas>
+            </rdf:Description>
+          </rdf:RDF>
+        </x:xmpmeta>
+      </pdf:page>
+    </fo:simple-page-master>
+  </fo:layout-master-set>
+  <fo:page-sequence master-reference="simple">
+    <fo:flow flow-name="xsl-region-body">
+      <fo:block>SLIDE 1</fo:block>
+    </fo:flow>
+  </fo:page-sequence>
+</fo:root>
\ No newline at end of file
index 74ca9a7a4d77765caa87b104be3078a30b03e493..8c9b592325d6cd0fa8fe1b04f5a94cc172c78ef4 100644 (file)
Binary files a/fop/lib/xmlgraphics-commons-svn-trunk.jar and b/fop/lib/xmlgraphics-commons-svn-trunk.jar differ
diff --git a/fop/test/layoutengine/standard-testcases/pdf-page-xmp.xml b/fop/test/layoutengine/standard-testcases/pdf-page-xmp.xml
new file mode 100644 (file)
index 0000000..922ef27
--- /dev/null
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- $Id$ -->
+<testcase>
+  <info>
+    <p>
+      This test checks the PDF dictionary extensions.
+    </p>
+  </info>
+  <fo>
+    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:pdf="http://xmlgraphics.apache.org/fop/extensions/pdf">
+      <fo:layout-master-set>
+        <fo:simple-page-master master-name="simple">
+          <fo:region-body/>
+          <fo:region-before/>
+          <fo:region-after/>
+          <pdf:page page-numbers="*">
+            <x:xmpmeta xmlns:x="adobe:ns:meta/">
+              <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:abc="http://www.abc.de/abc/">
+                <rdf:Description rdf:about="" abc:def="val"/>
+                <rdf:Description rdf:about=""  xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
+                                 xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
+                                 xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">
+                  <pdfaExtension:schemas>
+                    <rdf:Bag>
+                      <rdf:li rdf:parseType="Resource">
+                        <pdfaSchema:property>
+                          <rdf:Seq>
+                            <rdf:li rdf:parseType="Resource">
+                              <pdfaProperty:name>split</pdfaProperty:name>
+                            </rdf:li>
+                          </rdf:Seq>
+                        </pdfaSchema:property>
+                      </rdf:li>
+                    </rdf:Bag>
+                  </pdfaExtension:schemas>
+                </rdf:Description>
+              </rdf:RDF>
+            </x:xmpmeta>
+          </pdf:page>
+        </fo:simple-page-master>
+      </fo:layout-master-set>
+      <fo:page-sequence master-reference="simple">
+        <fo:flow flow-name="xsl-region-body">
+          <fo:block>SLIDE 1</fo:block>
+        </fo:flow>
+      </fo:page-sequence>
+    </fo:root>
+  </fo>
+  <if-checks xmlns:if="http://xmlgraphics.apache.org/fop/intermediate" xmlns:pdf="apache:fop:extensions:pdf"
+             xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:abc="http://www.abc.de/abc/">
+    <eval expected="val" xpath="//if:page[@name=1]/if:page-header/pdf:page//rdf:Description[1]/@abc:def"/>
+  </if-checks>
+</testcase>