diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT index 0068dc13..5d560d0a 100644 --- a/CHANGELOG.TXT +++ b/CHANGELOG.TXT @@ -1,3 +1,6 @@ +6.10.0 (2025-05-27) + - Embedded files support (Factur-X 1.07 / ZUGFeRD 2.3) #789 + 6.9.5 (2025-05-27) - Automatically add destinations from HTML code #804 - Wrong default value when $table_el['old_cell_padding'] is missing #807 diff --git a/VERSION b/VERSION index e1e5d136..cf79bf90 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.9.5 +6.10.0 diff --git a/composer.json b/composer.json index 17391f14..8d779197 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "barcodes" ], "homepage": "http://www.tcpdf.org/", - "version": "6.9.4", + "version": "6.10.0", "license": "LGPL-3.0-or-later", "authors": [ { diff --git a/examples/example_068.php b/examples/example_068.php new file mode 100644 index 00000000..f3a26b0f --- /dev/null +++ b/examples/example_068.php @@ -0,0 +1,312 @@ + + * @license LGPL-3.0 + */ + +/** + * Creates an example PDF/A-3b with embedded file (Factur-X 1.07 - ZUGFeRD 2.3) + * + * @abstract TCPDF - Example: PDF/A-3b with embedded file (Factur-X 1.07 - ZUGFeRD 2.3) + * @author Nicola Asuni + * @since 2021-03-26 + * @group A-3b + * @group pdf + */ + +// Include the main TCPDF library (search for installation path). +require_once('tcpdf_include.php'); + +// create new PDF document +$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false, 3); + +// set document information +$pdf->setCreator(PDF_CREATOR); +$pdf->setAuthor('Nicola Asuni'); +$pdf->setTitle('TCPDF Example 068'); +$pdf->setSubject('TCPDF Tutorial'); +$pdf->setKeywords('TCPDF, PDF, example, test, guide'); + +// set default header data +$pdf->setHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, PDF_HEADER_TITLE . ' 068', PDF_HEADER_STRING); + +// set header and footer fonts +$pdf->setHeaderFont(array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN)); +$pdf->setFooterFont(array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA)); + +// set default monospaced font +$pdf->setDefaultMonospacedFont(PDF_FONT_MONOSPACED); + +// set margins +$pdf->setMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT); +$pdf->setHeaderMargin(PDF_MARGIN_HEADER); +$pdf->setFooterMargin(PDF_MARGIN_FOOTER); + +// set auto page breaks +$pdf->setAutoPageBreak(true, PDF_MARGIN_BOTTOM); + +// set image scale factor +$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); + +// set some language-dependent strings (optional) +if (@file_exists(__DIR__ . '/lang/eng.php')) { + require_once __DIR__ . '/lang/eng.php'; + + $pdf->setLanguageArray($l); +} + +// --------------------------------------------------------- + +// set default font subsetting mode +$pdf->setFontSubsetting(true); + +// Set font +$pdf->setFont('helvetica', '', 14, '', true); + +// Add a page +// This method has several options, check the source code documentation for more information. +$pdf->AddPage(); + +// Set some content to print +$html = <<Example of  TCPDF  document in PDF/A-3b mode. +This document conforms to the standard Factur-X 1.07 / ZUGFeRD 2.3. +

