Module: Einvoicing::Formats::FacturX
- Defined in:
- lib/einvoicing/formats/facturx.rb
Overview
Embeds a CII XML document into an existing PDF to produce a Factur-X
PDF/A-3 file. Requires the hexapdf gem.
The embedded file is named "factur-x.xml" and tagged as the primary associated file (AFRelationship: Data). XMP metadata is updated to declare PDF/A-3b conformance and the Factur-X extension schema.
Constant Summary collapse
- FILENAME =
"factur-x.xml"- CONFORMANCE =
"EN 16931"- PROFILE_URN =
"urn:factur-x.eu:1p0:en16931"- FX_NAMESPACE =
"urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#"- FX_PREFIX =
"fx"- MIME_TYPE =
"text/xml"- DATA_DIR =
File.("../data", __dir__)
Class Method Summary collapse
-
.embed(pdf_data, xml_string, profile: CONFORMANCE) ⇒ String
Embed CII XML into a PDF binary and return the Factur-X PDF binary.
Class Method Details
.embed(pdf_data, xml_string, profile: CONFORMANCE) ⇒ String
Embed CII XML into a PDF binary and return the Factur-X PDF binary.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/einvoicing/formats/facturx.rb', line 32 def self.(pdf_data, xml_string, profile: CONFORMANCE) unless pdf_data.to_s.b.start_with?("%PDF-") raise ArgumentError, Einvoicing::I18n.t("formats.invalid_pdf") end require "hexapdf" io = StringIO.new(pdf_data.dup.force_encoding("BINARY")) doc = HexaPDF::Document.new(io: io) xml_bytes = xml_string.encode("UTF-8").b # 1. Embed the XML as an embedded file stream. ef_stream = doc.add({ Type: :EmbeddedFile, Subtype: "text/xml", Params: { Size: xml_bytes.bytesize, CheckSum: md5(xml_bytes) } }) ef_stream.set_filter(:FlateDecode) ef_stream.stream = xml_bytes filespec = doc.add({ Type: :Filespec, F: FILENAME, UF: FILENAME, AFRelationship: :Data, Desc: "Factur-X invoice", EF: { F: ef_stream, UF: ef_stream } }) # 2. Register in the EmbeddedFiles name tree. doc.catalog[:Names] ||= doc.add({}) names_dict = doc.catalog[:Names] names_dict[:EmbeddedFiles] ||= doc.add({ Names: [] }) names_dict[:EmbeddedFiles][:Names] << FILENAME << filespec # 3. Set AF array on the catalog. doc.catalog[:AF] = [ filespec ] # 4. Add OutputIntent (required for PDF/A-3 conformance). add_output_intent(doc) # 5. Update XMP metadata. update_xmp(doc, profile) # 6. Write back to binary string. out = StringIO.new("".b) doc.write(out) result = out.string # PDF/A-3 requires %PDF-1.x header (PDF 2.0 is not permitted). # HexaPDF preserves the source version in the written header, so # patch it here if the source was PDF 2.0. result.sub!(/\A%PDF-2\.\d/, "%PDF-1.7") result end |