Skip to content

Conformance: ConformanceMode routing and the validation boundary

NextPDF\Conformance is the single discriminator that tells the writer which International Organization for Standardization (ISO) contract a Portable Document Format (PDF) file targets. The library emits the structures that contract defines. It does not, and cannot, certify the resulting file as conformant. Only an external validator can certify conformance.

Terminal window
composer require nextpdf/core:^3

The Conformance module exposes two public types. ConformanceMode is a backed enum that names the target contract (Plain, PdfUa1, PdfUa2, PdfA2, PdfA3, PdfA3b, PdfA3u, PdfA4, PdfA4e, PdfA4f). ConformancePolicy is an immutable value object that combines a mode with independent strictness toggles.

The mode is the single source of truth for downstream writer gates. Before this enum existed, the engine inferred whether a document was spec-tagged from scattered flags. ConformanceMode::isTagged(), isAccessibility(), isArchival(), pdfaPart(), pdfaConformanceLetter(), and requiresPdf17() each return a typed answer that the writer reads directly. The catalog, /MarkInfo, file-header lineage, and Extensible Metadata Platform (XMP) pdfaid markers stay aligned with the declared intent.

Read the support boundary literally. NextPDF Core emits structures these standards define. ISO 19005-4:2020 §6.7.3 specifies the identification schema that records which PDF/A variant a file claims. ISO 19005-4:2020 states that conformance is determined as specified in its Clause 5: against the normative requirements, by a checking tool, not by the producing library. ISO 14289-2:2024 §6 frames conformity as a property a file satisfies. Setting a NextPDF mode is necessary input for a conforming file. By itself, it is not a conformance result.

This follows the same Verified-versus-Claimed discipline as the Cascading Style Sheets (CSS) support matrix. A capability is Verified only when it has both a passing test or oracle run and a cited clause. Everything else is something the library emits: useful, but not a conformance guarantee. Conformance belongs to the final file and a validator, not to a library promise. Validate output with veraPDF (see “Conformance” below).

A second boundary matters for archival work. PDF/A-4 authoring, including the OutputIntent dictionary, the embedded International Color Consortium (ICC) profile, and the XMP extension schema, ships in the nextpdf/pro extension, not in Core. In a Core-only installation, Document::enablePdfA() raises InvalidConfigException because the security.pdfa capability is not registered. Core still owns the ConformanceMode discriminator, so introspection and the PDF/Universal Accessibility (PDF/UA-2) tagged path work, but it does not author a PDF/A-4 file on its own.

TypeKindKey members
NextPDF\Conformance\ConformanceModeenum: stringPlain, PdfUa1, PdfUa2, PdfA2, PdfA3, PdfA3b, PdfA3u, PdfA4, PdfA4e, PdfA4f; isTagged(), isAccessibility(), isArchival(), requiresPdfUa2PageTabs(), pdfaPart(): ?int, pdfaConformanceLetter(): string, requiresPdf17(): bool
NextPDF\Conformance\ConformancePolicyfinal readonly class__construct(ConformanceMode $mode = PdfUa2, bool $strictUa2 = false, bool $rejectUnvalidatedLang = false, …); lax(), strictUa2(), withUax9IsolateSupport(), withoutAstShadowMode(), withBlackPointCompensation(), withStrictOcspProducedAtTolerance(), withAllowStaleOcsp()

ConformancePolicy enforces one invariant in its constructor: the strict PDF/UA-2 toggles apply only when the mode is PdfUa2, and strictUa2 = true forces rejectUnvalidatedLang = true. Incoherent combinations throw InvalidConfigException instead of degrading silently.

<?php
declare(strict_types=1);
use NextPDF\Conformance\ConformanceMode;
$mode = ConformanceMode::PdfA4f;
// Introspect the declared contract — these drive writer-side gates.
$mode->pdfaPart(); // 4
$mode->pdfaConformanceLetter(); // 'F'
$mode->requiresPdf17(); // false (PDF/A-4 is PDF 2.0 lineage)
$mode->isArchival(); // true

The path that ships with Core and exercises a conformance contract end to end is the PDF/UA-2 tagged route. This runnable example is examples/31-pdfua2-tagged.php. It is also the oracle target for the strict PDF/UA-2 profile.

