]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Bugzilla #44460:
authorJeremias Maerki <jeremias@apache.org>
Tue, 3 Aug 2010 13:44:54 +0000 (13:44 +0000)
committerJeremias Maerki <jeremias@apache.org>
Tue, 3 Aug 2010 13:44:54 +0000 (13:44 +0000)
Added support for PDF file attachments (embedded files) including basic-link support to reference them.

Based on work submitted by: Andrejus Chaliapinas <a.chaliapinas.at.infosana.com>
I've changed and improved his patch extensively and added some documentation.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@981875 13f79535-47bb-0310-9956-ffa450edef68

20 files changed:
src/documentation/content/xdocs/trunk/output.xml
src/java/META-INF/services/org.apache.fop.fo.ElementMapping
src/java/org/apache/fop/pdf/PDFEmbeddedFile.java [new file with mode: 0644]
src/java/org/apache/fop/pdf/PDFFactory.java
src/java/org/apache/fop/pdf/PDFFileSpec.java
src/java/org/apache/fop/pdf/PDFGoTo.java
src/java/org/apache/fop/pdf/PDFLaunch.java
src/java/org/apache/fop/pdf/PDFNames.java
src/java/org/apache/fop/pdf/PDFProfile.java
src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java
src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java
src/java/org/apache/fop/render/pdf/PDFRenderer.java
src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java [new file with mode: 0644]
status.xml

index d1ab9e19f331f330ad63f403184d5927fd2b3cd2..1766cd4932b4a7a849085d8ee734fe50353dfc7a 100644 (file)
@@ -194,32 +194,76 @@ out = proc.getOutputStream();]]></source>
     e.printStackTrace();
   }
 }]]></source>
-    <p>
-      Check the iText tutorial and documentation for setting access flags, password, 
-      encryption strength and other parameters.
-    </p>
-  </section>
-  <section id="pdf-watermark">
-    <title>Watermarks</title>
-    <p>
-      In addition to the <a href="#pdf-postprocess">PDF Post-processing</a> options, consider the following workarounds:
-    </p>
-    <ul>
-      <li>
-        Use a background image for the body region.
-      </li>
-      <li>
-        (submitted by Trevor Campbell) Place an image in a
-        region that overlaps the flowing text. For example, make
-        region-before large enough to contain your image. Then include a
-        block (if necessary, use an absolutely positioned block-container)
-        containing the watermark image in the static-content for the
-        region-before. Note that the image will be drawn on top of the
-        normal content.
-      </li>
-    </ul>
+      <p>
+        Check the iText tutorial and documentation for setting access flags, password, 
+        encryption strength and other parameters.
+      </p>
+    </section>
+    <section id="pdf-watermark">
+      <title>Watermarks</title>
+      <p>
+        In addition to the <a href="#pdf-postprocess">PDF Post-processing</a> options, consider the following workarounds:
+      </p>
+      <ul>
+        <li>
+          Use a background image for the body region.
+        </li>
+        <li>
+          (submitted by Trevor Campbell) Place an image in a
+          region that overlaps the flowing text. For example, make
+          region-before large enough to contain your image. Then include a
+          block (if necessary, use an absolutely positioned block-container)
+          containing the watermark image in the static-content for the
+          region-before. Note that the image will be drawn on top of the
+          normal content.
+        </li>
+      </ul>
+    </section>
+    <section id="pdf-extensions">
+      <title>Extensions</title>
+      <p>The PDF Renderer supports some PDF specific extensions which can be embedded 
+        into the input FO document. To use the extensions the appropriate namespace must 
+        be declared in the fo:root element like this:</p>
+      <source><![CDATA[
+<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"
+         xmlns:pdf="http://xmlgraphics.apache.org/fop/extensions/pdf">
+  ]]></source>
+      <section id="pdf-embedded-file">
+        <title>Embedded Files</title>
+        <p>
+          It is possible to attach/embed arbitrary files into a PDF file. You can give a name and
+          a description of the file. Example:
+        </p>
+        <source><![CDATA[
+  <fo:declarations>
+    <pdf:embedded-file filename="image.jpg" src="url(file:///C:/Temp/myimage.jpg)" description="My image"/>
+    <pdf:embedded-file src="url(file:///C:/Temp/MyTextDoc.odt)"/>
+  </fo:declarations>
+  ]]></source>
+        <p>
+          <code>pdf:embedded-file</code> must be a child of <code>fo:declarations</code>.
+          The "src" property is used to reference the file that is to be embedded. This property
+          uses the "uri-specification" datatype from the XSL-FO specification.
+          The "filename" property is optional. If it is missing the filename is automatically set
+          from the URI/IRI of the "src" property. An optional description can also be added to
+          further describe the file attachment.
+        </p>
+        <p>
+          It is also possible to reference an embedded file from an <code>fo:basic-link</code>.
+          Use the special "embedded-file:" URI scheme with the filename as single argument after
+          the URI scheme. Example:
+        </p>
+        <source><![CDATA[
+<fo:basic-link external-destination="url(embedded-file:image.jpg)">Attached Image</fo:basic-link>
+]]></source>
+        <p>
+          Note: Not all PDF Viewers (including some Acrobat Versions) will open the embedded file
+          when clicking on the link. In that case, the user will have to open he attachment via
+          the separate list of file attachments.
+        </p>
+      </section>
+    </section>
   </section>
