Files
InvoiceShelf/config/pdf.php
Darko Gjorgjijoski 773670c18f feat(pdf): archival PDF/A output and document properties (#732)
Generated files carried no document properties at all, so an archive of them
showed a column of blank titles and no author. Title, Subject, Author and
Creator are now written from the document number and company, on both drivers:
dompdf via addInfo(), Gotenberg via metadata().

dompdf needed more than the API call. It reads Title from the <title> element
during render(), which happens after addInfo(), so metadata set through the API
alone was silently overwritten by whatever the template put there and the two
drivers disagreed about what the file was called. The title is written into the
markup as well, escaped.

Also adds an archival format setting for Gotenberg: off, PDF/A-1b, -2b or -3b.
PDF/A-3 is what the EU e-invoicing formats expect. Verified against a stock
gotenberg:8 -- LibreOffice inside the image does the conversion and the output
carries the right pdfaid:part in its XMP -- so no extra components are needed.

A fixed list rather than free text, because the SDK forwards whatever it is
given and an unsupported value would surface only as an HTTP error from the
service at render time. Empty is a real choice meaning an ordinary PDF, so it
overrides an env default rather than falling through it.

Gotenberg only: dompdf cannot produce PDF/A.

Claude-Session: https://claude.ai/code/session_01QmECndmNZwzN65Zz9P87dF
2026-08-01 14:37:22 +02:00

88 lines
3.4 KiB
PHP

<?php
return [
/*
|--------------------------------------------------------------------------
| Default PDF Driver
|--------------------------------------------------------------------------
| Here you may specify which of the PDF drivers below you wish to use as
| your default driver for all PDF generation.
|
*/
'driver' => env('PDF_DRIVER', 'dompdf'),
/*
|--------------------------------------------------------------------------
| Page Setup
|--------------------------------------------------------------------------
| Geometry applied to every document, whichever driver renders it. Sizes and
| margins are CSS lengths (pt, px, pc, mm, cm, in) because that is the only
| notation both drivers accept without loss — Gotenberg has no named sizes,
| and dompdf's points array can express anything a name can.
|
| The 1.2cm margin default is dompdf's own, from its user-agent stylesheet.
| Gotenberg used to be hardcoded to zero, so the same template came out
| edge-to-edge on one driver and inset on the other; matching dompdf keeps
| existing documents looking as they always have.
|
*/
'page' => [
'paper_width' => env('PDF_PAPER_WIDTH', '210mm'),
'paper_height' => env('PDF_PAPER_HEIGHT', '297mm'),
'orientation' => env('PDF_ORIENTATION', 'portrait'),
'margin_top' => env('PDF_MARGIN_TOP', '1.2cm'),
'margin_right' => env('PDF_MARGIN_RIGHT', '1.2cm'),
'margin_bottom' => env('PDF_MARGIN_BOTTOM', '1.2cm'),
'margin_left' => env('PDF_MARGIN_LEFT', '1.2cm'),
/*
* Repeat "page / total" at the foot of every page. Gotenberg only:
* Chromium repeats a footer template and substitutes the counts, and
* dompdf has no equivalent. Off by default so existing documents are
* unchanged. The footer draws inside the bottom margin, so it needs one.
*/
'page_numbers' => env('PDF_PAGE_NUMBERS', false),
],
/*
|--------------------------------------------------------------------------
| PDF Connections
|--------------------------------------------------------------------------
|
| Here are each of the connections setup for your application. Example
| configuration has been included, but you may add as many connections as
| you would like.
|
*/
'connections' => [
'dompdf' => [],
'gotenberg' => [
'host' => env('GOTENBERG_HOST', 'http://pdf:3000'),
/*
* Archival conformance, converted by LibreOffice inside the Gotenberg
* image. Empty means an ordinary PDF. PDF/A-3 is what the EU
* e-invoicing formats ask for. Gotenberg only: dompdf cannot produce
* PDF/A.
*/
'pdfa' => env('GOTENBERG_PDFA'),
/*
* Gotenberg usually runs as a sidecar on a private network, which the
* SSRF guard rejects. Name that one host here to exempt it — e.g.
* GOTENBERG_ALLOWED_PRIVATE_HOST=http://pdf:3000. Only this exact value
* is exempt; the guard still blocks every other private target, so the
* host setting cannot be repointed at an internal service. No default:
* the `host` fallback above must never be trusted implicitly.
*/
'allowed_private_host' => env('GOTENBERG_ALLOWED_PRIVATE_HOST'),
],
],
];