diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java
index 517798c05..7de6c8f5a 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LogMessageId.java
@@ -42,6 +42,7 @@ enum LogMessageId0Param implements LogMessageId {
GENERAL_PDF_SPECIFIED_FONTS_DONT_CONTAIN_A_SPACE_CHARACTER(XRLog.GENERAL, "Specified fonts don't contain a space character!"),
GENERAL_PDF_USING_FAST_MODE(XRLog.GENERAL, "Using fast-mode renderer. Prepare to fly."),
GENERAL_PDF_ACCESSIBILITY_NO_DOCUMENT_TITLE_PROVIDED(XRLog.GENERAL, "No document title provided. Document will not be PDF/UA compliant."),
+ GENERAL_PDF_ACCESSIBILITY_NO_DOCUMENT_DESCRIPTION_PROVIDED(XRLog.GENERAL, "No document description provided. Document will not be PDF/UA compliant."),
GENERAL_PDF_USING_GET_REQUEST_FOR_FORM(XRLog.GENERAL, "Using GET request method for form. You probably meant to add a method=\"post\" attribute to your form"),
GENERAL_PDF_ACROBAT_READER_DOES_NOT_SUPPORT_FORMS_WITH_FILE_INPUT(XRLog.GENERAL, "Acrobat Reader does not support forms with file input controls"),
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
index 6d5cec53a..9e91e2c36 100644
--- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
@@ -35,6 +35,7 @@
import com.openhtmltopdf.outputdevice.helper.ExternalResourceControlPriority;
import com.openhtmltopdf.outputdevice.helper.ExternalResourceType;
import com.openhtmltopdf.extend.FSDOMMutator;
+import com.openhtmltopdf.outputdevice.helper.NullUserInterface;
import com.openhtmltopdf.outputdevice.helper.PageDimensions;
import com.openhtmltopdf.outputdevice.helper.UnicodeImplementation;
import com.openhtmltopdf.pdfboxout.PdfBoxSlowOutputDevice.Metadata;
@@ -65,11 +66,7 @@
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences;
import org.apache.xmpbox.XMPMetadata;
-import org.apache.xmpbox.schema.AdobePDFSchema;
-import org.apache.xmpbox.schema.DublinCoreSchema;
-import org.apache.xmpbox.schema.PDFAIdentificationSchema;
-import org.apache.xmpbox.schema.XMPBasicSchema;
-import org.apache.xmpbox.schema.XMPSchema;
+import org.apache.xmpbox.schema.*;
import org.apache.xmpbox.type.BadFieldValueException;
import org.apache.xmpbox.xml.XmpSerializer;
import org.w3c.dom.Document;
@@ -84,6 +81,8 @@
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.logging.Level;
@@ -604,10 +603,8 @@ private void writePDFFast(List pages, RenderingContext c, Rectangle2D f
firePreWrite(pageCount); // opportunity to adjust meta data
setDidValues(doc); // set PDF header fields from meta data
- if (_pdfUaConformance) {
- addPdfUaXMPSchema(doc);
- } else if (_pdfAConformance != PdfAConformance.NONE) {
- addPdfASchema(doc, _pdfAConformance.getPart(), _pdfAConformance.getConformanceValue());
+ if (_pdfUaConformance || _pdfAConformance != PdfAConformance.NONE) {
+ addPdfASchema(doc, _pdfAConformance, _pdfUaConformance);
}
DisplayListCollector dlCollector = new DisplayListCollector(_root.getLayer().getPages());
@@ -685,8 +682,8 @@ private void writePDF(List pages, RenderingContext c, Rectangle2D first
firePreWrite(pageCount); // opportunity to adjust meta data
setDidValues(doc); // set PDF header fields from meta data
- if (_pdfAConformance != PdfAConformance.NONE) {
- addPdfASchema(doc, _pdfAConformance.getPart(), _pdfAConformance.getConformanceValue());
+ if (_pdfUaConformance || _pdfAConformance != PdfAConformance.NONE) {
+ addPdfASchema(doc, _pdfAConformance, _pdfUaConformance);
}
for (int i = 0; i < pageCount; i++) {
@@ -712,107 +709,105 @@ private void writePDF(List pages, RenderingContext c, Rectangle2D first
// Kindly provided by GurpusMaximus at:
// https://stackoverflow.com/questions/49682339/how-can-i-create-an-accessible-pdf-with-java-pdfbox-2-0-8-library-that-is-also-v
- private void addPdfUaXMPSchema(PDDocument doc) {
- try
- {
- PDDocumentCatalog catalog = doc.getDocumentCatalog();
- String lang = _doc.getDocumentElement().getAttribute("lang");
- catalog.setLanguage(!lang.isEmpty() ? lang : "EN-US");
- catalog.setViewerPreferences(new PDViewerPreferences(new COSDictionary()));
- catalog.getViewerPreferences().setDisplayDocTitle(true);
-
- PDMarkInfo markInfo = new PDMarkInfo();
- markInfo.setMarked(true);
- catalog.setMarkInfo(markInfo);
-
- PDDocumentInformation info = doc.getDocumentInformation();
- String title = info.getTitle() != null ? info.getTitle() : "";
-
- if (title.isEmpty()) {
- XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.GENERAL_PDF_ACCESSIBILITY_NO_DOCUMENT_TITLE_PROVIDED);
- }
-
- XMPMetadata xmp = XMPMetadata.createXMPMetadata();
- xmp.createAndAddDublinCoreSchema();
- xmp.getDublinCoreSchema().setTitle(title);
- String metaDescription = _outputDevice.getMetadataByName("description");
- xmp.getDublinCoreSchema().setDescription(metaDescription != null ? metaDescription : title);
- xmp.createAndAddPDFAExtensionSchemaWithDefaultNS();
- xmp.getPDFExtensionSchema().addNamespace(
- "http://www.aiim.org/pdfa/ns/schema#", "pdfaSchema");
- xmp.getPDFExtensionSchema().addNamespace(
- "http://www.aiim.org/pdfa/ns/property#", "pdfaProperty");
- xmp.getPDFExtensionSchema().addNamespace(
- "http://www.aiim.org/pdfua/ns/id/", "pdfuaid");
- XMPSchema uaSchema = new XMPSchema(XMPMetadata.createXMPMetadata(),
- "pdfaSchema", "pdfaSchema", "pdfaSchema");
- uaSchema.setTextPropertyValue("schema",
- "PDF/UA Universal Accessibility Schema");
- uaSchema.setTextPropertyValue("namespaceURI",
- "http://www.aiim.org/pdfua/ns/id/");
- uaSchema.setTextPropertyValue("prefix", "pdfuaid");
- XMPSchema uaProp = new XMPSchema(XMPMetadata.createXMPMetadata(),
- "pdfaProperty", "pdfaProperty", "pdfaProperty");
- uaProp.setTextPropertyValue("name", "part");
- uaProp.setTextPropertyValue("valueType", "Integer");
- uaProp.setTextPropertyValue("category", "internal");
- uaProp.setTextPropertyValue("description",
- "Indicates, which part of ISO 14289 standard is followed");
- uaSchema.addUnqualifiedSequenceValue("property", uaProp);
- xmp.getPDFExtensionSchema().addBagValue("schemas", uaSchema);
- xmp.getPDFExtensionSchema().setPrefix("pdfuaid");
- xmp.getPDFExtensionSchema().setTextPropertyValue("part", "1");
- XmpSerializer serializer = new XmpSerializer();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- serializer.serialize(xmp, baos, true);
- PDMetadata metadata = new PDMetadata(doc);
- metadata.importXMPMetadata(baos.toByteArray());
- doc.getDocumentCatalog().setMetadata(metadata);
- } catch (IOException|TransformerException e) {
- throw new RuntimeException(e);
- }
- }
-
- private void addPdfASchema(PDDocument document, int part, String conformance) {
+ private void addPdfASchema(PDDocument document, PdfAConformance pdfAConformance, boolean isPdfUa) {
PDDocumentInformation information = document.getDocumentInformation();
XMPMetadata metadata = XMPMetadata.createXMPMetadata();
try {
+ // NOTE: These XMP metadata MUST match up with the document information dictionary
+ // to be a valid PDF/A document, As per ISO 19005-1:2005/Cor.1:2007, 6.7.2
String title = information.getTitle();
String author = information.getAuthor();
String subject = information.getSubject();
String keywords = information.getKeywords();
+ String creator = information.getCreator();
String producer = information.getProducer();
-
- // NOTE: The XMP metadata MUST match up with the document information dictionary
- // to be a valid PDF/A document.
-
- PDFAIdentificationSchema pdfaid = metadata.createAndAddPFAIdentificationSchema();
- pdfaid.setConformance(conformance);
- pdfaid.setPart(part);
+ Calendar creationDate = information.getCreationDate();
+ Calendar modDate = information.getModificationDate();
- AdobePDFSchema pdfSchema = metadata.createAndAddAdobePDFSchema();
- if (keywords != null) {
- pdfSchema.setKeywords(keywords);
- }
- if (producer != null) {
- pdfSchema.setProducer(producer);
+ if (isPdfUa && (title == null || title.isEmpty())) {
+ XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.GENERAL_PDF_ACCESSIBILITY_NO_DOCUMENT_TITLE_PROVIDED);
}
-
- XMPBasicSchema xmpBasicSchema = metadata.createAndAddXMPBasicSchema();
- xmpBasicSchema.setCreateDate(information.getCreationDate());
-
- DublinCoreSchema dc = metadata.createAndAddDublinCoreSchema();
- if (author != null) {
- dc.addCreator(author);
+
+ if (pdfAConformance != PdfAConformance.NONE) {
+ PDFAIdentificationSchema pdfaid = metadata.createAndAddPFAIdentificationSchema();
+ pdfaid.setConformance(pdfAConformance.getConformanceValue());
+ pdfaid.setPart(pdfAConformance.getPart());
+
+ AdobePDFSchema pdfSchema = metadata.createAndAddAdobePDFSchema();
+ pdfSchema.setPDFVersion(String.valueOf(pdfAConformance.getPdfVersion()));
+ if (keywords != null) {
+ pdfSchema.setKeywords(keywords);
+ }
+ if (producer != null) {
+ pdfSchema.setProducer(producer);
+ }
+
+ XMPBasicSchema xmpBasicSchema = metadata.createAndAddXMPBasicSchema();
+ if (creator != null) {
+ xmpBasicSchema.setCreatorTool(creator);
+ }
+ if (creationDate != null) {
+ xmpBasicSchema.setCreateDate(creationDate);
+ }
+ if (modDate != null) {
+ xmpBasicSchema.setModifyDate(modDate);
+ }
+
+
+ DublinCoreSchema dc = metadata.createAndAddDublinCoreSchema();
+ dc.setFormat("application/pdf");
+ if (author != null) {
+ dc.addCreator(author);
+ }
+ if (title != null) {
+ dc.setTitle(title);
+ }
+ if (subject != null) {
+ dc.setDescription(subject);
+ } else if (isPdfUa) {
+ XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.GENERAL_PDF_ACCESSIBILITY_NO_DOCUMENT_DESCRIPTION_PROVIDED);
+ }
}
- if (title != null) {
- dc.setTitle(title);
+
+ PDFAExtensionSchema pdfAExt = metadata.createAndAddPDFAExtensionSchemaWithDefaultNS();
+ pdfAExt.addNamespace("http://www.aiim.org/pdfa/ns/extension/", "pdfaExtension");
+ pdfAExt.addNamespace("http://www.aiim.org/pdfa/ns/schema#", "pdfaSchema");
+ pdfAExt.addNamespace("http://www.aiim.org/pdfa/ns/property#", "pdfaProperty");
+
+ if (pdfAConformance != PdfAConformance.NONE) {
+ // Description of Adobe PDF Schema
+ List pdfProperties = new ArrayList<>(3);
+ pdfProperties.add(
+ createPdfaProperty("internal", "The PDF file version.", "PDFVersion", "Text"));
+ pdfProperties.add(
+ createPdfaProperty("external", "Keywords.", "Keywords", "Text"));
+ pdfProperties.add(
+ createPdfaProperty("internal", "The name of the tool that created the PDF document.", "Producer", "AgentName"));
+ pdfAExt.addBagValue("schemas",
+ createPdfaSchema("Adobe PDF Schema", "http://ns.adobe.com/pdf/1.3/", "pdf", pdfProperties));
+
+ // Description of PDF/A ID Schema
+ List pdfaidProperties = new ArrayList<>(2);
+ pdfaidProperties.add(
+ createPdfaProperty("internal", "Part of PDF/A standard", "part", "Integer"));
+ pdfaidProperties.add(
+ createPdfaProperty("internal", "Conformance level of PDF/A standard", "conformance", "Text"));
+ pdfAExt.addBagValue("schemas",
+ createPdfaSchema("PDF/A ID Schema", "http://www.aiim.org/pdfa/ns/id/", "pdfaid", pdfaidProperties));
}
- if (subject != null) {
- dc.setDescription(subject);
+ if (isPdfUa) {
+ // Description of PDF/UA
+ List pdfUaProperties = new ArrayList<>(1);
+ pdfUaProperties.add(
+ createPdfaProperty("internal", "Indicates, which part of ISO 14289 standard is followed", "part", "Integer"));
+ XMPSchema pdfUa = createPdfaSchema("PDF/UA Universal Accessibility Schema", "http://www.aiim.org/pdfua/ns/id/", "pdfuaid" , pdfUaProperties);
+ pdfAExt.addBagValue("schemas", pdfUa);
+ pdfAExt.addNamespace("http://www.aiim.org/pdfua/ns/id/", "pdfuaid");
+ pdfAExt.setPrefix("pdfuaid");
+ pdfAExt.setTextPropertyValue("part", "1");
}
-
+
PDMetadata metadataStream = new PDMetadata(document);
PDMarkInfo markInfo = new PDMarkInfo();
markInfo.setMarked(true);
@@ -830,7 +825,10 @@ private void addPdfASchema(PDDocument document, int part, String conformance) {
XmpSerializer serializer = new XmpSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.serialize(metadata, baos, true);
- metadataStream.importXMPMetadata( baos.toByteArray() );
+ String xmp = baos.toString("UTF-8");
+ // Fix for bad XML generation by some transformers
+ xmp = xmp.replace(" lang=\"x-default\"", " xml:lang=\"x-default\"");
+ metadataStream.importXMPMetadata(xmp.getBytes(StandardCharsets.UTF_8));
if (_colorProfile != null) {
ByteArrayInputStream colorProfile = new ByteArrayInputStream(_colorProfile);
@@ -846,6 +844,30 @@ private void addPdfASchema(PDDocument document, int part, String conformance) {
}
}
+ // Creates an XML Schema to be used in the PDFA Extension
+ private XMPSchema createPdfaSchema(String schema, String namespace, String prefix, List properties) {
+ XMPSchema xmpSchema = new XMPSchema(XMPMetadata.createXMPMetadata(),
+ "pdfaSchema", "pdfaSchema", "pdfaSchema");
+ xmpSchema.setTextPropertyValue("schema", schema);
+ xmpSchema.setTextPropertyValue("namespaceURI", namespace);
+ xmpSchema.setTextPropertyValue("prefix", prefix);
+ for (XMPSchema property : properties) {
+ xmpSchema.addUnqualifiedSequenceValue("property", property);
+ }
+ return xmpSchema;
+ }
+
+ // Creates an XML Property to be used in the PDFA Extension
+ private XMPSchema createPdfaProperty(String category, String description, String name, String valueType) {
+ XMPSchema xmpSchema = new XMPSchema(XMPMetadata.createXMPMetadata(),
+ "pdfaProperty", "pdfaProperty", "pdfaProperty");
+ xmpSchema.setTextPropertyValue("name", name);
+ xmpSchema.setTextPropertyValue("valueType", valueType);
+ xmpSchema.setTextPropertyValue("category", category);
+ xmpSchema.setTextPropertyValue("description", description);
+ return xmpSchema;
+ }
+
// Sets the document information dictionary values from html metadata
private void setDidValues(PDDocument doc) {
PDDocumentInformation info = new PDDocumentInformation();
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfRendererBuilder.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfRendererBuilder.java
index 7b69fca75..78a7b4d7e 100644
--- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfRendererBuilder.java
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfRendererBuilder.java
@@ -173,6 +173,7 @@ public PdfRendererBuilder usePdfVersion(float version) {
*/
public PdfRendererBuilder usePdfAConformance(PdfAConformance pdfAConformance) {
this.state._pdfAConformance = pdfAConformance;
+ this.state._pdfVersion = pdfAConformance.getPdfVersion();
return this;
}
@@ -301,18 +302,20 @@ public PdfRendererBuilder useSlowMode() {
* PDF/A-1, PDF/A-2 and PDF/A-3
*/
public enum PdfAConformance {
- NONE(-1, ""),
- PDFA_1_A(1, "A"), PDFA_1_B(1, "B"),
- PDFA_2_A(2, "A"), PDFA_2_B(2, "B"), PDFA_2_U(2, "U"),
- PDFA_3_A(3, "A"), PDFA_3_B(3, "B"), PDFA_3_U(3, "U");
+ NONE(-1, "", 0f),
+ PDFA_1_A(1, "A", 1.4f), PDFA_1_B(1, "B", 1.4f),
+ PDFA_2_A(2, "A", 1.7f), PDFA_2_B(2, "B", 1.7f), PDFA_2_U(2, "U", 1.7f),
+ PDFA_3_A(3, "A", 1.7f), PDFA_3_B(3, "B", 1.7f), PDFA_3_U(3, "U", 1.7f);
- PdfAConformance(int part, String value) {
+ PdfAConformance(int part, String value, float pdfVersion) {
this.part = part;
this.value = value;
+ this.pdfVersion = pdfVersion;
}
private final int part;
private final String value;
+ private final float pdfVersion;
public String getConformanceValue() {
return this.value;
@@ -321,6 +324,10 @@ public String getConformanceValue() {
public int getPart() {
return this.part;
}
+
+ public float getPdfVersion() {
+ return this.pdfVersion;
+ }
}
}