From c939a03e874b06f3121a755867293707dbcdf2bc Mon Sep 17 00:00:00 2001 From: Erras IT GmbH Date: Wed, 4 Feb 2026 14:45:03 +0100 Subject: [PATCH 1/6] Added a breaking change notice regarding the removal of hardcoded values for TypeCode (58) and Information in the getSettlementXML() method of the IZUGFeRDTradeSettlementPayment interface. This change requires that any code utilizing this interface must explicitly set the corresponding attributes. --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index ffa54fec..aa7b87ba 100644 --- a/History.md +++ b/History.md @@ -84,7 +84,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 From 96d5b0899e9b7044560417be021e9aa9770f040e Mon Sep 17 00:00:00 2001 From: Timo Jacobs Date: Wed, 11 Feb 2026 14:07:14 +0100 Subject: [PATCH 2/6] Use Flate Decode for attachments --- .../java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index 0b59b7fd..efd1d94d 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -392,7 +392,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte dict.setString("Desc", description); ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); - PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, COSName.FLATE_DECODE); // ef.addCompression(); ef.setSubtype(subType); ef.setSize(data.length); From 406be10f254d5d4968af0bae0230413f0351d724 Mon Sep 17 00:00:00 2001 From: Timo Jacobs Date: Wed, 11 Feb 2026 16:36:03 +0100 Subject: [PATCH 3/6] Fixed Unit Tests --- .../ZUGFeRD/MustangReaderWriterTest.java | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java index b7fbede6..aa85abcb 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; @@ -415,17 +419,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"; @@ -442,7 +447,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); @@ -451,12 +457,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) @@ -470,6 +478,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"; From 9ae8b5e5481ed1e72d0327295895af65a6182070 Mon Sep 17 00:00:00 2001 From: Erras IT GmbH Date: Thu, 12 Feb 2026 15:55:46 +0100 Subject: [PATCH 4/6] Quotation marks inserted Quotation marks inserted --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index aa7b87ba..42e48335 100644 --- a/History.md +++ b/History.md @@ -84,7 +84,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. **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. +- #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 From da592eea6eb8aa55e66c7198c1ab419b8a9dbf61 Mon Sep 17 00:00:00 2001 From: Daniel Airich Date: Fri, 13 Feb 2026 01:07:36 +0100 Subject: [PATCH 5/6] fix: #1038 --- .../java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index d7aab617..ea1aa211 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -850,7 +850,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 += "" + "" + From 8ce79b36789b89c0b920b4b0cfd12f17a9a3c8f6 Mon Sep 17 00:00:00 2001 From: pgjacobs Date: Thu, 26 Feb 2026 12:26:21 +0100 Subject: [PATCH 6/6] Configurable PDF Attachment Compression --- .../org/mustangproject/ZUGFeRD/DXExporterFromA1.java | 1 + .../org/mustangproject/ZUGFeRD/DXExporterFromA3.java | 6 +++--- .../org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java | 1 + .../org/mustangproject/ZUGFeRD/OXExporterFromA3.java | 7 +++++-- .../ZUGFeRD/ZUGFeRDExporterFromA3.java | 12 +++++++++++- .../ZUGFeRD/ZUGFeRDExporterFromPDFA.java | 4 ++++ 6 files changed, 25 insertions(+), 6 deletions(-) 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/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index efd1d94d..7b18ea79 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -101,6 +101,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 @@ -392,7 +395,8 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte dict.setString("Desc", description); ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); - PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, COSName.FLATE_DECODE); + COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null; + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter); // ef.addCompression(); ef.setSubtype(subType); ef.setSize(data.length); @@ -503,6 +507,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);