-</section>
 <section id="ps">
   <title>PostScript</title>
   <p>
index b62f36196fb177e36ebaa3642f2b37f93079dba3..6f4374a93905df3f24918b6a7562ab9cb57680b2 100644 (file)
@@ -9,3 +9,4 @@ org.apache.fop.fo.extensions.xmp.RDFElementMapping
 org.apache.fop.render.ps.extensions.PSExtensionElementMapping
 org.apache.fop.render.afp.extensions.AFPElementMapping
 org.apache.fop.render.pcl.extensions.PCLElementMapping
+org.apache.fop.render.pdf.extensions.PDFElementMapping
diff --git a/src/java/org/apache/fop/pdf/PDFEmbeddedFile.java b/src/java/org/apache/fop/pdf/PDFEmbeddedFile.java
new file mode 100644 (file)
index 0000000..78010dd
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.util.Date;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This class represents an embedded file stream.
+ */
+public class PDFEmbeddedFile extends PDFStream {
+
+    protected static Log log = LogFactory.getLog(PDFEmbeddedFile.class.getName());
+
+    /**
+     * Creates a new embedded file stream.
+     */
+    public PDFEmbeddedFile() {
+        super();
+        put("Type", new PDFName("EmbeddedFile"));
+        PDFDictionary params = new PDFDictionary();
+        params.put("CreationDate", params.formatDateTime(new Date()));
+        put("Params", params);
+    }
+
+}
index bf3399b09c16b308591799c17924b9e9704b91c4..f92fdc4e7299221eb5777c4f29ddb8128bf70117 100644 (file)
@@ -1044,6 +1044,8 @@ public class PDFFactory {
         return link;
     }
 
+    private static final String EMBEDDED_FILE = "embedded-file:";
+
     /**
      * Create/find and return the appropriate external PDFAction according to the target
      *
@@ -1056,32 +1058,70 @@ public class PDFFactory {
     public PDFAction getExternalAction(String target, boolean newWindow) {
         int index;
         String targetLo = target.toLowerCase();
-        // HTTP URL?
-        if (targetLo.startsWith("http://")) {
+        if (target.startsWith(EMBEDDED_FILE)) {
+            // File Attachments (Embedded Files)
+            String filename = target.substring(EMBEDDED_FILE.length());
+            return getLaunchActionForEmbeddedFile(filename);
+        } else if (targetLo.startsWith("http://")) {
+            // HTTP URL?
             return new PDFUri(target);
-        // Non PDF files. Try to /Launch them.
         } else if (targetLo.startsWith("file://")) {
+            // Non PDF files. Try to /Launch them.
             target = target.substring("file://".length());
             return getLaunchAction(target);
-        // Bare PDF file name?
         } else if (targetLo.endsWith(".pdf")) {
+            // Bare PDF file name?
             return getGoToPDFAction(target, null, -1, newWindow);
-        // PDF file + page?
         } else if ((index = targetLo.indexOf(".pdf#page=")) > 0) {
+            // PDF file + page?
             String filename = target.substring(0, index + 4);
             int page = Integer.parseInt(target.substring(index + 10));
             return getGoToPDFAction(filename, null, page, newWindow);
-        // PDF file + destination?
         } else if ((index = targetLo.indexOf(".pdf#dest=")) > 0) {
+            // PDF file + destination?
             String filename = target.substring(0, index + 4);
             String dest = target.substring(index + 10);
             return getGoToPDFAction(filename, dest, -1, newWindow);
-        // None of the above? Default to URI:
         } else {
+            // None of the above? Default to URI:
             return new PDFUri(target);
         }
     }
 
+    private PDFAction getLaunchActionForEmbeddedFile(String filename) {
+        PDFNames names = getDocument().getRoot().getNames();
+        if (names == null) {
+            throw new IllegalStateException(
+                    "No Names dictionary present."
+                    + " Cannot create Launch Action for embedded file: " + filename);
+        }
+        PDFNameTreeNode embeddedFiles = names.getEmbeddedFiles();
+        if (embeddedFiles == null) {
+            throw new IllegalStateException(
+                    "No /EmbeddedFiles name tree present."
+                    + " Cannot create Launch Action for embedded file: " + filename);
+        }
+        PDFArray files = embeddedFiles.getNames();
+        PDFReference embeddedFileRef = null;
+        int i = 0;
+        while (i < files.length()) {
+            String name = (String)files.get(i);
+            i++;
+            PDFReference ref = (PDFReference)files.get(i);
+            if (name.equals(filename)) {
+                embeddedFileRef = ref;
+                break;
+            }
+            i++;
+        }
+        if (embeddedFileRef == null) {
+            throw new IllegalStateException(
+                    "No embedded file with name " + filename + " present.");
+        }
+        PDFLaunch launch = new PDFLaunch(embeddedFileRef);
+        return launch;
+    }
+
     /**
      * Create or find a PDF GoTo with the given page reference string and Y offset,
      * and return its PDF object reference
index 8de4164afcacd2bcebc77d51720c9895c951d7c0..44195d4eee420da61f8793fa2f65bb68c43d93a4 100644 (file)
 
 package org.apache.fop.pdf;
 
+
 /**
  * class representing a /FileSpec object.
  *
  */
