PDFlib Cookbook

cookbook

pdfa/facturx_add_xml_to_pdfa3b

Factur-X (=ZUGFeRD 2.1): add XML invoice to PDF.

Download PHP Code  Switch to Java Code  Show Output  Show Input (Factur-X-invoice.xml)  Show Input (Factur-X_invoice_noxml.pdf) 

<?php
/*
 * Factur-X (=ZUGFeRD 2.1): add XML invoice to PDF.
 * Import the pages of a plain PDF/A invoice with PDI and add a
 * corresponding XML invoice to the output file. Note that the application
 * must take care that the imported PDF invoice, the XMP file and the XML
 * invoice match.
 *
 * The XML invoice uses the Factur-X profile "EN 16931 (COMFORT)". This must
 * match the Factur-X profile that is stored in the XMP input file.
 *
 * Required software: PDFlib+PDI/PPS 9
 * Required data: PDF/A input document, XML invoice file, XMP metadata
 */

/* This is where the data files are. Adjust as necessary. */
$searchpath = dirname(__FILE__,3) . "/input";

/* By default annotations are also imported. In some cases this
 * requires the Noto fonts for creating annotation appearance streams.
 * We therefore set the searchpath to also point to the font directory.
 */
$fontpath = dirname(__FILE__,3)."/resource/font";

$outfile = "";
$infile = "Factur-X_invoice_noxml.pdf";
$title = "Factur-X (=ZUGFeRD 2.1) add XML Invoice to PDF";
$xmpfile = "Factur-X_extension_schema.xmp";
$xmlinvoicefile = "Factur-X-invoice.xml";
$attachmentname = "factur-x.xml";

$exitcode = 0;

try {
    $p = new pdflib();

    $p->set_option("searchpath={" . $searchpath . "}");

    $p->set_option("searchpath={" . $fontpath . "}");

    /* This means we must check return values of load_font() etc. */
    $p->set_option("errorpolicy=return");

    if ($p->begin_document($outfile, "pdfa=PDF/A-3b metadata={filename={"
        . $xmpfile . "}}") == 0)
        throw new Exception("Error: " . $p->get_errmsg());

    /* Open the input PDF */
    $indoc = $p->open_pdi_document($infile, "");
    if ($indoc == 0) {
        throw new Exception("Error: " . $p->get_errmsg());
    }
    
    /*
     * Clone PDF/A or PDF/X output intent
     */
    if ($p->process_pdi($indoc, -1, "action=copyoutputintent") == 0) {
        throw new Exception("Error: " . $p->get_errmsg());
    }

    $p->set_info("Creator", "PDFlib Cookbook");
    $p->set_info("Title", $title);
    
    $endpage = $p->pcos_get_number($indoc, "length:pages");

    /* Loop over all pages of the input document */
    for ($pageno = 1; $pageno <= $endpage; $pageno++) {
        $page = $p->open_pdi_page($indoc, $pageno, "");

        if ($page == -1) {
            throw new Exception("Error: " . $p->get_errmsg());
        }
        
        /* Page size will be adjusted by fit_pdi_page() */
        $p->begin_page_ext(0, 0, "width=a4.width height=a4.height");

        /*
        * Place the imported page on the output page, and adjust the
        * page size
        */
        $p->fit_pdi_page($page, 0, 0, "adjustpage");
        $p->close_pdi_page($page);

        $p->end_page_ext("");
    }
    
    /*
     * Clone XMP metadata of input document if present. In order to
     * be able to merge the XMP from the input document with the
     * XMP needed according to Factur-X for identifying the
     * XML invoice, the XMP from the input document is supplied
     * to the "metadata" option of PDF_end_document(), while the
     * Facture-X XMP was supplied to the "metadata" option of
     * PDF_begin_document().
     */
    $pfv_xmpfile = "/xmp/document.xmp";
    if (!$p->pcos_get_string($indoc, "type:/Root/Metadata") == "stream"){
    throw new Exception(
            "Error: XMP metadata missing in input document");
    }
    
    $xmp = $p->pcos_get_stream($indoc, "", "/Root/Metadata");
    $p->create_pvf($pfv_xmpfile, $xmp, "");
    $xmp_optlist = "metadata={filename=" . $pfv_xmpfile . "}";
    
    $p->close_pdi_document($indoc);

    /*
     * Load the XML file for the invoice, and associate it with the
     * document with relationship "Alternative". Also mark it as an
     * attachment that can be retrieved with Acrobat's normal attachment
     * dialog.
     * The name of the attachment is explicitly specified, as the
     * name of the disk file differs from the attachment name
     * required by Factur-X.
     */
    $xml_invoice = $p->load_asset("Attachment", $xmlinvoicefile,
    "mimetype=text/xml "
            . "description={Invoice data in Factur-X XML format} "
            . "relationship=Alternative documentattachment=true "
            . "name=" . $attachmentname);

    if ($xml_invoice == 0) {
    throw new Exception("Error: " . $p->get_errmsg());
    }
    
    $p->end_document("associatedfiles={" . $xml_invoice . "} "
                    . $xmp_optlist);
    
    $p->delete_pvf($pfv_xmpfile);

    $buf = $p->get_buffer();
    $len = strlen($buf);

    header("Content-type: application/pdf");
    header("Content-Length: $len");
    header("Content-Disposition: inline; filename=facturx_add_xml_to_pdfa3b.pdf");
    print $buf;
}
catch (PDFlibException $e) {
        echo("PDFlib exception occurred in facturx_add_xml_to_pdfa3b sample:\n" .
        "[" . $e->get_errnum() . "] " . $e->get_apiname() . ": " .
        $e->get_errmsg() . "\n");
        exit(1);
}
catch (Throwable $e) {
        echo($e);
        exit(1);
}

$p = 0;
?>