Embedding files and adding custom XMP with PDF/A-3b
📌 Overview
This issue relates to two operations with PDF/A-3b - embedding files and adding custom XMP packets. As of 2025, electronic invoicing based on Zugferd will be mandatory in Germany which means that some customers will have to embed different file types in a PDF/A-3. Additionally, sometimes, when writing files with any of the PDF/A presets or PDFVersion parameter set, Mako will drop XMP properties if they cannot be parsed correctly.
Issue Description
This customer needed to create a PDF/A-3b file with a custom .xml file attachment. They also needed to add custom XMP metadata that is preserved in the PDF output. They used the XmpCore library to generate the custom XMP, which was not being parsed by Mako.
💡 Solution
Embedding an xml file is demonstrated in the sample below using the
IDocument::addEmbeddedStream()method.XMP generated by XmpCore is slightly edited by removing a rogue question mark at the start.
The example also makes use of the
setIgnoreXmp(false)feature introduced in Mako 8, which ensures that the custom XMP is preserved in the final output.
⌨️ Sample Code
Here is the C# code that was provided to the customer. You can also find it in full on our GitHub Gists page, see CustomXmlAndXmp.cs.
Embedding an xml file
IRAInputStream fileStream = IInputStream.createFromFile(mako, testFilepath + "factur-x.xml");
if (fileStream is not null && fileStream.open())
{
var metadata = new IFileSpecAsEmbeddedData.Data();
metadata.fileName = "faktur-x.xml";
metadata.description = "ZUGFeRD-invoice";
metadata.mimeType = "text/xml";
metadata.modificationDate = DateTime.Now.ToString();
metadata.fileSize = (ulong)fileStream.IRAStream_length();
metadata.fileDataStream = fileStream;
IFileSpecAsEmbeddedData embeddedFile = IFileSpecAsEmbeddedData.createInstance(mako.getFactory(), metadata);
if (embeddedFile is not null)
document.addEmbeddedStream(embeddedFile);
}
Create and add ZUGFeRD XMP
var xmp = XmpMetaFactory.Create();
XmpMetaFactory.SchemaRegistry.RegisterNamespace(@"urn:ferd:pdfa:invoice:rc#", "zf");
var namespaces = XmpMetaFactory.SchemaRegistry.Namespaces;
xmp.SetProperty(@"urn:ferd:pdfa:invoice:rc#", "DocumentType", "INVOICE");
var options = new SerializeOptions();
options.OmitPacketWrapper = true;
options.UseCanonicalFormat = true;
var newXmp = XmpSerializerHelper.SerializeToString((XmpMeta)xmp, options);
newXmp = newXmp[1..]; //remove leading question mark
// Prepare XMP packet for Mako
var pair = mako.getTempStore().createTemporaryReaderWriterPair();
var newXmpStreamReader = pair.inputStream;
var newXmpStreamWriter = pair.outputStream;
newXmpStreamWriter.open();
newXmpStreamWriter.completeWrite(newXmp);
newXmpStreamReader.close();
assembly.setXmpPacket(newXmpStreamReader);
Ensure it is preserved in the output
var output = IPDFOutput.create(mako);
output.setPreset("PDF/A-3b");
output.setIgnoreXmp(false);
output.setValidateXmp(false);
☑️ Conclusion
Incorporating custom XMP metadata and embedding files within a PDF/A-3b document can be crucial for compliance with standards such as Zugferd, especially in regions where electronic invoicing is mandatory. By following the outlined steps and utilizing the provided sample code, users can effectively manage these tasks using Mako.
📚 Additional Resources
If you need additional help, see our API documentation for detailed information on class/method usage, or raise a support ticket via our customer portal.