Une liste de contrôle PDF/A-3 dit généralement que le paquet XMP doit porter deux entrées d'identification, pdfaid:part et pdfaid:conformance. Un fichier PDF/A-4 correctement écrit porte pdfaid:part 4 et pdfaid:rev 2020, et pour le profil de base il n'y a pas d'entrée de conformité du tout. Ajouter une lettre pour « réparer » cela rend le fichier faux : ISO 19005-4 identifie la partie de base par numéro et année de révision, et seules les deux extensions gardent une lettre
PDF/UA-2 a un piège similaire un étage plus bas, à l'intérieur de l'arbre de structure. Déclarer un rôle personnalisé dans son propre espace de noms ressemble à tout le travail, puisque les espaces de noms sont la fonctionnalité qu'a ajoutée la partie 2. Ce n'en est que la moitié. Un rôle tel que WidgetGroup dans https://example.com/ns/widgets doit quand même se mapper vers un rôle standard avant qu'un vérificateur conforme ne l'accepte, et rien dans la page rendue ne laisse deviner que le mappage manque
Sélectionner PDF/A-4 avec PDFACompliance
Dans HotPDF, les profils PDF/A-4 sont trois valeurs de la propriété PDFACompliance existante : '4' pour la partie de base, '4E' pour les documents d'ingénierie et '4F' pour les fichiers qui portent des fichiers incorporés. Avec '4' sélectionné, le paquet XMP reçoit pdfaid:part 4 et pdfaid:rev 2020 et rien d'autre de l'espace de noms pdfaid. '4E' et '4F' ajoutent pdfaid:conformance avec E ou F. Les fonctions de décodage rapportent la même répartition : PDFAPart renvoie 4 pour les trois valeurs, et PDFAConformanceLevel renvoie E, F, ou #0 pour le profil de base
Les trois forcent un en-tête PDF 2.0. La montée de version se passe à l'intérieur de EndDoc, si bien qu'un document parti de PDF 1.4 ressort en 2.0 sans appel séparé. Le seul cas qui l'arrête est StrictVersionLock. Cet indicateur interdit les montées de version automatiques, donc EndDoc lève « PDF/A-4 requires PDF 2.0; StrictVersionLock prevents the version upgrade » plutôt que d'écrire un fichier 1.7 qui prétend la partie 4 dans ses métadonnées. Une base de code qui épingle la version pour un ancien flux d'impression verra exactement ce message la première fois que quelqu'un bascule le profil
Deux règles PDF/A-4 changent ce qui atterrit dans le trailer et le Catalog. ISO 19005-4 §6.1.3 ne permet un dictionnaire d'informations de document que quand le Catalog a une entrée PieceInfo, et même là le limite à ModDate, si bien que HotPDF n'écrit pas de dictionnaire Info pour la partie 4 et que le titre voyage comme dc:title dans le XMP. Title et Lang restent obligatoires. Laissez l'un vide et EndDoc lève. Il lève aussi quand aucune intention de sortie n'a été ajoutée avec AddPDFAOutputIntent, et EnableEncrypt refuse de tourner tant qu'un profil PDF/A est sélectionné, car un fichier d'archivage chiffré est une contradiction que la norme ne permet pas
Fichiers associés sous PDF/A-4f et 4e
Les fichiers associés sont la principale raison de choisir PDF/A-4f : une facture XML derrière la page rendue, une charge JSON qu'un système en aval analyse. AddPDFAssociatedFile prend un nom de fichier, un type MIME, une description, une valeur AFRelationship et les octets, et renvoie le dictionnaire FileSpec indirect pour que vous puissiez attacher vos propres clés optionnelles
var
Pdf: THotPDF;
Icc: TFileStream;
begin
Pdf := THotPDF.Create(nil);
Icc := TFileStream.Create('sRGB2014.icc', fmOpenRead or fmShareDenyWrite);
try
Pdf.FileName := 'invoice-archive.pdf';
Pdf.PDFACompliance := '4F'; // partie 4, révision 2020, conformité F
Pdf.Title := 'Invoice 2026-0731'; // écrit seulement dans le dc:title XMP
Pdf.Lang := 'en-US';
Pdf.BeginDoc;
Pdf.AddPDFAOutputIntent('sRGB IEC61966-2.1', 'sRGB IEC61966-2.1',
Icc, 3, 'DeviceRGB');
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Invoice 2026-0731');
// LoadInvoiceBytes est votre propre fonction renvoyant TBytes
Pdf.AddPDFAssociatedFile('invoice.xml', 'text/xml',
'Structured invoice data', 'Data', LoadInvoiceBytes);
Pdf.EndDoc;
finally
Icc.Free;
Pdf.Free;
end;
end;
Chaque appel fait quatre choses. Il écrit un flux EmbeddedFile dont le /Subtype porte le type MIME comme nom PDF, avec un dictionnaire /Params portant la taille, une date de modification et une somme de contrôle MD5. Il construit un FileSpec avec à la fois /F et /UF, la description comme /Desc, et le nom de relation. Puis il enregistre ce FileSpec deux fois, dans le tableau /AF du Catalog et dans l'arbre de noms EmbeddedFiles. Les deux enregistrements sont exigés. Un fichier enregistré à un seul endroit peut encore s'ouvrir depuis la liste de pièces jointes d'un visualiseur, voilà pourquoi un contrôle oculaire ponctuel prouve très peu sur une archive
La chaîne de relation doit être l'une de Source, Data, Alternative, Supplement ou Unspecified ; tout le reste lève avant qu'un octet soit écrit, et un type MIME vide aussi. Le profil est vérifié également. AddPDFAssociatedFile accepte PDF/A-3, PDF/A-4e et PDF/A-4f, et le profil de base '4' lève car il n'admet pas de fichiers associés. Dans l'autre sens, AddDocumentAttachment refuse de tourner sous PDF/A-3, 4e et 4f, puisqu'une pièce jointe simple manque à l'enregistrement /AF que ces profils exigent. Du code écrit pour PDF/A-3 contre AddPDFA3AssociatedFile continue de compiler et se comporte pareil ; AddPDFAssociatedFile est le nom neutre de profil de cette méthode
Les deux extensions se partagent les permissions restantes. Sous '4F', EndDoc s'assure que le Catalog porte un arbre de noms EmbeddedFiles, et AddFileAttachmentAnnotation est acceptée, tandis que le profil de base et 4e la rejettent. Sous '4E', Add3DAnnotation et AddRichMediaAnnotation sont permises, ainsi que les actions SetOCGState et GoTo3DView. Tout autre profil PDF/A refuse les quatre
Pourquoi un rôle personnalisé avec espace de noms échoue-t-il encore en PDF/UA-2 ?
Un rôle personnalisé passe PDF/UA-2 seulement quand deux conditions tiennent : son entrée /NS pointe vers un dictionnaire d'espace de noms listé à la racine de l'arbre de structure, et le rôle se résout à travers le RoleMapNS de cet espace de noms vers un rôle d'un espace de noms standard. HotPDF répartit le travail sur trois méthodes. RegisterStructureNamespace crée un dictionnaire indirect /Type /Namespace, l'ajoute à StructTreeRoot /Namespaces et le renvoie ; un second appel avec la même URI renvoie le dictionnaire existant au lieu d'en ajouter un autre. AddStructureElementNS crée l'élément et règle son /NS, en enregistrant l'espace de noms au passage. AddStructRoleMapNS écrit le mappage
const
PDF2NS = 'http://iso.org/pdf2/ssn';
WidgetNS = 'https://example.com/ns/widgets';
var
DocElem, Group: THPDFDictionaryObject;
begin
Pdf.PDFUACompliance := True;
Pdf.PDFUAPart := 2; // PDF/UA-2 exige PDF 2.0
Pdf.Title := 'Widget catalog'; // un Title vide lève à EndDoc
Pdf.Lang := 'en-US';
Pdf.BeginDoc;
DocElem := Pdf.AddStructureElement('Document', nil); // lié à PDF2NS
Group := Pdf.AddStructureElementNS('WidgetGroup', WidgetNS, DocElem);
// sans ce mappage EndDoc lève ua2.rolemap.unresolved
Pdf.AddStructRoleMapNS('WidgetGroup', WidgetNS, 'Div', PDF2NS);
Pdf.CurrentPage.SetFont('Arial', [], 18);
Pdf.CurrentPage.BeginMarkedContentMCID('H1', 0);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Widgets');
Pdf.CurrentPage.EndMarkedContent;
Pdf.AddStructureElement('H1', Group, 0, 0); // index de page 0, MCID 0
Pdf.EndDoc;
end;
Une partie de ce listing arrive sans qu'on la demande. Avec PDFUAPart à 2, AddStructureElement('Document', nil) lie l'élément à l'espace de noms standard PDF 2.0, http://iso.org/pdf2/ssn, car la racine d'un arbre PDF/UA-2 doit être un Document de cet espace de noms. Le même liage arrive pour chaque rôle du vocabulaire PDF 2.0, si bien que H1 et P n'ont pas besoin d'argument d'espace de noms. Les rôles qui n'existent que dans l'ensemble PDF 1.7, tels que Note, Code ou BlockQuote, restent dans l'espace de noms 1.7
La cible du mappage est un vrai choix. Passer un espace de noms cible vide à AddStructRoleMapNS écrit la forme courte, un nom de rôle nu résolu contre l'ensemble PDF 1.7. Passer l'URI PDF 2.0 écrit la forme à deux éléments [role namespace]. Dans un document PDF/UA-2, mappez vers l'espace de noms PDF 2.0 : tout l'arbre se résout alors dans un vocabulaire unique, et des cibles telles que Aside ou FENote, qui n'ont pas d'équivalent 1.7, deviennent disponibles. Deux formes de mappage sont rejetées sans appel à EndDoc, une chaîne qui reboucle sur elle-même et un rôle d'un espace de noms explicite mappé vers un autre rôle de ce même espace de noms
L'ordre des appels est plus souple qu'il n'y paraît. Le mappage peut être ajouté avant ou après que l'élément existe, car rien ne le vérifie avant que l'arbre soit fini. Ce qui échoue immédiatement, c'est AddStructureElementNS quand PDFUAPart vaut 1. La partie 1 n'a pas d'espaces de noms, et l'appel lève « structure namespaces require PDFUAPart=2 » sur-le-champ
Ce que EndDoc vérifie avant que le fichier soit écrit
Avec PDFUACompliance activé et PDFUAPart à 2, EndDoc exécute une validation de structure PDF/UA-2 complète avant de sérialiser quoi que ce soit, et la première erreur devient une exception. Quatre de ses vérifications gardent le squelette de l'arbre. La racine doit porter exactement un élément Document de plus haut niveau. Chaque dictionnaire d'espace de noms doit être indirect, typé Namespace, et porter une URI non vide qui n'apparaît qu'une fois. Chaque référence /NS doit se résoudre vers un dictionnaire listé dans le tableau /Namespaces racine. Et chaque rôle doit se résoudre vers un espace de noms standard, directement ou à travers RoleMap et RoleMapNS. Les éléments injoignables depuis l'élément Document sont signalés aussi
La même passe couvre les exigences de niveau document : un Lang non vide et valide ; MarkInfo /Marked à true ; DisplayDocTitle activé ; et un paquet XMP avec un dc:title non vide et pdfuaid:part 2. HotPDF règle lui-même l'indicateur marked, la préférence de visualiseur et l'identification XMP dès que PDF/UA est activé, et écrit pdfuaid:rev 2024 à côté du numéro de partie. Titre et langue sont à fournir par vous, et une valeur vide pour l'un ou l'autre lève avant même que le validateur démarre. Titres, listes, tableaux, annotations et formulaires sont vérifiés dans la même exécution
Attendre EndDoc est délibéré. À moitié de la génération, un arbre est légitimement incomplet : l'élément Document peut n'avoir pas encore d'enfants, un espace de noms peut être enregistré avant que quelque élément l'emploie, un mappage peut arriver après son élément. Vérifier à chaque appel rejetterait ces états. À EndDoc, l'arbre est fini et toujours en mémoire, et l'exception nomme un code de règle et le chemin de l'élément qui l'a enfreinte
try
Pdf.EndDoc;
except
on E: Exception do
begin
// E.Message a la forme
// PDF/UA-2 validation failed [ua2.heading.skipped at ...]:
// Heading level skips from H2 to H4
LogBuildFailure(Pdf.FileName, E.Message); // votre propre journal
raise;
end;
end;
La sortie PDF/A-4 reçoit le même traitement de son propre validateur. Quand PDFACompliance porte une valeur de partie 4, EndDoc confronte le graphe généré à ISO 19005-4 et lève avec le code de règle et la clause, par exemple pdfa4.filespec.relationship pour un FileSpec sans relation valide ou pdfa4.trailer.info_entries pour une clé Info égarée. L'édition Enterprise expose les deux validateurs comme ValidatePDFUA2Structure et ValidatePDFA4, qui remplissent un tableau d'anomalies avec chaque diagnostic retenu au lieu de s'arrêter à la première erreur. Pour une étape de CI qui devrait imprimer une liste complète, c'est le meilleur point d'entrée
Rôles PDF 2.0 et titres au-delà de H6
L'énumération typée THPDFStandardStructureType gagne sstDocumentFragment, sstAside, sstTitle, sstFENote, sstSub, sstEm, sstStrong et sstArtifact. Trois d'entre eux changent la façon dont les documents d'affaires ordinaires sont balisés. Aside donne aux barres latérales et aux citations tirées un foyer qui n'est pas un Sect mal employé. FENote marque notes de bas de page et notes de fin comme notes, si bien qu'un lecteur d'écran peut les proposer à la demande au lieu de les lire en pleine phrase. Em et Strong portent l'emphase comme structure, là où un arbre PDF 1.7 ne pouvait que l'envelopper dans un Span
Les titres sont ouverts en PDF 2.0. L'énumération s'arrête à sstH6, mais la surcharge chaîne de AddStructureElement accepte H7, H8 et au-delà, car l'ensemble PDF 2.0 définit Hn pour tout n de 1 en haut. PDF 1.7 s'arrêtait à H6, ce qui forçait les documents techniques profonds à aplanir leur plan ou à réutiliser des niveaux. Pour les normes, les codes juridiques et les catalogues de pièces, cela seul peut justifier de déplacer la sortie vers PDF 2.0
Les titres profonds viennent avec une règle plus stricte. La passe PDF/UA-2 à l'intérieur de EndDoc exige que les niveaux de titres soient séquentiels : le premier titre doit être H1, et un saut de H2 à H4 lève avec ua2.heading.skipped. Une hiérarchie peut démarrer plus bas seulement à l'intérieur d'un DocumentFragment, le rôle prévu pour du contenu extrait d'un document plus grand. Un générateur qui mappe un plan Markdown ou HTML directement sur des rôles Hn hérite de chaque niveau que l'auteur a sauté pour la taille visuelle, alors normalisez le plan avant de le baliser
Avant de basculer la sortie de production vers PDF 2.0
PDF 2.0 commence par un changement d'en-tête, et l'en-tête est ce que les consommateurs plus anciens lisent en premier. Un service d'ingestion, un flux d'impression ou un visualiseur métier qui n'accepte que jusqu'à PDF 1.7 s'arrêtera à %PDF-2.0 quelle que soit la propreté du reste du fichier. Sélectionner n'importe quel profil PDF/A-4 ou PDF/UA partie 2 sélectionne PDF 2.0 que vous l'ayez demandé ou non, alors confirmez chaque système consommateur avant la bascule, pas après
Le déploiement au risque le plus bas garde PDF/A-3 pour les documents qui partent vers des lecteurs que vous ne contrôlez pas, emploie PDF/A-4f pour les archives internes où vous détenez le côté ingestion, et adopte PDF/UA-2 là où une politique d'accessibilité le nomme. StrictVersionLock joue aussi le rôle de garde pendant la transition : réglez Version à pdf17 et le verrou dans les chemins de code qui doivent rester en 1.7, et une sélection PDF/A-4 accidentelle échoue à EndDoc avec un message au lieu d'atteindre un système qui ne peut pas ouvrir le fichier. Les guides de validation PDF/A, PDF/X et PDF/UA et de factures hybrides ZUGFeRD et Factur-X sur PDF/A-3 couvrent les choix de profil qui précèdent le numéro de version, et les notes sur le rapport de préflight automatisé montrent comment transformer le verdict en étape de build
Tout cela tourne en code VCL natif à l'intérieur du composant pour Delphi et C++Builder, si bien que la sortie PDF/A-4 et PDF/UA-2 se livre avec votre exécutable et n'a besoin d'aucun moteur externe. Les profils pris en charge et les versions RAD Studio sont listés sur la page composant HotPDF Delphi