From 3800fbcd385314883c58c7ca69ceea56ad39e538 Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Tue, 3 Aug 2010 13:44:54 +0000 Subject: [PATCH] Bugzilla #44460: Added support for PDF file attachments (embedded files) including basic-link support to reference them. Based on work submitted by: Andrejus Chaliapinas 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 --- .../content/xdocs/trunk/output.xml | 94 ++++++++--- .../services/org.apache.fop.fo.ElementMapping | 1 + .../org/apache/fop/pdf/PDFEmbeddedFile.java | 45 +++++ src/java/org/apache/fop/pdf/PDFFactory.java | 54 +++++- src/java/org/apache/fop/pdf/PDFFileSpec.java | 41 +++-- src/java/org/apache/fop/pdf/PDFGoTo.java | 8 +- src/java/org/apache/fop/pdf/PDFLaunch.java | 24 ++- src/java/org/apache/fop/pdf/PDFNames.java | 23 ++- src/java/org/apache/fop/pdf/PDFProfile.java | 12 ++ .../fop/render/pdf/PDFDocumentHandler.java | 9 + .../pdf/PDFDocumentNavigationHandler.java | 5 +- .../apache/fop/render/pdf/PDFRenderer.java | 8 + .../fop/render/pdf/PDFRenderingUtil.java | 65 ++++++++ .../AbstractPDFExtensionElement.java | 73 +++++++++ .../pdf/extensions/PDFElementMapping.java | 51 ++++++ .../extensions/PDFEmbeddedFileElement.java | 105 ++++++++++++ .../PDFEmbeddedFileExtensionAttachment.java | 155 ++++++++++++++++++ .../extensions/PDFExtensionAttachment.java | 65 ++++++++ .../pdf/extensions/PDFExtensionHandler.java | 97 +++++++++++ status.xml | 5 +- 20 files changed, 872 insertions(+), 68 deletions(-) create mode 100644 src/java/org/apache/fop/pdf/PDFEmbeddedFile.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java create mode 100644 src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java diff --git a/src/documentation/content/xdocs/trunk/output.xml b/src/documentation/content/xdocs/trunk/output.xml index d1ab9e19f..1766cd493 100644 --- a/src/documentation/content/xdocs/trunk/output.xml +++ b/src/documentation/content/xdocs/trunk/output.xml @@ -194,32 +194,76 @@ out = proc.getOutputStream();]]> e.printStackTrace(); } }]]> -

- Check the iText tutorial and documentation for setting access flags, password, - encryption strength and other parameters. -

- -
- Watermarks -

- In addition to the PDF Post-processing options, consider the following workarounds: -

-
    -
  • - Use a background image for the body region. -
  • -
  • - (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. -
  • -
+

+ Check the iText tutorial and documentation for setting access flags, password, + encryption strength and other parameters. +

+
+
+ Watermarks +

+ In addition to the PDF Post-processing options, consider the following workarounds: +

+
    +
  • + Use a background image for the body region. +
  • +
  • + (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. +
  • +
+
+
+ Extensions +

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:

+ + ]]> +
+ Embedded Files +

+ It is possible to attach/embed arbitrary files into a PDF file. You can give a name and + a description of the file. Example: +

+ + + + + ]]> +

+ pdf:embedded-file must be a child of fo:declarations. + 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. +

+

+ It is also possible to reference an embedded file from an fo:basic-link. + Use the special "embedded-file:" URI scheme with the filename as single argument after + the URI scheme. Example: +

+ Attached Image +]]> +

+ 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. +

+
+
-
PostScript

diff --git a/src/java/META-INF/services/org.apache.fop.fo.ElementMapping b/src/java/META-INF/services/org.apache.fop.fo.ElementMapping index b62f36196..6f4374a93 100644 --- a/src/java/META-INF/services/org.apache.fop.fo.ElementMapping +++ b/src/java/META-INF/services/org.apache.fop.fo.ElementMapping @@ -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 index 000000000..78010dd78 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFEmbeddedFile.java @@ -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); + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index bf3399b09..f92fdc4e7 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -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 diff --git a/src/java/org/apache/fop/pdf/PDFFileSpec.java b/src/java/org/apache/fop/pdf/PDFFileSpec.java index 8de4164af..44195d4ee 100644 --- a/src/java/org/apache/fop/pdf/PDFFileSpec.java +++ b/src/java/org/apache/fop/pdf/PDFFileSpec.java @@ -19,16 +19,12 @@ 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; } diff --git a/src/java/org/apache/fop/pdf/PDFGoTo.java b/src/java/org/apache/fop/pdf/PDFGoTo.java index ad04650c6..0626f0fcd 100644 --- a/src/java/org/apache/fop/pdf/PDFGoTo.java +++ b/src/java/org/apache/fop/pdf/PDFGoTo.java @@ -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); } diff --git a/src/java/org/apache/fop/pdf/PDFLaunch.java b/src/java/org/apache/fop/pdf/PDFLaunch.java index 386a7a9a3..f32afe656 100644 --- a/src/java/org/apache/fop/pdf/PDFLaunch.java +++ b/src/java/org/apache/fop/pdf/PDFLaunch.java @@ -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; } diff --git a/src/java/org/apache/fop/pdf/PDFNames.java b/src/java/org/apache/fop/pdf/PDFNames.java index 4253dd6d1..aa6f64547 100644 --- a/src/java/org/apache/fop/pdf/PDFNames.java +++ b/src/java/org/apache/fop/pdf/PDFNames.java @@ -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); } } diff --git a/src/java/org/apache/fop/pdf/PDFProfile.java b/src/java/org/apache/fop/pdf/PDFProfile.java index b645cb825..fb4575105 100644 --- a/src/java/org/apache/fop/pdf/PDFProfile.java +++ b/src/java/org/apache/fop/pdf/PDFProfile.java @@ -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())); + } + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index bc2f16f45..c7210344d 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -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() + ")"); diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java index 5e1b1b250..c215243a7 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java @@ -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); diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index aab84807c..b0809c10f 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -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); + } } } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index 3d68812b1..f6123fb33 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -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 index 000000000..2b50112c0 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/AbstractPDFExtensionElement.java @@ -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 index 000000000..55795f4fa --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFElementMapping.java @@ -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 index 000000000..d2c51aba4 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileElement.java @@ -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 index 000000000..b4572663a --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFEmbeddedFileExtensionAttachment.java @@ -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 index 000000000..a4f5b47b7 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionAttachment.java @@ -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 index 000000000..d285904a8 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/extensions/PDFExtensionHandler.java @@ -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; + } +} diff --git a/status.xml b/status.xml index 660fc6002..146a65ad0 100644 --- a/status.xml +++ b/status.xml @@ -58,7 +58,10 @@ documents. Example: the fix of marks layering will be such a case when it's done. --> - + + Added support for PDF File Attachments (Embedded Files). + + AFP Output: Fixed positioning of Java2D-based images (when GOCA is enabled). -- 2.39.5