-public class PDFFileSpec extends PDFObject {
-
-    /**
-     * the filename
-     */
-    protected String filename;
+public class PDFFileSpec extends PDFDictionary {
 
     /**
      * create a /FileSpec object.
@@ -39,29 +35,30 @@ public class PDFFileSpec extends PDFObject {
 
         /* generic creation of object */
         super();
+        put("Type", new PDFName("Filespec"));
+        put("F", filename);
+    }
 
-        this.filename = filename;
+    private String getFilename() {
+        return (String)get("F");
     }
 
     /**
-     * {@inheritDoc}
+     * Associates an dictionary with pointers to embedded file streams with this file spec.
+     * @param embeddedFile the dictionary with pointers to embedded file streams
      */
-    public String toPDFString() {
-        return getObjectID()
-                + "<<\n/Type /FileSpec\n"
-                + "/F (" + this.filename + ")\n"
-                + ">>\nendobj\n";
+    public void setEmbeddedFile(PDFDictionary embeddedFileDict) {
+        put("EF", embeddedFileDict);
     }
 
-    /*
-     * example
-     * 29 0 obj
-     * <<
-     * /Type /FileSpec
-     * /F (table1.pdf)
-     * >>
-     * endobj
+    /**
+     * Sets a description for the file spec.
+     * @param description the description
+     * @since PDF 1.6
      */
+    public void setDescription(String description) {
+        put("Desc", description);
+    }
 
     /** {@inheritDoc} */
     protected boolean contentEquals(PDFObject obj) {
@@ -75,7 +72,7 @@ public class PDFFileSpec extends PDFObject {
 
         PDFFileSpec spec = (PDFFileSpec)obj;
 
-        if (!spec.filename.equals(filename)) {
+        if (!spec.getFilename().equals(getFilename())) {
             return false;
         }
 
index ad04650c6c4c2fad83932a346580d794e0f6b2fb..0626f0fcd73c2fab26a3228ffb7ad2a34dee7637 100644 (file)
@@ -42,10 +42,8 @@ public class PDFGoTo extends PDFAction {
      * @param pageReference the pageReference represented by this object
      */
     public PDFGoTo(String pageReference) {
-        /* generic creation of object */
         super();
-
-        this.pageReference = pageReference;
+        setPageReference(pageReference);
     }
 
     /**
@@ -56,9 +54,7 @@ public class PDFGoTo extends PDFAction {
      */
     public PDFGoTo(String pageReference, Point2D position) {
         /* generic creation of object */
-        super();
-
-        this.pageReference = pageReference;
+        this(pageReference);
         setPosition(position);
     }
 
index 386a7a9a362b4436a44501c17c7dc901d70aba25..f32afe65633278571b3cffe772b7d8b268202360 100644 (file)
@@ -22,21 +22,39 @@ package org.apache.fop.pdf;
  */
 public class PDFLaunch extends PDFAction {
 
-    private PDFFileSpec externalFileSpec;
+    private PDFReference externalFileSpec;
 
+    /**
+     * Creates a new /Launch action.
+     * @param fileSpec the file specification to launch
+     */
     public PDFLaunch(PDFFileSpec fileSpec) {
+        this(fileSpec.makeReference());
+    }
+
+    /**
+     * Creates a new /Launch action.
+     * @param fileSpec a reference to the file specification
+     */
+    public PDFLaunch(PDFReference fileSpec) {
+        PDFObject fs = fileSpec.getObject();
+        if (fs != null) {
+            assert fs instanceof PDFFileSpec;
+        }
         this.externalFileSpec = fileSpec;
     }
 
+    /** {@inheritDoc} */
     public String getAction() {
         return this.referencePDF();
     }
 
+    /** {@inheritDoc} */
     public String toPDFString() {
         StringBuffer sb = new StringBuffer(64);
         sb.append(getObjectID());
         sb.append("<<\n/S /Launch\n/F ");
-        sb.append(externalFileSpec.referencePDF());
+        sb.append(externalFileSpec.toString());
         sb.append(" \n>>\nendobj\n");
 
         return sb.toString();
@@ -54,7 +72,7 @@ public class PDFLaunch extends PDFAction {
 
         PDFLaunch launch = (PDFLaunch) obj;
 
-        if (!launch.externalFileSpec.referencePDF().equals(externalFileSpec.referencePDF())) {
+        if (!launch.externalFileSpec.toString().equals(externalFileSpec.toString())) {
             return false;
         }
 
index 4253dd6d119e74c347e90e76dc206738fbe28259..aa6f64547a55d550a873f7cfa448dbfa8be851b9 100644 (file)
@@ -24,6 +24,9 @@ package org.apache.fop.pdf;
  */
 public class PDFNames extends PDFDictionary {
 
+    private static final String DESTS = "Dests";
+    private static final String EMBEDDED_FILES = "EmbeddedFiles";
+
     /**
      * Create the Names object
      */
@@ -37,7 +40,7 @@ public class PDFNames extends PDFDictionary {
      * @return the Dests object, or null if it's not used
      */
     public PDFDests getDests() {
-        return (PDFDests)get("Dests");
+        return (PDFDests)get(DESTS);
     }
 
     /**
@@ -45,7 +48,23 @@ public class PDFNames extends PDFDictionary {
      * @param dests the Dests object
      */
     public void setDests(PDFDests dests) {
-        put("Dests", dests);
+        put(DESTS, dests);
+    }
+
+    /**
+     * Returns the EmbeddedFiles object
+     * @return the EmbeddedFiles object, or null if it's not used
+     */
+    public PDFNameTreeNode getEmbeddedFiles() {
+        return (PDFNameTreeNode)get(EMBEDDED_FILES);
+    }
+
+    /**
+     * Set the EmbeddedFiles object
+     * @param dests the EmbeddedFiles object
+     */
+    public void setEmbeddedFiles(PDFNameTreeNode dests) {
+        put(EMBEDDED_FILES, dests);
     }
 
 }
index b645cb825f05adc9c0aebabfff2e7de30f57311d..fb45751056fae01017bb1e1c6ba1e785a09be8a6 100644 (file)
@@ -265,4 +265,16 @@ public class PDFProfile {
         }
     }
 
+    /** Checks if embedded files are allowed. */
+    public void verifyEmbeddedFilesAllowed() {
+        final String err = "{0} does not allow embedded files.";
+        if (isPDFAActive()) {
+            throw new PDFConformanceException(format(err, getPDFAMode()));
+        }
+        if (isPDFXActive()) {
+            //Implicit since file specs are forbidden
+            throw new PDFConformanceException(format(err, getPDFXMode()));
+        }
+    }
+
 }
index bc2f16f450f11bc95a96a9b141d1c1ff65f5a6e9..c7210344d43dc45facb968a43a12800c872f85cf 100644 (file)
@@ -51,6 +51,7 @@ import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
 import org.apache.fop.render.intermediate.IFDocumentNavigationHandler;
 import org.apache.fop.render.intermediate.IFException;
 import org.apache.fop.render.intermediate.IFPainter;
+import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileExtensionAttachment;
 import org.apache.fop.util.XMLUtil;
 
 /**
@@ -292,6 +293,14 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
         } else if (extension instanceof Metadata) {
             XMPMetadata wrapper = new XMPMetadata(((Metadata)extension));
             pdfUtil.renderXMPMetadata(wrapper);
+        } else if (extension instanceof PDFEmbeddedFileExtensionAttachment) {
+            PDFEmbeddedFileExtensionAttachment embeddedFile
+                = (PDFEmbeddedFileExtensionAttachment)extension;
+            try {
+                pdfUtil.addEmbeddedFile(embeddedFile);
+            } catch (IOException ioe) {
+                throw new IFException("Error adding embedded file: " + embeddedFile.getSrc(), ioe);
+            }
         } else {
             log.debug("Don't know how to handle extension object. Ignoring: "
                     + extension + " (" + extension.getClass().getName() + ")");
index 5e1b1b250c2b305b8f9fedb071241a78cfc42059..c215243a7d437a1d3647a34c9bb67cdab9ece7c2 100644 (file)
@@ -166,10 +166,11 @@ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler
         } else if (action instanceof URIAction) {
             URIAction u = (URIAction)action;
             assert u.isComplete();
+            String uri = u.getURI();
             PDFFactory factory = getPDFDoc().getFactory();
-            pdfAction = factory.getExternalAction(u.getURI(), u.isNewWindow());
+            pdfAction = factory.getExternalAction(uri, u.isNewWindow());
             if (!pdfAction.hasObjectNumber()) {
-                //Some PDF actions a pooled
+                //Some PDF actions are pooled
                 getPDFDoc().registerObject(pdfAction);
             }
             this.completeActions.put(action.getID(), pdfAction);
index aab84807cbfef8a6388cd334663bab12f60b3bc5..b0809c10f4381eb07a4a15bbde11261152d6a6ca 100644 (file)
@@ -97,6 +97,7 @@ import org.apache.fop.render.AbstractPathOrientedRenderer;
 import org.apache.fop.render.Graphics2DAdapter;
 import org.apache.fop.render.RendererContext;
 import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo;
+import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileExtensionAttachment;
 import org.apache.fop.traits.RuleStyle;
 import org.apache.fop.util.AbstractPaintingState;
 import org.apache.fop.util.CharUtilities;
@@ -312,6 +313,13 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf
             ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
             if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) {
                 pdfUtil.renderXMPMetadata((XMPMetadata)attachment);
+            } else if (PDFEmbeddedFileExtensionAttachment.CATEGORY.equals(
+                    attachment.getCategory())) {
+                try {
+                    pdfUtil.addEmbeddedFile((PDFEmbeddedFileExtensionAttachment)attachment);
+                } catch (IOException ioe) {
+                    throw new RuntimeException("Error embedding file", ioe);
+                }
             }
         }
     }
index 3d68812b18c3aff2e13d8bd02ffadc51d02a88de..f6123fb338e753484cee8fb575d8f474dd333d06 100644 (file)
@@ -20,6 +20,7 @@
 package org.apache.fop.render.pdf;
 
 import java.awt.color.ICC_Profile;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -33,6 +34,7 @@ import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.xmlgraphics.image.loader.util.ImageUtil;
 import org.apache.xmlgraphics.xmp.Metadata;
 import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
 import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
@@ -41,19 +43,26 @@ import org.apache.fop.accessibility.Accessibility;
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.fo.extensions.xmp.XMPMetadata;
 import org.apache.fop.pdf.PDFAMode;
+import org.apache.fop.pdf.PDFArray;
 import org.apache.fop.pdf.PDFConformanceException;
 import org.apache.fop.pdf.PDFDictionary;
 import org.apache.fop.pdf.PDFDocument;
+import org.apache.fop.pdf.PDFEmbeddedFile;
 import org.apache.fop.pdf.PDFEncryptionManager;
 import org.apache.fop.pdf.PDFEncryptionParams;
+import org.apache.fop.pdf.PDFFileSpec;
 import org.apache.fop.pdf.PDFICCBasedColorSpace;
 import org.apache.fop.pdf.PDFICCStream;
 import org.apache.fop.pdf.PDFInfo;
 import org.apache.fop.pdf.PDFMetadata;
+import org.apache.fop.pdf.PDFNameTreeNode;
+import org.apache.fop.pdf.PDFNames;
 import org.apache.fop.pdf.PDFNumsArray;
 import org.apache.fop.pdf.PDFOutputIntent;
 import org.apache.fop.pdf.PDFPageLabels;
+import org.apache.fop.pdf.PDFReference;
 import org.apache.fop.pdf.PDFXMode;
+import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileExtensionAttachment;
 import org.apache.fop.util.ColorProfileUtil;
 
 /**
@@ -413,4 +422,60 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
         nums.put(pageIndex, dict);
     }
 
+    /**
+     * Adds an embedded file to the PDF file.
+     * @param embeddedFile the object representing the embedded file to be added
+     * @throws IOException if an I/O error occurs
+     */
+    public void addEmbeddedFile(PDFEmbeddedFileExtensionAttachment embeddedFile)
+            throws IOException {
+        this.pdfDoc.getProfile().verifyEmbeddedFilesAllowed();
+        PDFNames names = this.pdfDoc.getRoot().getNames();
+        if (names == null) {
+            //Add Names if not already present
+            names = this.pdfDoc.getFactory().makeNames();
+            this.pdfDoc.getRoot().setNames(names);
+        }
+
+        //Create embedded file
+        PDFEmbeddedFile file = new PDFEmbeddedFile();
+        this.pdfDoc.registerObject(file);
+        Source src = getUserAgent().resolveURI(embeddedFile.getSrc());
+        InputStream in = ImageUtil.getInputStream(src);
+        if (in == null) {
+            throw new FileNotFoundException(embeddedFile.getSrc());
+        }
+        try {
+            OutputStream out = file.getBufferOutputStream();
+            IOUtils.copyLarge(in, out);
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+        PDFDictionary dict = new PDFDictionary();
+        dict.put("F", file);
+        PDFFileSpec fileSpec = new PDFFileSpec(embeddedFile.getFilename());
+        fileSpec.setEmbeddedFile(dict);
+        if (embeddedFile.getDesc() != null) {
+            fileSpec.setDescription(embeddedFile.getDesc());
+        }
+        this.pdfDoc.registerObject(fileSpec);
+
+        //Make sure there is an EmbeddedFiles in the Names dictionary
+        PDFNameTreeNode embeddedFiles = names.getEmbeddedFiles();
+        if (embeddedFiles == null) {
+            embeddedFiles = new PDFNameTreeNode();
+            //this.pdfDoc.registerObject(embeddedFiles);
+            names.setEmbeddedFiles(embeddedFiles);
+        }
+
+        //Add to EmbeddedFiles in the Names dictionary
+        PDFArray nameArray = embeddedFiles.getNames();
+        if (nameArray == null) {
+            nameArray = new PDFArray();
+            embeddedFiles.setNames(nameArray);
+        }
+        nameArray.add(embeddedFile.getFilename());
+        nameArray.add(new PDFReference(fileSpec));
+    }
+
 }
diff --git a/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java b/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java
new file mode 100644 (file)
index 0000000..2b50112
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf.extensions;
+
+// FOP
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+
+/**
+ * Base class for the PDF-specific extension elements.
+ */
+public abstract class AbstractPDFExtensionElement extends FONode {
+
+    /**Extension attachment. */
+    protected PDFExtensionAttachment attachment;
+
+    /**
+     * Default constructor
+     *
+     * @param parent parent of this node
+     * @see org.apache.fop.fo.FONode#FONode(FONode)
+     */
+    public AbstractPDFExtensionElement(FONode parent) {
+        super(parent);
+    }
+
+    /** {@inheritDoc} */
+    public String getNamespaceURI() {
+        return PDFElementMapping.NAMESPACE;
+    }
+
+    /** {@inheritDoc} */
+    public String getNormalNamespacePrefix() {
+        return "pdf";
+    }
+
+    /**
+     * Returns the extension attachment.
+     * @return the extension attachment if one is created by the extension element, null otherwise.
+     * @see org.apache.fop.fo.FONode#getExtensionAttachment()
+     */
+    public ExtensionAttachment getExtensionAttachment() {
+        if (attachment == null) {
+            this.attachment = (PDFExtensionAttachment)instantiateExtensionAttachment();
+        }
+        return this.attachment;
+    }
+
+    /**
+     * Instantiates extension attachment object.
+     * @return extension attachment
+     */
+    protected abstract ExtensionAttachment instantiateExtensionAttachment();
+
+}
+
diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java b/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java
new file mode 100644 (file)
index 0000000..55795f4
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf.extensions;
+
+import org.apache.fop.fo.ElementMapping;
+import org.apache.fop.fo.FONode;
+
+/**
+ * This class provides the element mapping for the PDF-specific extensions.
+ */
+public class PDFElementMapping extends ElementMapping {
+
+    /** Namespace for the extension */
+    public static final String NAMESPACE = "http://xmlgraphics.apache.org/fop/extensions/pdf";
+
+    /** Main constructor */
+    public PDFElementMapping() {
+        this.namespaceURI = NAMESPACE;
+    }
+
+    /** {@inheritDoc} */
+    protected void initialize() {
+        if (foObjs == null) {
+            foObjs = new java.util.HashMap();
+            foObjs.put(PDFEmbeddedFileElement.ELEMENT, new PDFEmbeddedFileMaker());
+        }
+    }
+
+    static class PDFEmbeddedFileMaker extends ElementMapping.Maker {
+        public FONode make(FONode parent) {
+            return new PDFEmbeddedFileElement(parent);
+        }
+    }
+}
diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java
new file mode 100644 (file)
index 0000000..d2c51ab
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf.extensions;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.datatypes.URISpecification;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.PropertyList;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+
+/**
+ * Extension element for pdf:embedded-file.
+ */
+public class PDFEmbeddedFileElement extends AbstractPDFExtensionElement {
+
+    protected static final String ELEMENT = "embedded-file";
+
+    /**
+     * Main constructor
+     * @param parent parent FO node
+     */
+    protected PDFEmbeddedFileElement(FONode parent) {
+        super(parent);
+    }
+
+    /** {@inheritDoc} */
+    protected void startOfNode() throws FOPException {
+        super.startOfNode();
+        if (parent.getNameId() != Constants.FO_DECLARATIONS) {
+            invalidChildError(getLocator(), parent.getName(), getNamespaceURI(), getName(),
+                "rule.childOfDeclarations");
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void processNode(String elementName, Locator locator,
+                            Attributes attlist, PropertyList propertyList)
+                                throws FOPException {
+        PDFEmbeddedFileExtensionAttachment embeddedFile
+            = (PDFEmbeddedFileExtensionAttachment)getExtensionAttachment();
+        String desc = attlist.getValue("description");
+        if (desc != null && desc.length() > 0) {
+            embeddedFile.setDesc(desc);
+        }
+        String src = attlist.getValue("src");
+        src = URISpecification.getURL(src);
+        if (src != null && src.length() > 0) {
+            embeddedFile.setSrc(src);
+        } else {
+            missingPropertyError("src");
+        }
+        String filename = attlist.getValue("filename");
+        if (filename == null || filename.length() == 0) {
+            try {
+                URI uri = new URI(src);
+                String path = uri.getPath();
+                int idx = path.lastIndexOf('/');
+                if (idx > 0) {
+                    filename = path.substring(idx + 1);
+                } else {
+                    filename = path;
+                }
+                embeddedFile.setFilename(filename);
+            } catch (URISyntaxException e) {
+                //Filename could not be deduced from URI
+                missingPropertyError("name");
+            }
+        }
+        embeddedFile.setFilename(filename);
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalName() {
+        return ELEMENT;
+    }
+
+    /** {@inheritDoc} */
+    protected ExtensionAttachment instantiateExtensionAttachment() {
+        return new PDFEmbeddedFileExtensionAttachment();
+    }
+}
diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java
new file mode 100644 (file)
index 0000000..b457266
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf.extensions;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * This is the pass-through value object for the PDF extension.
+ */
+public class PDFEmbeddedFileExtensionAttachment extends PDFExtensionAttachment {
+
+    /** element name */
+    protected static final String ELEMENT = "embedded-file";
+
+    /** name of file to be embedded */
+    private static final String ATT_NAME = "filename";
+
+    /** source of file to be embedded (URI) */
+    private static final String ATT_SRC = "src";
+
+    /** a description of the file to be embedded */
+    private static final String ATT_DESC = "desc";
+
+    /** filename attribute */
+    private String filename = null;
+
+    /** description attribute (optional) */
+    private String desc = null;
+
+    /** source name attribute */
+    private String src = null;
+
+    /**
+     * No-argument contructor.
+     */
+    public PDFEmbeddedFileExtensionAttachment() {
+        super();
+    }
+
+    /**
+     * Default constructor.
+     * @param filename the name of the file
+     * @param src the location of the file
+     * @param desc the description of the file
+     */
+    public PDFEmbeddedFileExtensionAttachment(String filename, String src, String desc) {
+        super();
+        this.filename = filename;
+        this.src = src;
+        this.desc = desc;
+    }
+
+    /**
+     * Returns the file name.
+     * @return the file name
+     */
+    public String getFilename() {
+        return filename;
+    }
+
+    /**
+     * Sets the file name.
+     * @param name The file name to set.
+     */
+    public void setFilename(String name) {
+        this.filename = name;
+    }
+
+    /**
+     * Returns the file description.
+     * @return the description
+     */
+    public String getDesc() {
+        return desc;
+    }
+
+    /**
+     * Sets the description of the file.
+     * @param desc the description to set
+     */
+    public void setDesc(String desc) {
+        this.desc = desc;
+    }
+
+    /**
+     * Returns the source URI of the file.
+     * @return the source URI
+     */
+    public String getSrc() {
+        return src;
+    }
+
+    /**
+     * Sets the source URI of the file.
+     * @param src the source URI
+     */
+    public void setSrc(String src) {
+        this.src = src;
+    }
+
+    /** {@inheritDoc} */
+    public String getCategory() {
+        return CATEGORY;
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return "PDFEmbeddedFile(name=" + getFilename() + ", " + getSrc() + ")";
+    }
+
+    /**
+     * @return the element name
+     * @see org.apache.fop.render.ps.extensions.PDFExtensionAttachment#getElement()
+     */
+    protected String getElement() {
+        return ELEMENT;
+    }
+
+    /** {@inheritDoc} */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (filename != null && filename.length() > 0) {
+            atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", filename);
+        }
+        if (src != null && src.length() > 0) {
+            atts.addAttribute(null, ATT_SRC, ATT_SRC, "CDATA", src);
+        }
+        if (desc != null && desc.length() > 0) {
+            atts.addAttribute(null, ATT_DESC, ATT_DESC, "CDATA", desc);
+        }
+        String element = getElement();
+        handler.startElement(CATEGORY, element, element, atts);
+        handler.endElement(CATEGORY, element, element);
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java
new file mode 100644 (file)
index 0000000..a4f5b47
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf.extensions;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+
+/**
+ * This is the pass-through value object for the PDF extension.
+ */
+public abstract class PDFExtensionAttachment implements ExtensionAttachment, XMLizable {
+
+    /** The category URI for this extension attachment. */
+    public static final String CATEGORY = "apache:fop:extensions:pdf";
+
+    /**
+     * Default constructor.
+     */
+    public PDFExtensionAttachment() {
+        //nop
+    }
+
+    /**
+     * @return the category URI
+     * @see org.apache.fop.fo.extensions.ExtensionAttachment#getCategory()
+     */
+    public String getCategory() {
+        return CATEGORY;
+    }
+
+    /** @return type name */
+    public String getType() {
+        String className = getClass().getName();
+        return className.substring(className.lastIndexOf('.') + 3);
+    }
+
+    /**
+     * @return a string representation of this object
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return getType();
+    }
+
+    /** @return element */
+    protected abstract String getElement();
+}
diff --git a/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java
new file mode 100644 (file)
index 0000000..d285904
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf.extensions;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.ContentHandlerFactory.ObjectBuiltListener;
+
+/**
+ * ContentHandler (parser) for restoring PDF extension objects from XML.
+ */
+public class PDFExtensionHandler extends DefaultHandler
+            implements ContentHandlerFactory.ObjectSource {
+
+    /** Logger instance */
+    protected static Log log = LogFactory.getLog(PDFExtensionHandler.class);
+
+    private Attributes lastAttributes;
+
+    private PDFExtensionAttachment returnedObject;
+    private ObjectBuiltListener listener;
+
+    /** {@inheritDoc} */
+    public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+        boolean handled = false;
+        if (PDFExtensionAttachment.CATEGORY.equals(uri)) {
+            lastAttributes = attributes;
+            handled = false;
+            if (localName.equals(PDFEmbeddedFileExtensionAttachment.ELEMENT)) {
+                //handled in endElement
+                handled = true;
+            }
+        }
+        if (!handled) {
+            if (PDFExtensionAttachment.CATEGORY.equals(uri)) {
+                throw new SAXException("Unhandled element " + localName
+                        + " in namespace: " + uri);
+            } else {
+                log.warn("Unhandled element " + localName
+                        + " in namespace: " + uri);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        if (PDFExtensionAttachment.CATEGORY.equals(uri)) {
+            if (PDFEmbeddedFileExtensionAttachment.ELEMENT.equals(localName)) {
+                String name = lastAttributes.getValue("name");
+                String src = lastAttributes.getValue("src");
+                String desc = lastAttributes.getValue("description");
+                this.returnedObject = new PDFEmbeddedFileExtensionAttachment(name, src, desc);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void endDocument() throws SAXException {
+        if (listener != null) {
+            listener.notifyObjectBuilt(getObject());
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object getObject() {
+        return returnedObject;
+    }
+
+    /** {@inheritDoc} */
+    public void setObjectBuiltListener(ObjectBuiltListener listener) {
+        this.listener = listener;
+    }
+}
index 660fc6002111c077cefa5903571852de6d659ed8..146a65ad017ee939467316af806cb068d184329a 100644 (file)
       documents. Example: the fix of marks layering will be such a case when it's done.
     -->
     <release version="FOP Trunk" date="TBD">
-       </release>
+      <action context="Renderers" dev="JM" type="add" fixes-bug="44460" due-to="Andrejus Chaliapinas">
+        Added support for PDF File Attachments (Embedded Files).
+      </action>
+    </release>
     <release version="1.0" date="21 July 2010">
       <action context="Renderers" dev="JM" type="fix">
         AFP Output: Fixed positioning of Java2D-based images (when GOCA is enabled).