Technical Article

PDF/A, PDF/X, and PDF/UA Output in Delphi: HotPDF Guide

A government archive once bounced an entire deposit of 1,400 invoices generated by a customer's Delphi billing system. Every file opened perfectly in every viewer the customer tried; veraPDF rejected all of them for the same single reason, a missing OutputIntent. That incident captures the core lesson of standards-compliant PDF work: visual correctness proves nothing, because PDF/A, PDF/X, and PDF/UA are constraints on the file's internal structure, not on what it looks like. HotPDF, losLab's native VCL PDF library, builds these constraints into document generation so that compliance is configured before the first page exists instead of patched in afterwards.

UK teams should align this hotpdf pdfa pdfx pdfua validation delphi workflow with local governance, audit, and data quality requirements before production release

Three ISO standards, three different promises

PDF/A (ISO 19005) promises that a file will still render identically decades from now, which explains why it demands complete self-containment: every font embedded, every colour device-independent via an OutputIntent, full XMP metadata, and a ban on anything whose behaviour depends on the environment, including encryption and JavaScript. PDF/X (ISO 15930) promises blind exchange between a designer and a print shop, so its rules are about colour and geometry: characterized printing conditions, a mandatory /Trapped key, defined trim and bleed geometry, and in the X-1a flavor no live transparency. PDF/UA (ISO 14289) promises that assistive technology can read the document, which makes it a requirement on logical structure: a complete tag tree, correct reading order, a declared document language, and text alternatives for non-text content.

These promises pull in different directions. An interactive form is fine in PDF/UA and impossible to combine with the archival profile's restrictions on dynamic behaviour; a CMYK-only print master is exactly wrong for screen-reader users who never see colour. Decide per output channel which standard governs, instead of chasing one file that satisfies everything.

PDF/A in HotPDF: the OutputIntent is the whole game

The archive rejection above came down to one missing structure, and it is the structure most generators forget because nothing visible depends on it. ISO 19005 requires an OutputIntent: an embedded ICC profile that gives device colours an unambiguous meaning. HotPDF makes the profile an explicit input:

var
  Pdf: THotPDF;
  ICC: TFileStream;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-archival.pdf';
    Pdf.PDFACompliance := 'B';            // level B: visual fidelity
    Pdf.Lang := 'en-US';
    Pdf.StandardFontEmulation := False;   // embed real fonts, no Base-14 emulation
    ICC := TFileStream.Create('sRGB.icc', fmOpenRead);
    try
      Pdf.AddPDFAOutputIntent('sRGB IEC61966-2.1', '', ICC, 3, 'DeviceRGB');
    finally
      ICC.Free;
    end;
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 760, 0, 'Archival invoice body');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Three configuration details decide pass or fail. StandardFontEmulation must be off, because emulated Base-14 fonts are by definition not embedded and embedding is non-negotiable under ISO 19005. Encryption must stay disabled; do not combine PDFACompliance with ActivateProtection, since an encrypted archival file is a contradiction the validator will catch. And the component count passed to AddPDFAOutputIntent has to match the profile, 3 for an RGB profile such as sRGB IEC61966-2.1. HotPDF tracks DeviceRGB and DeviceCMYK usage during generation against the declared intent, so a CMYK fill in an RGB-intent document surfaces as a validation problem instead of a silent inconsistency.

Treat the ICC profile itself as a deployment artefact with a version, not as a file someone once copied onto the build server. The profile bytes are embedded into every generated document, so a corrupted or truncated profile poisons an entire batch in one pass, and the failure only appears at validation time. Ship the profile with your installer, record its checksum in the run log, and load it through the TFileStream pattern above so a missing file fails loudly at generation instead of silently at the archive gate.

PDF/X for print: Trapped, CMYK, and the press profile

Print masters flip the colour story: the press wants characterized CMYK, and the standard insists you state whether trapping has been applied. The /Trapped key is mandatory even when the honest answer is that you do not know:

Pdf.PDFXCompliance := 'X-1a';
Pdf.Trapped := 'Unknown';        // mandatory key under ISO 15930
ICC := TFileStream.Create('FOGRA39.icc', fmOpenRead);
try
  Pdf.AddPDFXOutputIntent('FOGRA39 (ISO 12647-2:2004)', '', ICC, 4, 'DeviceCMYK');