Please check the source code documentation and other examples for further information (http://www.tcpdf.org).

+HTML; + +$invoiceXml = << + + + + Baurechnung + + + urn:cen.eu:en16931:2017 + + + + 181301674 + 204 + + 20241115 + + + Rapport-Nr.: 42389 vom 01.11.2024 + +Im 2. OG BT1 Besprechungsraum eine Beamerhalterung an die Decke montiert. Dafür eine Deckenplatte ausgesägt. Beamerhalterung zur Montage auseinander gebaut. Ein Stromkabel für den Beamer, ein HDMI Kabel und ein VGA Kabel durch die Halterung gezogen. Beamerhalterung wieder zusammengebaut und Beamer montiert. Beamer verkabelt und ausgerichtet. Decke geschlossen. + + + + + + 01 + + 01 Beamermontage +Für die doppelte Verlegung, falls erforderlich. + + + + TGA Obermonteur/Monteur + + + + 43.2 + + + 43.2 + + + + 3 + + + + VAT + S + 19 + + + 129.6 + + + + + + 02 + + 02 Außerhalb Angebot + + + + Beamer-Deckenhalterung + + + + 122.5 + + + 122.5 + + + + 1 + + + + VAT + S + 19 + + + 122.5 + + + + + Liselotte Müller-Lüdenscheidt + + 549910 + ELEKTRON Industrieservice GmbH + Geschäftsführer Egon Schrempp Amtsgericht Stuttgart HRB 1234 + + 74465 + Erfurter Strasse 13 + Demoort + DE + + + DE136695976 + + + + 16259 + ConsultingService GmbH + + 76138 + Musterstr. 18 + Karlsruhe + DE + + + + per Mail vom 01.09.2024 + + + 13130162 + #ef=Aufmass.png + 916 + + + 42389 + #ef=ElektronRapport_neu-red.pdf + 916 + + + 13130162 + Projekt + + + + + + 20241101 + + + + + Rechnung 181301674 + EUR + + 58 + + DE91100000000123456789 + + + + 47.9 + VAT + 252.1 + S + 19 + + + Zahlbar sofort rein netto + + + 252.1 + 0 + 0 + 252.1 + 47.9 + 300 + 0 + 300 + + + 420 + + + + +XML; + +// Print text using writeHTMLCell() +$pdf->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, true, '', true); + +$pdf->EmbedFileFromString('factur-x.xml', $invoiceXml); + +$pdf->setExtraXMPRDF( + ' + INVOICE + factur-x.xml + 1.0 + EN 16931 +'); + +$pdf->setExtraXMPPdfaextension( + ' + Factur-X PDFA Extension Schema + urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# + fx + + + + DocumentFileName + Text + external + The name of the embedded XML document + + + DocumentType + Text + external + The type of the hybrid document in capital letters, e.g. INVOICE or ORDER + + + Version + Text + external + The actual version of the standard applying to the embedded XML document + + + ConformanceLevel + Text + external + The conformance level of the embedded XML document + + + +' +); + + +// --------------------------------------------------------- + +// Close and output PDF document +// This method has several options, check the source code documentation for more information. +$pdf->Output('example_068.pdf', 'I'); diff --git a/examples/index.php b/examples/index.php index e456e364..61826597 100644 --- a/examples/index.php +++ b/examples/index.php @@ -87,6 +87,7 @@
  • PDF/A-3b (ISO 19005-3:2012) document: [PDF]
  • Using WriteHTMLCell: [PDF]
  • Shorthand border styles including !important: [PDF]
  • +
  • PDF/A-3b with embedded file (Factur-X 1.07 - ZUGFeRD 2.3): [PDF]
  • Barcodes

    diff --git a/include/tcpdf_static.php b/include/tcpdf_static.php index 2d89212f..efadc925 100644 --- a/include/tcpdf_static.php +++ b/include/tcpdf_static.php @@ -55,7 +55,7 @@ class TCPDF_STATIC { * Current TCPDF version. * @private static */ - private static $tcpdf_version = '6.9.5'; + private static $tcpdf_version = '6.10.0'; /** * String alias for total number of pages. diff --git a/tcpdf.php b/tcpdf.php index 344104ac..42c425e9 100644 --- a/tcpdf.php +++ b/tcpdf.php @@ -1,7 +1,7 @@ * @package com.tecnick.tcpdf * @author Nicola Asuni - * @version 6.9.5 + * @version 6.10.0 */ // TCPDF configuration @@ -128,7 +128,7 @@ * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.
    * @package com.tecnick.tcpdf * @brief PHP class for generating PDF documents without requiring external extensions. - * @version 6.9.5 + * @version 6.10.0 * @author Nicola Asuni - info@tecnick.com * @IgnoreAnnotation("protected") * @IgnoreAnnotation("public") @@ -1810,6 +1810,13 @@ class TCPDF { */ protected $custom_xmp_rdf = ''; + /** + * Custom XMP RDF pdfaextension data. + * @protected + * @since 6.9.0 (2025-02-11) + */ + protected $custom_xmp_rdf_pdfaExtension = ''; + /** * Overprint mode array. * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008). @@ -4932,6 +4939,32 @@ public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), } } + /** + * Embed the attached files. + * @since 6.9.000 (2025-02-11) + * @public + */ + public function EmbedFile($opt) { + if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) { + if ((($opt['Subtype'] == 'FileAttachment')) AND (!TCPDF_STATIC::empty_string($opt['FS'])) + AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS'])) + AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) { + $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']); + } + } + } + + /** + * Embed the attached files. + * @since 6.9.000 (2025-02-11) + * @public + */ + public function EmbedFileFromString($filename, $content) { + if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) { + $this->embeddedfiles[$filename] = array('f' => ++$this->n, 'n' => ++$this->n, 'content' => $content ); + } + } + /** * Embedd the attached files. * @since 4.4.000 (2008-12-07) @@ -4945,7 +4978,12 @@ protected function _putEmbeddedFiles() { } reset($this->embeddedfiles); foreach ($this->embeddedfiles as $filename => $filedata) { - $data = $this->getCachedFileContents($filedata['file']); + $data = false; + if (isset($filedata['file']) && !empty($filedata['file'])) { + $data = $this->getCachedFileContents($filedata['file']); + } elseif ($filedata['content'] && !empty($filedata['content'])) { + $data = $filedata['content']; + } if ($data !== FALSE) { $rawsize = strlen($data); if ($rawsize > 0) { @@ -9630,6 +9668,17 @@ public function setExtraXMPRDF($xmp) { $this->custom_xmp_rdf = $xmp; } + /** + * Set additional XMP data to be added to the default XMP data for PDF/A extensions. + * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method! + * @param string $xmp Custom XMP RDF data. + * @since 6.9.0 (2025-02-14) + * @public + */ + public function setExtraXMPPdfaextension($xmp) { + $this->custom_xmp_rdf_pdfaExtension = $xmp; + } + /** * Put XMP data object and return ID. * @return int The object ID. @@ -9764,6 +9813,7 @@ protected function _putXMP() { $xmp .= "\t\t\t\t\t\t\t".''."\n"; $xmp .= "\t\t\t\t\t\t".''."\n"; $xmp .= "\t\t\t\t\t".''."\n"; + $xmp .= $this->custom_xmp_rdf_pdfaExtension; $xmp .= "\t\t\t\t".''."\n"; $xmp .= "\t\t\t".''."\n"; $xmp .= "\t\t".''."\n"; @@ -9802,7 +9852,11 @@ protected function _putcatalog() { } // start catalog $oid = $this->_newobj(); - $out = '<< /Type /Catalog'; + $out = '<< '; + if (!empty($this->efnames)) { + $out .= ' /AF [ '. implode(' ', $this->efnames) .' ]'; + } + $out .= ' /Type /Catalog'; $out .= ' /Version /'.$this->PDFVersion; //$out .= ' /Extensions <<>>'; $out .= ' /Pages 1 0 R'; diff --git a/tests/launch.sh b/tests/launch.sh index b3684463..1265006b 100755 --- a/tests/launch.sh +++ b/tests/launch.sh @@ -125,7 +125,7 @@ for file in $EXAMPLE_FILES; do FAILED_FLAG=1 echo "Generated-invalid-file: $file" fi - if [ "$file" = "examples/example_065.php" ] || [ "$file" = "examples/example_066.php" ]; then + if [ "$file" = "examples/example_065.php" ] || [ "$file" = "examples/example_066.php" ] || [ "$file" = "examples/example_068.php" ]; then VALIDATION_OUTPUT="$(docker run -v $TEMP_FOLDER:/data --quiet --rm -w /data/ pdfix/verapdf-validation:latest validate --format 'json' -i 'output.pdf')" VALIDATION_RESULT="$(echo $VALIDATION_OUTPUT | jq '.report.jobs[0].validationResult[0].compliant')" if [ "$VALIDATION_RESULT" = "false" ]; then