On PDFDocument, it is now possible to set the PDF-A mode which triggers conformance checks where necessary. The first PDF/A-1b conformance checks are now in the code. When conformance problems are detected, the new PDFConformanceException (subclass of RuntimeException since this would be a programmer error) is thrown. PDF Renderer got a setting (through rendering options and XML configuration) to set the PDF/A-1 mode. Default is "disabled". git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@378560 13f79535-47bb-0310-9956-ffa450edef68tags/fop-0_92-beta
@@ -35,4 +35,7 @@ public interface XMPConstants { | |||
/** Namespace URI for the XMP Basic Schema */ | |||
String XMP_BASIC_NAMESPACE = "http://ns.adobe.com/xap/1.0/"; | |||
/** Namespace URI for the Adobe PDF Schema */ | |||
String ADOBE_PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/"; | |||
} |
@@ -0,0 +1,71 @@ | |||
/* | |||
* Copyright 2006 The Apache Software Foundation. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
/* $Id$ */ | |||
package org.apache.fop.pdf; | |||
/** Enum class for PDF/A modes. */ | |||
public final class PDFAMode { | |||
/** PDF/A disabled */ | |||
public static final PDFAMode DISABLED = new PDFAMode("PDF/A disabled"); | |||
/** PDF/A-1a enabled */ | |||
public static final PDFAMode PDFA_1A = new PDFAMode("PDF/A-1a"); | |||
/** PDF/A-1b enabled */ | |||
public static final PDFAMode PDFA_1B = new PDFAMode("PDF/A-1b"); | |||
private String name; | |||
/** | |||
* Constructor to add a new named item. | |||
* @param name Name of the item. | |||
*/ | |||
private PDFAMode(String name) { | |||
this.name = name; | |||
} | |||
/** @return the name of the enum */ | |||
public String getName() { | |||
return this.name; | |||
} | |||
/** @return true if this mode obey the restrictions established by PDF/A-1b. */ | |||
public boolean isPDFA1LevelB() { | |||
return (this != DISABLED); | |||
} | |||
/** | |||
* Returns the mode enum object given a String. | |||
* @param s the string | |||
* @return the PDFAMode enum object (DISABLED will be returned if no match is found) | |||
*/ | |||
public static PDFAMode valueOf(String s) { | |||
if (PDFA_1A.getName().equalsIgnoreCase(s)) { | |||
return PDFA_1A; | |||
} else if (PDFA_1B.getName().equalsIgnoreCase(s)) { | |||
return PDFA_1B; | |||
} else { | |||
return DISABLED; | |||
} | |||
} | |||
/** @see java.lang.Object#toString() */ | |||
public String toString() { | |||
return name; | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
/* | |||
* Copyright 2006 The Apache Software Foundation. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
/* $Id$ */ | |||
package org.apache.fop.pdf; | |||
/** | |||
* RuntimeException descendant indicating a conformance problem during PDF generation. This | |||
* exception will be throws, for example, when PDF/A-1 Level B conformance is activated but the | |||
* PDF version produced is not PDF 1.4 as mandated by ISO 19005-1:2005(E). | |||
*/ | |||
public class PDFConformanceException extends RuntimeException { | |||
/** | |||
* Constructs an PDFConformanceException with no detail message. | |||
* A detail message is a String that describes this particular exception. | |||
*/ | |||
public PDFConformanceException() { | |||
super(); | |||
} | |||
/** | |||
* Constructs an PDFConformanceException with the specified detail | |||
* message. A detail message is a String that describes this particular | |||
* exception. | |||
* @param message the String that contains a detailed message | |||
*/ | |||
public PDFConformanceException(String message) { | |||
super(message); | |||
} | |||
} |
@@ -58,10 +58,6 @@ import org.apache.commons.logging.LogFactory; | |||
public class PDFDocument { | |||
private static final Integer LOCATION_PLACEHOLDER = new Integer(0); | |||
/** | |||
* the version of PDF supported which is 1.4 | |||
*/ | |||
protected static final String PDF_VERSION = "1.4"; | |||
/** Integer constant to represent PDF 1.3 */ | |||
public static final int PDF_VERSION_1_3 = 3; | |||
@@ -107,6 +103,12 @@ public class PDFDocument { | |||
/** Indicates what PDF version is active */ | |||
protected int pdfVersion = PDF_VERSION_1_4; | |||
/** | |||
* Indicates the PDF/A-1 mode currently active. Defaults to "no restrictions", i.e. | |||
* PDF/A-1 not active. | |||
*/ | |||
protected PDFAMode pdfAMode = PDFAMode.DISABLED; | |||
/** | |||
* the /Root object | |||
*/ | |||
@@ -250,6 +252,35 @@ public class PDFDocument { | |||
return this.pdfVersion; | |||
} | |||
/** @return the String representing the active PDF version */ | |||
public String getPDFVersionString() { | |||
switch (getPDFVersion()) { | |||
case PDF_VERSION_1_3: | |||
return "1.3"; | |||
case PDF_VERSION_1_4: | |||
return "1.4"; | |||
default: | |||
throw new IllegalStateException("Unsupported PDF version selected"); | |||
} | |||
} | |||
/** @return the PDF/A mode currently active. */ | |||
public PDFAMode getPDFAMode() { | |||
return this.pdfAMode; | |||
} | |||
/** | |||
* Sets the active PDF/A mode. This must be set immediately after calling the constructor so | |||
* the checks will be activated. | |||
* @param mode one of the PDFAMode constants | |||
*/ | |||
public void setPDFAMode(PDFAMode mode) { | |||
if (mode == null) { | |||
throw new NullPointerException("mode must not be null"); | |||
} | |||
this.pdfAMode = mode; | |||
} | |||
/** | |||
* Returns the factory for PDF objects. | |||
* @return PDFFactory the factory | |||
@@ -474,8 +505,10 @@ public class PDFDocument { | |||
* @param params The encryption parameters for the pdf file | |||
*/ | |||
public void setEncryption(PDFEncryptionParams params) { | |||
this.encryption = | |||
PDFEncryptionManager.newInstance(++this.objectcount, params); | |||
if (getPDFAMode().isPDFA1LevelB()) { | |||
throw new PDFConformanceException("PDF/A-1 doesn't allow encrypted PDFs"); | |||
} | |||
this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params); | |||
((PDFObject)this.encryption).setDocument(this); | |||
if (encryption != null) { | |||
/**@todo this cast is ugly. PDFObject should be transformed to an interface. */ | |||
@@ -824,13 +857,16 @@ public class PDFDocument { | |||
public void outputHeader(OutputStream stream) throws IOException { | |||
this.position = 0; | |||
byte[] pdf = ("%PDF-" + PDF_VERSION + "\n").getBytes(); | |||
if (getPDFAMode().isPDFA1LevelB() && getPDFVersion() != PDF_VERSION_1_4) { | |||
throw new PDFConformanceException("PDF version must be 1.4 for " + getPDFAMode()); | |||
} | |||
byte[] pdf = ("%PDF-" + getPDFVersionString() + "\n").getBytes(); | |||
stream.write(pdf); | |||
this.position += pdf.length; | |||
// output a binary comment as recommended by the PDF spec (3.4.1) | |||
byte[] bin = | |||
{ | |||
byte[] bin = { | |||
(byte)'%', | |||
(byte)0xAA, | |||
(byte)0xAB, |
@@ -44,6 +44,11 @@ public class PDFInfo extends PDFObject { | |||
*/ | |||
private String creator; | |||
/** @return the producer of the document or null if not set */ | |||
public String getProducer() { | |||
return this.producer; | |||
} | |||
/** | |||
* set the producer string | |||
* | |||
@@ -110,7 +115,7 @@ public class PDFInfo extends PDFObject { | |||
} | |||
/** @return the keywords for the document or null if not set */ | |||
public String getKeyword() { | |||
public String getKeywords() { | |||
return this.keywords; | |||
} | |||
@@ -23,6 +23,7 @@ import java.io.OutputStream; | |||
import java.text.DateFormat; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Date; | |||
import java.util.TimeZone; | |||
import javax.xml.transform.OutputKeys; | |||
import javax.xml.transform.Transformer; | |||
@@ -32,7 +33,6 @@ import javax.xml.transform.TransformerFactory; | |||
import javax.xml.transform.dom.DOMSource; | |||
import javax.xml.transform.stream.StreamResult; | |||
import org.apache.fop.apps.FOUserAgent; | |||
import org.apache.fop.fo.ElementMapping; | |||
import org.apache.fop.fo.extensions.xmp.XMPConstants; | |||
import org.w3c.dom.DOMImplementation; | |||
@@ -127,6 +127,11 @@ public class PDFMetadata extends PDFStream { | |||
/** @see org.apache.fop.pdf.AbstractPDFStream#buildStreamDict(String) */ | |||
protected String buildStreamDict(String lengthEntry) { | |||
final String filterEntry = getFilterList().buildFilterDictEntries(); | |||
if (getDocumentSafely().getPDFAMode().isPDFA1LevelB() | |||
&& filterEntry != null && filterEntry.length() > 0) { | |||
throw new PDFConformanceException( | |||
"The Filter key is prohibited when PDF/A-1 is active"); | |||
} | |||
final StringBuffer sb = new StringBuffer(128); | |||
sb.append(getObjectID()); | |||
sb.append("<< "); | |||
@@ -153,7 +158,8 @@ public class PDFMetadata extends PDFStream { | |||
Element desc, el; | |||
PDFInfo info = pdfDoc.getInfo(); | |||
DateFormat pseudoISO8601DateFormat = new SimpleDateFormat( | |||
"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS"); | |||
"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'"); | |||
pseudoISO8601DateFormat.setTimeZone(TimeZone.getTimeZone("GMT+00")); | |||
//Set creation date if not available, yet | |||
if (info.getCreationDate() == null) { | |||
@@ -161,9 +167,12 @@ public class PDFMetadata extends PDFStream { | |||
info.setCreationDate(d); | |||
} | |||
final String xmlns = "http://www.w3.org/2000/xmlns/"; | |||
//Dublin Core | |||
desc = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:Description"); | |||
desc.setAttribute("about", ""); | |||
desc.setAttributeNS(xmlns, "xmlns:dc", XMPConstants.DUBLIN_CORE_NAMESPACE); | |||
rdf.appendChild(desc); | |||
if (info.getAuthor() != null) { | |||
el = doc.createElementNS(XMPConstants.DUBLIN_CORE_NAMESPACE, "dc:creator"); | |||
@@ -187,6 +196,7 @@ public class PDFMetadata extends PDFStream { | |||
//XMP Basic Schema | |||
desc = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:Description"); | |||
desc.setAttribute("about", ""); | |||
desc.setAttributeNS(xmlns, "xmlns:xmp", XMPConstants.XMP_BASIC_NAMESPACE); | |||
rdf.appendChild(desc); | |||
el = doc.createElementNS(XMPConstants.XMP_BASIC_NAMESPACE, "xmp:createDate"); | |||
desc.appendChild(el); | |||
@@ -197,6 +207,24 @@ public class PDFMetadata extends PDFStream { | |||
el.appendChild(doc.createTextNode(info.getCreator())); | |||
} | |||
//Adobe PDF Schema | |||
desc = doc.createElementNS(XMPConstants.RDF_NAMESPACE, "rdf:Description"); | |||
desc.setAttribute("about", ""); | |||
desc.setAttributeNS(xmlns, "xmlns:pdf", XMPConstants.ADOBE_PDF_NAMESPACE); | |||
rdf.appendChild(desc); | |||
if (info.getKeywords() != null) { | |||
el = doc.createElementNS(XMPConstants.ADOBE_PDF_NAMESPACE, "pdf:Keywords"); | |||
desc.appendChild(el); | |||
el.appendChild(doc.createTextNode(info.getKeywords())); | |||
} | |||
if (info.getProducer() != null) { | |||
el = doc.createElementNS(XMPConstants.ADOBE_PDF_NAMESPACE, "pdf:Producer"); | |||
desc.appendChild(el); | |||
el.appendChild(doc.createTextNode(info.getProducer())); | |||
} | |||
el = doc.createElementNS(XMPConstants.ADOBE_PDF_NAMESPACE, "pdf:PDFVersion"); | |||
desc.appendChild(el); | |||
el.appendChild(doc.createTextNode(pdfDoc.getPDFVersionString())); | |||
return doc; | |||
} |
@@ -62,6 +62,7 @@ import org.apache.fop.fonts.FontSetup; | |||
import org.apache.fop.image.FopImage; | |||
import org.apache.fop.image.ImageFactory; | |||
import org.apache.fop.image.XMLImage; | |||
import org.apache.fop.pdf.PDFAMode; | |||
import org.apache.fop.pdf.PDFAnnotList; | |||
import org.apache.fop.pdf.PDFColor; | |||
import org.apache.fop.pdf.PDFDocument; | |||
@@ -109,6 +110,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { | |||
*/ | |||
public static final String MIME_TYPE = MimeConstants.MIME_PDF; | |||
/** Rendering Options key for the PDF/A mode. */ | |||
public static final String PDF_A_MODE = "pdf-a-mode"; | |||
/** Controls whether comments are written to the PDF stream. */ | |||
protected static final boolean WRITE_COMMENTS = true; | |||
@@ -117,6 +121,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { | |||
*/ | |||
protected PDFDocument pdfDoc; | |||
/** the PDF/A mode (Default: disabled) */ | |||
protected PDFAMode pdfAMode = PDFAMode.DISABLED; | |||
/** | |||
* Map of pages using the PageViewport as the key | |||
* this is used for prepared pages that cannot be immediately | |||
@@ -230,6 +237,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { | |||
} else { | |||
this.fontList.addAll(cfgFonts); | |||
} | |||
String s = cfg.getChild(PDF_A_MODE, true).getValue(null); | |||
if (s != null) { | |||
this.pdfAMode = PDFAMode.valueOf(s); | |||
} | |||
} | |||
/** | |||
@@ -237,6 +249,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { | |||
*/ | |||
public void setUserAgent(FOUserAgent agent) { | |||
super.setUserAgent(agent); | |||
String s = (String)agent.getRendererOptions().get(PDF_A_MODE); | |||
if (s != null) { | |||
this.pdfAMode = PDFAMode.valueOf(s); | |||
} | |||
} | |||
/** | |||
@@ -249,6 +265,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { | |||
ostream = stream; | |||
this.pdfDoc = new PDFDocument( | |||
userAgent.getProducer() != null ? userAgent.getProducer() : ""); | |||
this.pdfDoc.setPDFAMode(this.pdfAMode); | |||
this.pdfDoc.setCreator(userAgent.getCreator()); | |||
this.pdfDoc.setCreationDate(userAgent.getCreationDate()); | |||
this.pdfDoc.getInfo().setAuthor(userAgent.getAuthor()); |