finally
  ICC.Free;
end;
Pdf.BeginDoc;
// draw with CMYK-safe colors, no transparency, no encryption
Pdf.EndDoc;

Note the component count changed to 4 for the CMYK press profile. X-1a additionally rules out live transparency, so review any drawing code that layers translucent elements; what a viewer composites on screen is precisely what a RIP refuses to guess at. If your shop hands you a different characterization, swap the profile and identifier but keep the structure identical.

PDF/UA: structure is generated, never retrofitted

Accessibility is the standard teams most often try to bolt on at the end, and it is the one where retrofitting fails hardest, because the tag tree must mirror the logical order in which content was created. In HotPDF, setting PDFUACompliance is enabled tagged output, and the structure API ties each drawing call to its semantic role:

Pdf.PDFUACompliance := True;     // auto-enables tagged PDF
Pdf.Lang := 'en-US';             // set explicitly; empty falls back to 'en'
Pdf.BeginDoc;

Root := Pdf.AddStructureElement(sstDocument, nil);
H1 := Pdf.EmitTaggedHeading(1, Root, 50, 700, 'Quarterly Report');
Para := Pdf.BeginTaggedContent('P', Root);
Pdf.CurrentPage.TextOut(50, 650, 0, 'Revenue grew in all regions.');
Pdf.EndTaggedContent;

Pdf.EndDoc;

The error pattern to watch for is content drawn outside any BeginTaggedContent/EndTaggedContent pair: it renders normally and is invisible to a screen reader, which is the worst kind of failure because no sighted tester will ever notice it. If your templates use custom structure role names, map them onto the standard set with AddStructRoleMap('MyHead', 'H1') so conforming readers know how to interpret them. ISO 14289 also requires a declared language; HotPDF substitutes 'en' when Lang is empty, which is a safety net, not an excuse to skip setting the real document language.

Verification: trust the validator, not the viewer

The invoice story has a simple moral: build verification into the release path with tools that check structure, not rendering. For PDF/A and PDF/UA, veraPDF is the reference-grade open validator and reports failures by ISO clause, which makes its output directly actionable against the configuration shown above. For PDF/X, Adobe Acrobat's Preflight profiles remain the practical check, since press conditions are as much about colour intent as about syntax. Within the generator, HotPDF reconciles feature flags with the configured PDF version at save time — silently downgrading what the version cannot express, such as AES-256 below PDF 1.7 — whilst the compliance gates in EndDoc raise outright on hard contradictions such as combining PDFACompliance with encryption. Neither check replaces the external validator; together they keep impossible configurations from ever reaching it.

Version the whole compliance configuration together: the HotPDF release, the template revision, the ICC profile checksum, and the validator version that signed off. Conformance drifts when any one of them changes underneath the others, and the most painful audits we have seen were ones where nobody could say which combination produced a five-year-old archive file. A single configuration record per batch closes that question permanently.

Run the validator on real production output, not on a hand-built sample. The failures that matter come from data: a customer logo that arrives as CMYK when the intent says RGB, a template revision that introduces an unembedded font, a new code path that draws untagged text. Keep one known-failing file per past incident as a regression input, and your compliance gate stays honest. For the rendering side of these pipelines, see our article on report output, fonts, and images with HotPDF, and for wiring validators into a build, the companion piece on automating PDF preflight checks.

FAQ

Can one PDF be PDF/A and PDF/X compliant at the same time?

Sometimes, but it is rarely worth the constraint juggling: the archival profile wants device-independent colour and full metadata, the print profile wants characterized CMYK and trapping declarations. Generate per channel from the same source data instead of forcing one file to serve both.

Why does veraPDF reject a file that opens fine in every viewer?

Viewers are deliberately forgiving; validators are deliberately strict. Missing OutputIntents, unembedded fonts, and absent XMP metadata never affect rendering, so only a structural validator will report them.

Should invoices use PDF/A level A or level B?

Level B guarantees visual reproduction and is what most archives require for scanned or generated business documents. Level A adds the tagged-structure requirements, effectively pulling in the PDF/UA workload, and is the right choice when accessibility obligations apply to the archive itself.

Further reading

The compliance properties, output intents, and tagging API in this article are all part of the HotPDF Component for Delphi and C++Builder; the product page links to the complete reference for each call used here.