ZUGFeRD 2.0 and Factur-X for Xojo
With the last DynaPDF update we got new support for ZUGFeRD 2.0 and Factur-X, two standards to deliver invoices as PDF files with embedded XML data. We supported ZUGFeRD 1.0 before and now upgraded code to support version 2 in addition.
So here is a little Xojo code snippet to convert an existing PDF into a ZUGFeRD 2.0 document. Before you start, please assemble the XML for the invoice and know whether it's Minimum, Basic, Comfort or Extended level. So the code here starts with a new DynaPDFMBS object and creates a new PDF environment. We set import flags to import all content and prepare for PDF/A. If needed, you could skip some content like annotations directly here.
dim pdf as new MyDynapdfMBS
dim f as FolderItem = SpecialFolder.Desktop.Child("Create PDF.pdf")
pdf.SetLicenseKey "Pro" // For this example you can use a Pro or Enterprise License
// now create PDF
call pdf.CreateNewPDF f
Dim flags As Integer
flags = Bitwise.BitOr(flags, pdf.kifImportAll)
flags = Bitwise.BitOr(flags, pdf.kifImportAsPage)
flags = Bitwise.BitOr(flags, pdf.kifPrepareForPDFA)
call pdf.SetImportFlags(flags)
Next we load an existing PDF from a container field. Of course you can also use functions to create new pages here or open an import PDF from a file on disk. The next line imports the whole PDF file we opened into our work PDF. If you repeat the open and import step, you can merge several documents here. If you need only one page, you can use DynaPDF.ImportPDFPage function.
// import PDF file
dim ImportFile as FolderItem = FindFile("invoice template.pdf")
call pdf.OpenImportFile(ImportFile)
call pdf.ImportPDFFile(1)
Per PDF/A requirement, we add here the language setting and a structure tree:
// PDF/A requires a language set
Call pdf.SetLanguage("en-US")
// PDF/A requires a structure tree
Call pdf.CreateStructureTree
If the PDF contains colorspaces which are not backed by an ICC Profile, we get an OnReplaceICCProfile event call to provide a replacement profile to the PDF engine. So if there is a colorspace missing the profile, it will be using one of our replacement profiles for RGB, Gray or CMYK. Here is an example implementation of the event, which looks for some generic profiles to replace an existing one:
EventHandler Function OnReplaceICCProfile(Type as integer, ColorSpace as integer) As integer
// provide missing ICC Profiles to DynaPDF
// The ICC profiles which should normally be configured by the user.
Dim filename As String
Select Case type
Case Me.kictGray
filename = "Generic Gray Profile.icc"
Case Me.kictRGB
filename = "Generic RGB Profile.icc"
Case Me.kictCMYK
filename = "Generic CMYK Profile.icc"
Case Me.kictLab
// not yet needed, but maybe in future
filename = "Generic Lab Profile.icc"
Else
Break
Return -1 // new type we don't know?
End Select
Dim f As FolderItem = FindFile(filename)
If f = Nil Or Not f.Exists Then
// file missing?
Return -1
End If
Dim e As Integer = ReplaceICCProfile(ColorSpace, f)
If e < 0 Then
// failed
Break
End If
// pass along success or failure
Return e
End EventHandler
There is another event for missing fonts, where you can provide a replacement font. We simply replace missing fonts with Arial
EventHandler Function OnFontNotFound(PDFFontRef as integer, FontName as string, Style as integer, StdFontIndex as integer, IsSymbolFont as boolean) As integer
// Here you could use your own mapping table.
// In this example we replace the font simply with Arial
if (WeightFromStyle(Style) < 500) then
// Only the weights 500 and 700 of Arial are installed
// by default. If you have also light variants then it is
// not required to change the style.
Style = BitwiseAnd(Style, &h0F)
Style = BitwiseOr(Style, kfsRegular)
end if
return ReplaceFont(PDFFontRef, "Arial", Style, true)
End EventHandler
Next we attach the XML invoice to the PDF. You can pass attachments as container, file or text, but please use the corresponding function. Here we pass it as text from a field, specify UTF-8 encoding and the ZUGFeRD specific file name. The attachment is associated with the main catalog as an alternative version.
// now add xml
Dim file As FolderItem = FindFile("ZUGFeRD-invoice.xml")
dim n as integer = pdf.AttachFile(file, "ZUGFeRD Rechnung", false)
if not pdf.AssociateEmbFile(pdf.kadCatalog, -1, pdf.karAlternative, n) then
Break // error
end if
Now we do the conformance check. If you licensed the PDF/A converter from us (Add-on for DynaPDF Pro), you get the PDF fixed if needed. If the PDF was already PDF/A, this should return okay and tel you which output indent is recommended. We than add the ICC Profile for RGB or CMYK to indicate how the PDF likes to be viewed.
// make sure we conform
// for perfect usage, you need PDF/A extension for DynaPDF (extra purchase)
// here we pass Basic level. Please make sure XML and level here match!
Dim retval As Integer = pdf.CheckConformance(pdf.kctZUGFeRD2_Basic, pdf.kcoDefault)
Select case retval
case 1
call pdf.AddOutputIntent(RGBProfileFile) // RGB
case 2
call pdf.AddOutputIntent(CMYKProfileFile) // CMYK
case 3
call pdf.AddOutputIntent(GrayProfileFile) // Gray
end Select
Finally we close the PDF file and let the destructor free the PDF environment.
call pdf.CloseFile
We hope you enjoy this blog post. Please check the example databases included with MBS Xojo Plugins 19.3.