diff --git a/History.md b/History.md index 83c8752b..59e6f8ad 100644 --- a/History.md +++ b/History.md @@ -115,7 +115,7 @@ corrections - support parsing of BT-90 CreditorReferenceID - #871 schema validation does not ignore external entities - #868 Fix wrong version in History.md -- #729 Updates about SpecifiedTradeSettlementHeaderMonetarySummation and SpecifiedTradeSettlementPaymentMeans +- #729 Updates about SpecifiedTradeSettlementHeaderMonetarySummation and SpecifiedTradeSettlementPaymentMeans. **Breaking Change:** The hardcoded values of ram:TypeCode (58) and ram:Information ("SEPA credit transfer") in the getSettlementXML() method of the IZUGFeRDTradeSettlementPayment interface have been removed. Starting with this version, any code utilizing this interface must explicitly set the corresponding attributes. - #863 LineCalculator throws NPE if product is null (since 2.17.0) - #731 Got a broken translation key when visualizing XML into PDF (xr:Business_process_type) - #865 Add sevdesk signature to PDF creators diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA1.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA1.java index f22d76c7..86462375 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA1.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA1.java @@ -136,6 +136,7 @@ public class DXExporterFromA1 extends DXExporterFromA3 { public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) { return (DXExporterFromA1) super.setConformanceLevel(newLevel); } + @Override public DXExporterFromA1 setProducer(String producer){ return (DXExporterFromA1) super.setProducer(producer); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA3.java index 3324e36c..bc0189e8 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA3.java @@ -71,6 +71,7 @@ import jakarta.activation.FileDataSource; public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 { protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE; + protected boolean compressionEnabled = false; protected ArrayList fileAttachments = new ArrayList<>(); /** @@ -296,8 +297,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 { dict.setString("Desc", description); ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); - PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); -// ef.addCompression(); + COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null; + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter); ef.setSubtype(subType); ef.setSize(data.length); ef.setCreationDate(new GregorianCalendar()); @@ -417,7 +418,6 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 { return this; } - @Override public DXExporterFromA3 setCreator(String creator) { this.creator = creator; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java index fd4ffb4c..ca84cc26 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java @@ -56,6 +56,7 @@ public interface IZUGFeRDExporter extends Closeable, IExporter { public IZUGFeRDExporter load(InputStream pdfSource) throws IOException; public IZUGFeRDExporter setCreator(String creator); public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel); + public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean enablePDFAttachmentCompression); public IZUGFeRDExporter setProducer(String producer); public IZUGFeRDExporter setZUGFeRDVersion(int version); public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java index 5db1a693..542b4249 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java @@ -129,6 +129,9 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { private boolean attachZUGFeRDHeaders = true; + /** Defines whether attachments to the PDF should be using FLATE compression */ + private boolean compressionEnabled = false; + /** * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the * metadata level, this will not e.g. convert graphics to JPG-2000) @@ -296,8 +299,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { dict.setString("Desc", description); ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); - PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); -// ef.addCompression(); + COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null; + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter); ef.setSubtype(subType); ef.setSize(data.length); ef.setCreationDate(new GregorianCalendar()); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index e4745ef8..c9f53282 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -861,7 +861,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) { - if (profile == Profiles.getByName("XRechnung")) { + if ((profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")) || (profile == Profiles.getByName("EXTENDED"))) { for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) { xml += "" + "" + diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index d0e32bb7..e497f202 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -104,6 +104,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte private Profile profile = null; protected boolean documentPrepared = false; + /** Defines whether attachments to the PDF should be using FLATE compression */ + private boolean compressionEnabled = false; + /** * Data (XML invoice) to be added to the ZUGFeRD PDF. It may be externally set, * in which case passing a IZUGFeRDExportableTransaction is not necessary. By @@ -395,7 +398,8 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte dict.setString("Desc", description); ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); - PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); + COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null; + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter); // ef.addCompression(); ef.setSubtype(subType); ef.setSize(data.length); @@ -506,6 +510,12 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte return this; } + @Override + public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) { + this.compressionEnabled = compressionEnabled; + return this; + } + public ZUGFeRDExporterFromA3 setCreator(String creator) { this.creator = creator; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java index 12653cdf..c9519fb5 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java @@ -188,6 +188,10 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter { return getExporter().setConformanceLevel(newLevel); } + public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) { + return getExporter().setEnablePDFAttachmentCompression(compressionEnabled); + } + public IZUGFeRDExporter setProducer(String producer) { return getExporter().setProducer(producer); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java index e4619802..720be396 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java @@ -23,7 +23,11 @@ import junit.framework.TestSuite; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException; import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.schema.PDFAIdentificationSchema; @@ -413,17 +417,18 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ze.export(baos); ze.close(); - String pdfContent = baos.toString(StandardCharsets.UTF_8); - assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.BASIC) >= 0); - assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.EXTENDED) >= 0); - assertTrue(pdfContent.indexOf(DocumentContextParameterTypeConstants.COMFORT) >= 0); + byte[] targetBytes = baos.toByteArray(); + String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "ZUGFeRD-invoice.xml"); + assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.BASIC)); + assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.EXTENDED)); + assertTrue(xmlContent.contains(DocumentContextParameterTypeConstants.COMFORT)); } catch (IOException e) { fail("IOException should not happen in testZExport"); } } - + public void testFXExport() throws Exception { final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20171118_506fx.pdf"; @@ -440,7 +445,8 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { result.write(buffer, 0, length); } - ze.addAdditionalFile("test.pdf", result.toByteArray()); + var bytes = result.toByteArray(); + ze.addAdditionalFile("test.pdf", bytes); ze.setTransaction(this); ze.disableAutoClose(true); @@ -449,12 +455,14 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ze.export(baos); ze.close(); - String pdfContent = baos.toString(StandardCharsets.UTF_8); - assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1); + byte[] targetBytes = baos.toByteArray(); + String pdfContent = new String(targetBytes, StandardCharsets.UTF_8); + String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "factur-x.xml"); + assertTrue(pdfContent.contains("(via mustangproject.org")); // check for pdf-a schema extension - assertFalse(pdfContent.indexOf("EN 16931") == -1); - assertFalse(pdfContent.indexOf("fx") == -1); - assertFalse(pdfContent.indexOf("urn:cen.eu:en16931:2017") == -1); + assertTrue(pdfContent.contains("EN 16931")); + assertTrue(pdfContent.contains("fx")); + assertTrue(xmlContent.contains("urn:cen.eu:en16931:2017")); } // now check the contents (like MustangReaderTest) @@ -468,6 +476,27 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { assertEquals(getNumber(), zi.getForeignReference()); } + private static String readEmbeddedXmlFromPdf(byte[] targetBytes, String xmlFilename) throws IOException { + // Read attached (compressed) XML file + try (PDDocument pdDocument = Loader.loadPDF(targetBytes)) { + PDDocumentNameDictionary names = new PDDocumentNameDictionary(pdDocument.getDocumentCatalog()); + + PDEmbeddedFilesNameTreeNode embeddedFiles = names.getEmbeddedFiles(); + Map fileMap = embeddedFiles.getNames(); + + + PDComplexFileSpecification fileSpec = fileMap.get(xmlFilename); + if (fileSpec == null) { + fail("Files does not contain a file named " + xmlFilename); + return null; + } + PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile(); + String xmlContent = new String(embeddedFile.toByteArray(), StandardCharsets.UTF_8); + System.out.println(xmlContent); + return xmlContent; + } + } + public void testExceptionOnPDF14() { final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505new.pdf";