<?php
declare(strict_types=1);
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// Set the tagged-PDF contract BEFORE writing content so the HTML pipeline
// wires the TaggedContentEmitter at parser-construction time.
$doc->enableTaggedPdf(lang: 'en');
$doc->setTitle('Accessible report');
// … write content …
$doc->save(__DIR__ . '/out/report-ua2.pdf');
// The library has now emitted PDF/UA-2 structures. It has NOT asserted
// conformance. Verify the file with the pinned veraPDF oracle:
//
// php oracle/run.php pdfua.strict
//
// (Requires the veraPDF Docker image — opt-in; see "Conformance" below.)
  • PDF/A-4 authoring is Premium. In a Core-only install, Document::enablePdfA() throws InvalidConfigException (security.pdfa unavailable). Core owns the discriminator, not the OutputIntent/ICC/XMP emission. See /specifications/pdfa4/.
  • Umbrella vs variant cases. PdfA4 is the umbrella case and reports an empty pdfaConformanceLetter(). ISO 19005-4:2020 §6.7.3 directs a file conforming to neither PDF/A-4e nor PDF/A-4f to provide no pdfa:conformance. Use PdfA4e / PdfA4f only for the Annex B / Annex A variants.
  • Strict PDF/UA-2 is opt-in. ConformancePolicy::lax() is the backward-compatible default. strictUa2() forces /MarkInfo /Marked true and Best Current Practice (BCP) 47 rejection. Enabling it on a non-PdfUa2 mode throws.
  • isTagged() is variant-aware. Plain, PdfA2, PdfA3b, and PdfA4e report untagged. The writer must not infer a structure tree from archival mode alone.
  • A passing mode is not a passing file. Setting PdfA4 does not make the output a valid PDF/A-4 file. Run a validator.

ConformanceMode and ConformancePolicy are pure value types. Enum case resolution and match dispatch are O(1), with no allocation beyond the immutable policy object. They add no measurable cost to the write path. The writer, not the discriminator, dominates the module budget (wall_ms: 1500). The veraPDF oracle is an out-of-band continuous integration (CI) step, not part of document generation.

The strict toggles on ConformancePolicy gate fail-closed security behaviour: securityPkiRfc5280Strict (Request for Comments (RFC) 5280 §6 path validation), strictOcspProducedAtTolerance (RFC 6960 §4.2.2.1 skew window), and allowStaleOcsp (default false; reject Open Certification Status Protocol (OCSP) responses missing nextUpdate). These defaults are conservative and flip to strict in a future major per the documented backward-compatibility strategy. See the security module and the project threat model for signature-path detail.

NextPDF does not certify conformance. It emits structures defined by the standards below. A separate validator decides whether a file conforms.

StandardClauseWhat NextPDF Core doesStatus
ISO 14289-2:2024 (PDF/UA-2)§6Emits structure tree, /MarkInfo /Marked true (strict), /Lang, pdfuaid XMP via the Core tagged pathVerified profile: pdfua.strict validated by the veraPDF oracle (php oracle/run.php pdfua.strict) when the veraPDF binary is present (opt-in gate)
ISO 19005-4:2020 (PDF/A-4)§6.7.3Emits the pdfaid:part / pdfa:conformance discriminator via ConformanceModeClaimed (discriminator emission, unit-tested); PDF/A-4 file authoring is Premium-only
ISO 32000-2:2020 (PDF 2.0)§7.5.2Base catalog/document structureClaimed (base engine behaviour)

Support is not conformance. NextPDF Core emits PDF/UA-2 and PDF/A-4 identification structures, with implementation evidence in tests/Unit/Conformance/. Only a validator running the matching profile can assert that a file conforms to PDF/A-4 or PDF/UA-2. Per ISO 19005-4:2020 Clause 5, conformance determination is the validator’s job, not the producing library’s.

veraPDF gating is honest about its requirement. The oracle (oracle/run.php, oracle/lib/OracleRunner.php) shells out to a pinned veraPDF Docker image. When Docker or the image is absent, the runner exits with code 2 (infrastructure failure) and verifies nothing. The pdfua.strict profile is therefore an opt-in conformance gate. It proves conformance only on machines where the veraPDF binary is present. NextPDF ships no embedded validator and makes no conformance claim in its absence.

Citations are paraphrased from the NextPDF compliance corpus. The full 64-character reference_id digests are recorded in the page frontmatter and in _normative-evidence-conf.md.