Table of contents
tc-lib-pdf has conformance modes for archival (PDF/A), print exchange (PDF/X) and accessible tagged output (PDF/UA). Selecting one changes what the library emits: features the standard forbids are suppressed, the metadata it requires is written, and the problems that cannot be fixed without discarding your content are reported instead. See Conformance Warnings.
PDF/A Archival
Use the mode constructor argument to select the desired PDF/A profile:
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa1');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa1a');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa1b');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa2a');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa2b');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa2u');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa3a');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa3b');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa3u');
The mode argument is declared as string|PdfConformance, so every mode is also available as a case of the \Com\Tecnick\Pdf\PdfConformance enum:
use Com\Tecnick\Pdf\PdfConformance;
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: PdfConformance::Pdfa3b);
| Mode suffix | Conformance | Unicode ToUnicode | Tagged structure |
|---|---|---|---|
a | Level A | required | required |
b | Level B | required | not required |
u | Level U (parts 2 and 3 only) | required | not required |
Every PDF/A mode applies the restrictions of ISO 19005:
- Encryption is not permitted: an encryption object passed to the constructor is ignored, and no
/Encryptentry is written. - JavaScript is not permitted and is omitted from the output.
- PDF/A-1 does not allow live transparency, so soft masks, blend modes, and transparency groups are suppressed. PDF/A-2 and PDF/A-3 allow them.
- Embedded files are only allowed in PDF/A-3.
- Imported streams that use the
LZWDecodefilter are re-encoded withFlateDecode; see /docs/pdf-import/. - Every annotation other than a link or a popup carries a normal appearance stream. When the caller supplies none, one is generated from the annotation rectangle.
- Annotation flags carry
Printand never carryHiddenorNoView. setAnnotation()drops the subtypes the active part forbids and returns 0:3d,movie,screenandsoundin every part, and additionallycaret,fileattachment,polygon,polyline,redactandwatermarkin PDF/A-1 (ISO 19005-1 clause 6.5.2).
Hybrid Invoices: Factur-X, ZUGFeRD, Order-X
PDF/A-3 is the only part that allows arbitrary file attachments, which is what the hybrid e-invoicing formats build on: the structured XML travels as an embedded file, and the XMP metadata declares which standard it follows. setFacturX() writes both halves.
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa3');
// ... build document ...
$pdf->setFacturX(
xml: $invoiceXML,
profile: \Com\Tecnick\Pdf\HybridProfile::FacturX,
level: \Com\Tecnick\Pdf\HybridConformance::En16931,
);
The XML is embedded as an associated file with the name, MIME type and /AFRelationship the profile requires, and the PDF/A extension schema is written to the XMP metadata together with the DocumentType, DocumentFileName, Version and ConformanceLevel properties.
HybridProfile case | Standard | Payload file name |
|---|---|---|
FacturX | Factur-X 1.0.x, equivalent to ZUGFeRD 2.1 and later | factur-x.xml |
ZugferdV1 | ZUGFeRD 1.0 | ZUGFeRD-invoice.xml |
ZugferdV2 | ZUGFeRD 2.0 | zugferd-invoice.xml |
OrderX | Order-X 1.0 | order-x.xml |
The profile also determines the XMP namespace, prefix, version and document type. Those, the description and the conformance level can each be overridden through the remaining arguments.
HybridConformance supplies the ConformanceLevel property, and each case backs the literal property value: Minimum, BasicWl, Basic, Comfort, En16931, Extended and XRechnung. Not every level belongs to every profile. Minimum, BasicWl and XRechnung are Factur-X and ZUGFeRD 2 levels; Comfort belongs to ZUGFeRD 1.
The document has to be in PDF/A-3 mode; any other mode raises a warning at output time. Building the CII XML payload is out of scope, so pass the XML produced by a dedicated e-invoicing library.
For a manual attachment outside these profiles, addContentAsEmbeddedFile() takes an afrel argument accepting the \Com\Tecnick\Pdf\AFRelationship enum (Source, Data, Alternative, Supplement, Unspecified) or the equivalent string.
Runnable example: /examples/E001_invoice/
Output Intent ICC Profile
The default output intent is sRGB. The bundled sRGB.icc.z profile (stored gzip-compressed) comes from the Debian icc-profiles-free package.
setOutputIntent() replaces it with a printing condition of your own:
$pdf->setOutputIntent(
identifier: 'FOGRA39',
iccfile: '/srv/app/icc/CoatedFOGRA39.icc',
);
The ICC path goes through the same allowlist as every other local read, so it must be covered by fileOptions.allowedPaths; that key replaces the package defaults, so list the bundled font directory alongside it. See /docs/remote-resources/.
ISO 19005 permits a device color space only when the output intent defines the same space. A DeviceCMYK image or color emitted under the default sRGB intent is therefore reported through getWarnings(), as is a spot color whose Separation alternate space is DeviceCMYK. Either pass a CMYK profile to setOutputIntent() or move the artwork to another space.
PDF/X Print Exchange
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx1a');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx3');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx4');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx5');
| Mode | Min PDF version | Transparency | Process colors | GTS_PDFXVersion |
|---|---|---|---|---|
pdfx / pdfx3 | 1.3 | blocked | CMYK forced | PDF/X-3:2003 |
pdfx1a | 1.3 | blocked | CMYK forced | PDF/X-1a:2003 |
pdfx4 | 1.6 | allowed | unrestricted | PDF/X-4:2010 |
pdfx5 | 1.6 | allowed | unrestricted | PDF/X-5g:2010 |
All PDF/X modes suppress encryption and JavaScript, which ISO 15930 does not permit. A PDF/X page carries a TrimBox and no ArtBox. The interactive annotation subtypes (widget, screen, movie, sound, fileattachment and 3d) are dropped, and the annotations that remain get an appearance stream and the same flag treatment as in PDF/A.
PDF/X-4 and PDF/X-5 also require the destination profile to be embedded, so pass an ICC file to setOutputIntent(); the earlier parts accept a registered condition name on its own.
Annotations and the Bleed Box
ISO 15930 requires an annotation to sit entirely outside the bleed box. A hyperlink over body text usually breaks that rule on purpose, so the library neither moves nor drops the annotation: it records the overlap, and getWarnings() returns the list once the document has been rendered.
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx1a');
// ... build document ...
$out = $pdf->getOutPDFString();
foreach ($pdf->getWarnings() as $warning) {
// PDF/X: the /Link annotation on page 1 overlaps the BleedBox; ...
}
Trapping Status
ISO 15930 requires the trapping status to be known. setTrapped() accepts 'True', 'False' or 'Unknown' and writes both the Info dictionary /Trapped entry and the pdf:Trapped XMP property, which ISO 19005 requires to agree. In a PDF/X mode 'Unknown' is coerced to 'False' with a warning.
Runnable examples: /examples/E010_pdfx/ through /examples/E014_pdfx5/
PDF/UA Accessibility
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfua');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfua1');
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfua2');
When a PDF/UA mode is active, the library:
- Writes a
StructTreeRootwith aParentTree - Emits
MarkInfo << /Marked true >>in the catalog - Sets
/Langwithen-USas the fallback when no language is provided - Forces
ViewerPreferences /DisplayDocTitle true - Maps HTML heading elements to
H1throughH6, clamping levels so no heading level is skipped - Tags text runs, links, and figures with structure information
- Tags
<img>elements asFigureand writes theiraltattribute as/Alt - Emits
ActualTextentries for ligatures and special glyphs - Provides Artifact helpers for headers, footers, and other non-semantic content (
beginArtifact(),endArtifact(),addArtifactContent()) - Nests every annotation in a structure element with an
OBJRreference and a/StructParent:Formfor a widget,Linkfor a link,Annotfor the rest.PrinterMarkandPopupannotations take none - Gives a form field a
/TUdescription, falling back to the field name when thetuoption is unset
Set the language explicitly:
$pdf->setLanguage('de-DE');
// equivalent, through the full metadata array
$pdf->setLanguageArray(['a_meta_language' => 'de-DE']);
The catalog falls back to en-US, which no validator can tell apart from a deliberate choice, so a tagged document with no language set raises a warning.
An encrypted PDF/UA document has to stay extractable for assistive technology, so blocking the extract permission raises a warning too. ISO 14289 does not forbid encryption, and veraPDF refuses encrypted files, which leaves this check to the library.
Decorative or repeated content such as headers, footers, and page numbers is tagged as Artifact:
$pid = $pdf->addPage()['pid'];
$headerOperators = $pdf->graph->getLine(10, 10, 200, 10);
$pdf->addArtifactContent($headerOperators, $pid, 'Pagination', 'Header');
$footerText = $pdf->getTextCell('Page 1', 180, 280, 20, 5);
$pdf->addArtifactContent($footerText, $pid, 'Pagination', 'Footer');
In PDF/UA mode the built-in defaultPageContent() page-number footer is emitted as Artifact with /Type /Pagination /Subtype /Footer.
Layout Bounding Boxes
Structure elements that need a bounding box carry one:
Figures: a figure contained on a single page carries a
/BBoxLayout attribute. For HTML images it is computed from the image placement; for manually tagged graphics pass it toaddTaggedFigureContent():$pdf->addTaggedFigureContent($operators, $pid, 'Chart of quarterly revenue', [$x0, $y0, $x1, $y1]);Tables: the
/BBoxis measured at the closing tag and extended to cover top and bottom captions. It is omitted for tables split across a page break, where a single box would not be meaningful.
AcroForm Fields in Tagged Documents
The /DA default appearance font is resolved dynamically instead of requiring a font named helvetica, and /DR font resources are keyed by font buffer index so /DA references always resolve.
Runnable examples: /examples/E015_pdfua/ through /examples/E017_pdfua2/
Conformance Warnings
Some conformance problems cannot be fixed without discarding content the caller asked for. Those are recorded instead of raised. getWarnings() returns the list, deduplicated, and is meaningful only after getOutPDFString() has run, since part of the checking happens during serialization.
$out = $pdf->getOutPDFString();
foreach ($pdf->getWarnings() as $warning) {
$logger->warning($warning);
}
Warnings currently cover the soft mask of an image dropped in a transparency-free mode, DeviceCMYK artwork emitted against a non-CMYK output intent, an annotation overlapping the PDF/X bleed box, a character with no Unicode mapping dropped from the text, and the problems propagated by the PDF importer.
A second group of problems reaches the standard PHP error handler through trigger_error(), because they point at a missing call rather than at the content: an empty title in PDF/X or PDF/UA, a tagged document with no language, encryption requested in a mode that forbids it, a missing embedded output intent in PDF/X-4 and PDF/X-5, setFacturX() outside PDF/A-3, and a PDF/UA document that blocks content extraction.
Reproducible Output
Two runs of the same code produce different bytes by default: the creation and modification dates follow the clock, and the file identifier is random. Archival and invoicing pipelines often need the opposite, so pin all three.
$pdf->setDocCreationDate(1600000000);
$pdf->setDocModificationDate(1600000000);
$pdf->setFileId('any string, or 32 hexadecimal digits');
Both date setters take a Unix timestamp or a DateTimeInterface. setDocCreationDate() drives the Info dictionary /CreationDate entry and the xmp:CreateDate property; setDocModificationDate() drives /ModDate along with xmp:ModifyDate and xmp:MetadataDate.
setFileId() drives the trailer /ID array and the XMP xmpMM:InstanceID property. A value that is not 32 hexadecimal digits is hashed to that form. It cannot be called on an encrypted document, because the encryption key is derived from the identifier chosen at construction time.
XMP defines xmpMM:DocumentID as stable across the renditions of a document and xmpMM:InstanceID as unique to one saved instance, so the two never carry the same value. The document identifier is derived from the file identifier unless it is set explicitly:
$pdf->setDocumentId('invoice-2026-0042');
XMP Metadata
The XMP packet ends with about 2 KB of padding, which is what lets a reader rewrite the metadata in place. Documents that are never edited after generation can drop it, which also declares the packet read-only:
$pdf->setXMPPaddingLines(0); // 100 bytes per line, capped at 200 lines
setCustomXMP() appends a fragment just before the closing tag of one of five insertion points, from x:xmpmeta down to the rdf:Bag of the PDF/A extension schema. Repeated calls with the same key append; pass replace: true to overwrite. The fragment is checked for XML well-formedness in the namespace context of its insertion point, so a fragment using a prefix that is not in scope there has to declare it on its own root element. DOCTYPE and ENTITY declarations are rejected.
setProducerSuffix() appends an application identification to the Info dictionary /Producer entry and the pdf:Producer XMP property, which ISO 19005 requires to be equivalent. Control characters are stripped and the suffix is truncated to 255 characters.
Runnable example: /examples/E060_custom_xmp_metadata/
Stream Compression
Compressed streams always use the FlateDecode filter, which is permitted by ISO 19005, ISO 15930, and ISO 14289. The library never emits LZWDecode, the only general-purpose filter that ISO 19005 forbids.
Compression is controlled by the compress argument of the Tcpdf constructor and is independent of the conformance mode:
// compressed output (default), in any mode
$pdf = new \Com\Tecnick\Pdf\Tcpdf(compress: true, mode: 'pdfa3b');
// uncompressed output
$pdf = new \Com\Tecnick\Pdf\Tcpdf(compress: false, mode: 'pdfa3b');
The argument applies to page content streams, appearance and form XObjects, patterns, shaders, imported objects, image ICC profiles, and palettes. Some streams do not depend on it:
- Embedded font programs and the sRGB output-intent ICC profile are always
FlateDecodeencoded. - XMP metadata is always stored uncompressed.
- PDF/A-3 file attachments are always stored uncompressed and carry the
/SubtypeMIME entry required by ISO 19005-3.
Related Guides
- Signature workflows for signed archival output: /docs/digital-signatures/
- PDF import behavior under conformance constraints: /docs/pdf-import/
- Development targets for validation pipelines: /docs/development/
Previous: /docs/digital-signatures/
Overview: /docs/
Next: /docs/pdf-import/