diff --git a/openpdf-core/src/main/java/org/openpdf/text/pdf/PdfWriter.java b/openpdf-core/src/main/java/org/openpdf/text/pdf/PdfWriter.java index 89a2f44a2..d87953eb9 100644 --- a/openpdf-core/src/main/java/org/openpdf/text/pdf/PdfWriter.java +++ b/openpdf-core/src/main/java/org/openpdf/text/pdf/PdfWriter.java @@ -386,6 +386,30 @@ public class PdfWriter extends DocWriter implements * PDFA-1B level. */ public static final int PDFA1B = 4; + /** + * PDFA-2A level. + */ + public static final int PDFA2A = 5; + /** + * PDFA-2B level. + */ + public static final int PDFA2B = 6; + /** + * PDFA-2U level. + */ + public static final int PDFA2U = 7; + /** + * PDFA-3A level. + */ + public static final int PDFA3A = 8; + /** + * PDFA-3B level. + */ + public static final int PDFA3B = 9; + /** + * PDFA-3U level. + */ + public static final int PDFA3U = 10; /** * No encryption */ @@ -1790,6 +1814,9 @@ public void setPDFXConformance(int pdfx) { } if (pdfx == PDFA1A || pdfx == PDFA1B) { setPdfVersion(VERSION_1_4); + } else if (pdfx == PDFA2A || pdfx == PDFA2B || pdfx == PDFA2U + || pdfx == PDFA3A || pdfx == PDFA3B || pdfx == PDFA3U) { + setPdfVersion(VERSION_1_7); } else if (pdfx != PDFXNONE) { setPdfVersion(VERSION_1_3); } @@ -1809,6 +1836,33 @@ public boolean isPdfA1() { return pdfxConformance.isPdfA1(); } + /** + * Checks if the PDF has to be in conformance with PDFA2 + * + * @return true if the PDF has to be in conformance with PDFA2 + */ + public boolean isPdfA2() { + return pdfxConformance.isPdfA2(); + } + + /** + * Checks if the PDF has to be in conformance with PDFA3 + * + * @return true if the PDF has to be in conformance with PDFA3 + */ + public boolean isPdfA3() { + return pdfxConformance.isPdfA3(); + } + + /** + * Checks if the PDF has to be in conformance with any PDF/A version + * + * @return true if the PDF has to be in conformance with any PDF/A version + */ + public boolean isPdfA() { + return pdfxConformance.isPdfA(); + } + /** * Sets the values of the output intent dictionary. Null values are allowed to suppress any key. * @@ -1843,7 +1897,7 @@ public void setOutputIntents(String outputConditionIdentifier, String outputCond } PdfName intentSubtype; - if (pdfxConformance.isPdfA1() || "PDFA/1".equals(outputCondition)) { + if (pdfxConformance.isPdfA() || "PDFA/1".equals(outputCondition)) { intentSubtype = PdfName.GTS_PDFA1; } else { intentSubtype = PdfName.GTS_PDFX; diff --git a/openpdf-core/src/main/java/org/openpdf/text/pdf/internal/PdfXConformanceImp.java b/openpdf-core/src/main/java/org/openpdf/text/pdf/internal/PdfXConformanceImp.java index 8d80426c7..d70beb5b8 100644 --- a/openpdf-core/src/main/java/org/openpdf/text/pdf/internal/PdfXConformanceImp.java +++ b/openpdf-core/src/main/java/org/openpdf/text/pdf/internal/PdfXConformanceImp.java @@ -278,8 +278,39 @@ public boolean isPdfA1A() { return pdfxConformance == PdfWriter.PDFA1A; } + /** + * Checks if the PDF has to be in conformance with PDFA2 + * + * @return true if the PDF has to be in conformance with PDFA2 + */ + public boolean isPdfA2() { + return pdfxConformance == PdfWriter.PDFA2A + || pdfxConformance == PdfWriter.PDFA2B + || pdfxConformance == PdfWriter.PDFA2U; + } + + /** + * Checks if the PDF has to be in conformance with PDFA3 + * + * @return true if the PDF has to be in conformance with PDFA3 + */ + public boolean isPdfA3() { + return pdfxConformance == PdfWriter.PDFA3A + || pdfxConformance == PdfWriter.PDFA3B + || pdfxConformance == PdfWriter.PDFA3U; + } + + /** + * Checks if the PDF has to be in conformance with any PDF/A version + * + * @return true if the PDF has to be in conformance with any PDF/A version + */ + public boolean isPdfA() { + return isPdfA1() || isPdfA2() || isPdfA3(); + } + public void completeInfoDictionary(PdfDictionary info) { - if (isPdfX() && !isPdfA1()) { + if (isPdfX() && !isPdfA()) { if (info.get(PdfName.GTS_PDFXVERSION) == null) { if (isPdfX1A2001()) { info.put(PdfName.GTS_PDFXVERSION, new PdfString("PDF/X-1:2001")); @@ -301,7 +332,7 @@ public void completeInfoDictionary(PdfDictionary info) { } public void completeExtraCatalog(PdfDictionary extraCatalog) { - if (isPdfX() && !isPdfA1()) { + if (isPdfX() && !isPdfA()) { if (extraCatalog.get(PdfName.OUTPUTINTENTS) == null) { PdfDictionary out = new PdfDictionary(PdfName.OUTPUTINTENT); out.put(PdfName.OUTPUTCONDITION, new PdfString("SWOP CGATS TR 001-1995")); diff --git a/openpdf-core/src/main/java/org/openpdf/text/xml/xmp/XmpWriter.java b/openpdf-core/src/main/java/org/openpdf/text/xml/xmp/XmpWriter.java index 4c7471242..96e3dee6a 100644 --- a/openpdf-core/src/main/java/org/openpdf/text/xml/xmp/XmpWriter.java +++ b/openpdf-core/src/main/java/org/openpdf/text/xml/xmp/XmpWriter.java @@ -206,13 +206,37 @@ public XmpWriter(OutputStream os, PdfDictionary info, int PdfXConformance) throw addRdfDescription(basic); } if (PdfXConformance == PdfWriter.PDFA1A || PdfXConformance == PdfWriter.PDFA1B) { - PdfA1Schema a1 = new PdfA1Schema(); + PdfA1Schema pdfA1Schema = new PdfA1Schema(); if (PdfXConformance == PdfWriter.PDFA1A) { - a1.addConformance("A"); + pdfA1Schema.addConformance("A"); } else { - a1.addConformance("B"); + pdfA1Schema.addConformance("B"); } - addRdfDescription(a1); + addRdfDescription(pdfA1Schema); + } else if (PdfXConformance == PdfWriter.PDFA2A || PdfXConformance == PdfWriter.PDFA2B + || PdfXConformance == PdfWriter.PDFA2U) { + PdfA1Schema pdfA2Schema = new PdfA1Schema(); + pdfA2Schema.addPart("2"); + if (PdfXConformance == PdfWriter.PDFA2A) { + pdfA2Schema.addConformance("A"); + } else if (PdfXConformance == PdfWriter.PDFA2B) { + pdfA2Schema.addConformance("B"); + } else { + pdfA2Schema.addConformance("U"); + } + addRdfDescription(pdfA2Schema); + } else if (PdfXConformance == PdfWriter.PDFA3A || PdfXConformance == PdfWriter.PDFA3B + || PdfXConformance == PdfWriter.PDFA3U) { + PdfA1Schema pdfA3Schema = new PdfA1Schema(); + pdfA3Schema.addPart("3"); + if (PdfXConformance == PdfWriter.PDFA3A) { + pdfA3Schema.addConformance("A"); + } else if (PdfXConformance == PdfWriter.PDFA3B) { + pdfA3Schema.addConformance("B"); + } else { + pdfA3Schema.addConformance("U"); + } + addRdfDescription(pdfA3Schema); } } } diff --git a/openpdf-core/src/test/java/org/openpdf/text/pdf/PdfAConformanceTest.java b/openpdf-core/src/test/java/org/openpdf/text/pdf/PdfAConformanceTest.java new file mode 100644 index 000000000..1c7f9543b --- /dev/null +++ b/openpdf-core/src/test/java/org/openpdf/text/pdf/PdfAConformanceTest.java @@ -0,0 +1,324 @@ +package org.openpdf.text.pdf; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import org.openpdf.text.Document; +import org.openpdf.text.DocumentException; +import org.openpdf.text.PageSize; +import org.openpdf.text.Phrase; +import org.openpdf.text.pdf.internal.PdfXConformanceImp; +import org.openpdf.text.xml.xmp.PdfA1Schema; +import org.openpdf.text.xml.xmp.XmpWriter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Tests for PDF/A-2 and PDF/A-3 conformance levels. + */ +class PdfAConformanceTest { + + @Test + void testPdfA1AConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + writer.setPDFXConformance(PdfWriter.PDFA1A); + + assertThat(writer.isPdfA1()).isTrue(); + assertThat(writer.isPdfA2()).isFalse(); + assertThat(writer.isPdfA3()).isFalse(); + assertThat(writer.isPdfA()).isTrue(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFA1A); + } + + @Test + void testPdfA1BConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + writer.setPDFXConformance(PdfWriter.PDFA1B); + + assertThat(writer.isPdfA1()).isTrue(); + assertThat(writer.isPdfA2()).isFalse(); + assertThat(writer.isPdfA3()).isFalse(); + assertThat(writer.isPdfA()).isTrue(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFA1B); + } + + @Test + void testPdfA2AConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + writer.setPDFXConformance(PdfWriter.PDFA2A); + + assertThat(writer.isPdfA1()).isFalse(); + assertThat(writer.isPdfA2()).isTrue(); + assertThat(writer.isPdfA3()).isFalse(); + assertThat(writer.isPdfA()).isTrue(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFA2A); + } + + @Test + void testPdfA2BConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + writer.setPDFXConformance(PdfWriter.PDFA2B); + + assertThat(writer.isPdfA1()).isFalse(); + assertThat(writer.isPdfA2()).isTrue(); + assertThat(writer.isPdfA3()).isFalse(); + assertThat(writer.isPdfA()).isTrue(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFA2B); + } + + @Test + void testPdfA2UConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + writer.setPDFXConformance(PdfWriter.PDFA2U); + + assertThat(writer.isPdfA1()).isFalse(); + assertThat(writer.isPdfA2()).isTrue(); + assertThat(writer.isPdfA3()).isFalse(); + assertThat(writer.isPdfA()).isTrue(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFA2U); + } + + @Test + void testPdfA3AConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + writer.setPDFXConformance(PdfWriter.PDFA3A); + + assertThat(writer.isPdfA1()).isFalse(); + assertThat(writer.isPdfA2()).isFalse(); + assertThat(writer.isPdfA3()).isTrue(); + assertThat(writer.isPdfA()).isTrue(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFA3A); + } + + @Test + void testPdfA3BConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + writer.setPDFXConformance(PdfWriter.PDFA3B); + + assertThat(writer.isPdfA1()).isFalse(); + assertThat(writer.isPdfA2()).isFalse(); + assertThat(writer.isPdfA3()).isTrue(); + assertThat(writer.isPdfA()).isTrue(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFA3B); + } + + @Test + void testPdfA3UConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + writer.setPDFXConformance(PdfWriter.PDFA3U); + + assertThat(writer.isPdfA1()).isFalse(); + assertThat(writer.isPdfA2()).isFalse(); + assertThat(writer.isPdfA3()).isTrue(); + assertThat(writer.isPdfA()).isTrue(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFA3U); + } + + @Test + void testPdfXNoneConformance() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + // Default should be PDFXNONE + + assertThat(writer.isPdfA1()).isFalse(); + assertThat(writer.isPdfA2()).isFalse(); + assertThat(writer.isPdfA3()).isFalse(); + assertThat(writer.isPdfA()).isFalse(); + assertThat(writer.isPdfX()).isFalse(); + assertThat(writer.getPDFXConformance()).isEqualTo(PdfWriter.PDFXNONE); + } + + @Test + void testPdfXConformanceImpPdfA2() { + PdfXConformanceImp imp = new PdfXConformanceImp(); + + imp.setPDFXConformance(PdfWriter.PDFA2A); + assertThat(imp.isPdfA2()).isTrue(); + assertThat(imp.isPdfA1()).isFalse(); + assertThat(imp.isPdfA3()).isFalse(); + assertThat(imp.isPdfA()).isTrue(); + + imp.setPDFXConformance(PdfWriter.PDFA2B); + assertThat(imp.isPdfA2()).isTrue(); + assertThat(imp.isPdfA()).isTrue(); + + imp.setPDFXConformance(PdfWriter.PDFA2U); + assertThat(imp.isPdfA2()).isTrue(); + assertThat(imp.isPdfA()).isTrue(); + } + + @Test + void testPdfXConformanceImpPdfA3() { + PdfXConformanceImp imp = new PdfXConformanceImp(); + + imp.setPDFXConformance(PdfWriter.PDFA3A); + assertThat(imp.isPdfA3()).isTrue(); + assertThat(imp.isPdfA1()).isFalse(); + assertThat(imp.isPdfA2()).isFalse(); + assertThat(imp.isPdfA()).isTrue(); + + imp.setPDFXConformance(PdfWriter.PDFA3B); + assertThat(imp.isPdfA3()).isTrue(); + assertThat(imp.isPdfA()).isTrue(); + + imp.setPDFXConformance(PdfWriter.PDFA3U); + assertThat(imp.isPdfA3()).isTrue(); + assertThat(imp.isPdfA()).isTrue(); + } + + @Test + void testXmpWriterPdfA2A() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDictionary info = new PdfDictionary(); + info.put(PdfName.TITLE, new PdfString("Test Document")); + try (XmpWriter xmpWriter = new XmpWriter(baos, info, PdfWriter.PDFA2A)) { + // XmpWriter should complete successfully for PDF/A-2A + } + String xmpContent = baos.toString("UTF-8"); + assertThat(xmpContent).contains("pdfaid:part"); + assertThat(xmpContent).contains(">2<"); // Part should be 2 + assertThat(xmpContent).contains(">A<"); // Conformance should be A + } + + @Test + void testXmpWriterPdfA2B() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDictionary info = new PdfDictionary(); + info.put(PdfName.TITLE, new PdfString("Test Document")); + try (XmpWriter xmpWriter = new XmpWriter(baos, info, PdfWriter.PDFA2B)) { + // XmpWriter should complete successfully for PDF/A-2B + } + String xmpContent = baos.toString("UTF-8"); + assertThat(xmpContent).contains("pdfaid:part"); + assertThat(xmpContent).contains(">2<"); // Part should be 2 + assertThat(xmpContent).contains(">B<"); // Conformance should be B + } + + @Test + void testXmpWriterPdfA2U() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDictionary info = new PdfDictionary(); + info.put(PdfName.TITLE, new PdfString("Test Document")); + try (XmpWriter xmpWriter = new XmpWriter(baos, info, PdfWriter.PDFA2U)) { + // XmpWriter should complete successfully for PDF/A-2U + } + String xmpContent = baos.toString("UTF-8"); + assertThat(xmpContent).contains("pdfaid:part"); + assertThat(xmpContent).contains(">2<"); // Part should be 2 + assertThat(xmpContent).contains(">U<"); // Conformance should be U + } + + @Test + void testXmpWriterPdfA3A() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDictionary info = new PdfDictionary(); + info.put(PdfName.TITLE, new PdfString("Test Document")); + try (XmpWriter xmpWriter = new XmpWriter(baos, info, PdfWriter.PDFA3A)) { + // XmpWriter should complete successfully for PDF/A-3A + } + String xmpContent = baos.toString("UTF-8"); + assertThat(xmpContent).contains("pdfaid:part"); + assertThat(xmpContent).contains(">3<"); // Part should be 3 + assertThat(xmpContent).contains(">A<"); // Conformance should be A + } + + @Test + void testXmpWriterPdfA3B() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDictionary info = new PdfDictionary(); + info.put(PdfName.TITLE, new PdfString("Test Document")); + try (XmpWriter xmpWriter = new XmpWriter(baos, info, PdfWriter.PDFA3B)) { + // XmpWriter should complete successfully for PDF/A-3B + } + String xmpContent = baos.toString("UTF-8"); + assertThat(xmpContent).contains("pdfaid:part"); + assertThat(xmpContent).contains(">3<"); // Part should be 3 + assertThat(xmpContent).contains(">B<"); // Conformance should be B + } + + @Test + void testXmpWriterPdfA3U() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDictionary info = new PdfDictionary(); + info.put(PdfName.TITLE, new PdfString("Test Document")); + try (XmpWriter xmpWriter = new XmpWriter(baos, info, PdfWriter.PDFA3U)) { + // XmpWriter should complete successfully for PDF/A-3U + } + String xmpContent = baos.toString("UTF-8"); + assertThat(xmpContent).contains("pdfaid:part"); + assertThat(xmpContent).contains(">3<"); // Part should be 3 + assertThat(xmpContent).contains(">U<"); // Conformance should be U + } + + @Test + void testPdfA1SchemaCanSetPart2() { + PdfA1Schema schema = new PdfA1Schema(); + schema.addPart("2"); + schema.addConformance("B"); + String content = schema.toString(); + assertThat(content).contains("pdfaid:part"); + assertThat(content).contains("pdfaid:conformance"); + } + + @Test + void testPdfA1SchemaCanSetPart3() { + PdfA1Schema schema = new PdfA1Schema(); + schema.addPart("3"); + schema.addConformance("U"); + String content = schema.toString(); + assertThat(content).contains("pdfaid:part"); + assertThat(content).contains("pdfaid:conformance"); + } + + @Test + void testPdfAConformanceCannotBeSetAfterDocumentOpen() throws DocumentException, IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Document document = new Document(PageSize.A4); + PdfWriter writer = PdfWriter.getInstance(document, baos); + document.open(); + // Add a simple rectangle so the document has content without using fonts + PdfContentByte cb = writer.getDirectContent(); + cb.rectangle(100, 100, 200, 200); + cb.stroke(); + + assertThatThrownBy(() -> writer.setPDFXConformance(PdfWriter.PDFA2A)) + .isInstanceOf(PdfXConformanceException.class); + + document.close(); + } + + @Test + void testConstantValues() { + // Verify the constant values are as expected + assertThat(PdfWriter.PDFXNONE).isEqualTo(0); + assertThat(PdfWriter.PDFX1A2001).isEqualTo(1); + assertThat(PdfWriter.PDFX32002).isEqualTo(2); + assertThat(PdfWriter.PDFA1A).isEqualTo(3); + assertThat(PdfWriter.PDFA1B).isEqualTo(4); + assertThat(PdfWriter.PDFA2A).isEqualTo(5); + assertThat(PdfWriter.PDFA2B).isEqualTo(6); + assertThat(PdfWriter.PDFA2U).isEqualTo(7); + assertThat(PdfWriter.PDFA3A).isEqualTo(8); + assertThat(PdfWriter.PDFA3B).isEqualTo(9); + assertThat(PdfWriter.PDFA3U).isEqualTo(10); + } +}