Article technique

Sortie PDF/A, PDF/X et PDF/UA en Delphi avec HotPDF

PDF/A, PDF/X et PDF/UA sont trois normes différentes qui résolvent trois problèmes différents : l'archivage à long terme, l'échange pour l'impression et l'accessibilité. Ce ne sont pas trois cases à cocher sur un même formulaire de conformité, et l'erreur la plus courante est de les traiter comme telles. Un fichier peut être un PDF/A impeccable et inutile pour un imprimeur ; un maître d'impression parfait peut être illisible pour un lecteur d'écran. Pire, toutes trois sont des contraintes sur la structure interne du fichier, pas sur son apparence. Un document qui s'ouvre proprement dans toutes les visionneuses que vous possédez peut malgré tout échouer à la validation du premier coup, et c'est le cas la plupart du temps

HotPDF, la bibliothèque PDF VCL native de losLab, traite la conformité comme quelque chose que vous déclarez avant que la première page existe. Vous posez une propriété de conformité, attachez les structures que la norme exige, et la bibliothèque refuse à l'enregistrement les configurations qui contredisent le profil. C'est un meilleur modèle que de générer un fichier en espérant qu'un post-traitement pourra le rattraper, car l'essentiel de ce que ces normes exigent ne peut pas être ajouté après coup

Trois normes ISO, trois promesses différentes

PDF/A (ISO 19005) parle du temps. Il promet qu'un fichier se rendra encore à l'identique dans des décennies, et exige donc une autonomie complète : chaque police incorporée, chaque couleur dotée d'un sens indépendant du périphérique par un OutputIntent, des métadonnées XMP complètes, et l'interdiction de tout ce dont le comportement dépend de l'environnement. Chiffrement et JavaScript sont exclus, car personne ne peut garantir que le déchiffreur ou le moteur de script existera en 2050

PDF/X (ISO 15930) parle de la couleur sur le papier. Il existe pour qu'un graphiste puisse remettre à un imprimeur un fichier dont aucun des deux n'a à discuter, ce qui implique des conditions d'impression caractérisées, une clé /Trapped obligatoire, une géométrie de rognage et de fond perdu définie, et, dans la saveur X-1a, aucune transparence vivante que le RIP aurait à deviner. PDF/UA (ISO 14289) parle de qui peut lire le résultat. Les technologies d'assistance ont besoin d'un arbre de balises complet, d'un ordre de lecture sensé, d'une langue de document déclarée et de textes de remplacement pour tout ce qui n'est pas du texte

Comme les trois tirent dans des directions différentes, choisissez la norme qui gouverne canal de sortie par canal de sortie plutôt que de courir après un fichier unique qui les satisferait toutes. Un maître d'impression tout en CMJN est précisément la mauvaise chose à remettre à un utilisateur de lecteur d'écran qui ne voit jamais la couleur, et le verrouillage du profil archivistique sur les comportements dynamiques entre en collision avec tout ce qui est interactif. Générez par canal depuis les mêmes données source et vous contournez tout le conflit

HotPDF génère des fichiers PDF/A, PDF/X et PDF/UA par canal de sortie depuis un seul document source en Delphi, chaque norme ISO tenant une promesse structurelle différente
PDF/A, PDF/X et PDF/UA tirent dans des directions différentes, HotPDF génère donc un fichier par canal de sortie depuis la même source

PDF/A : l'OutputIntent est la partie que tout le monde oublie

Si un fichier PDF/A échoue à la validation, l'OutputIntent est la première chose à vérifier. C'est la structure que les générateurs sautent le plus souvent, précisément parce que rien de visible n'en dépend. L'ISO 19005 en exige un : un profil ICC incorporé qui fixe ce que signifient réellement les couleurs périphérique du document. HotPDF fait de ce profil une entrée explicite plutôt qu'une pensée après coup :

var
  Pdf: THotPDF;
  ICC: TFileStream;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-archival.pdf';
    Pdf.PDFACompliance := 'B';            // niveau B : fidélité visuelle
    Pdf.Lang := 'en-US';
    Pdf.StandardFontEmulation := False;   // incorporer de vraies polices, pas d'émulation Base-14
    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;

Quelques détails décident ici du succès ou de l'échec. StandardFontEmulation doit être désactivé : les polices Base-14 émulées ne sont pas incorporées, et l'incorporation est non négociable sous l'ISO 19005. Le chiffrement doit rester désactivé, ne combinez donc jamais PDFACompliance avec ActivateProtection ; un fichier archivistique chiffré est une contradiction que le validateur attrape immédiatement. Le nombre de composants dans AddPDFAOutputIntent doit correspondre au profil, soit 3 pour un profil RVB comme sRGB IEC61966-2.1 et 4 pour du CMJN. HotPDF suit l'usage de DeviceRGB et DeviceCMYK au regard de l'intention déclarée pendant l'écriture, si bien qu'un remplissage CMJN égaré dans un document à intention RVB devient un problème signalé plutôt que silencieux

Une chose mérite d'être dite du profil ICC : traitez-le comme un artefact de déploiement versionné, pas comme un fichier que quelqu'un a un jour déposé sur le serveur de build. Ses octets sont incorporés dans chaque document que vous générez, si bien qu'un profil tronqué ou corrompu empoisonne discrètement tout un lot, et vous ne l'apprenez qu'à la validation. Livrez-le avec votre installeur, consignez son empreinte dans le journal d'exécution, et chargez-le par le schéma TFileStream montré ci-dessus pour qu'un fichier manquant échoue bruyamment pendant la génération plutôt qu'en silence à la porte de l'archive

PDF/X pour l'impression : Trapped, CMJN et le profil de presse

Les maîtres d'impression inversent l'histoire de la couleur. La presse veut du CMJN caractérisé, et la norme vous oblige à déclarer si le recouvrement a été appliqué même quand la réponse honnête est que vous n'en savez rien. La clé /Trapped est obligatoire quoi qu'il arrive :

Pdf.PDFXCompliance := 'X-1a';
Pdf.Trapped := 'Unknown';        // clé obligatoire selon 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;
// dessiner en couleurs sûres pour le CMJN, sans transparence ni chiffrement
Pdf.EndDoc;

Le nombre de composants passe maintenant à 4 pour le profil de presse CMJN. X-1a interdit également la transparence vivante, alors auditez tout code de dessin qui superpose des éléments translucides ; ce qu'une visionneuse compose à l'écran est exactement ce qu'un RIP refusera d'interpréter. Quand votre imprimeur envoie une autre caractérisation, échangez les octets du profil et la chaîne d'identification mais laissez la structure alentour intacte

PDF/UA : la structure se génère, elle ne se rattrape jamais

L'accessibilité est la norme que les équipes essaient le plus souvent de greffer à la fin, et elle punit cette approche plus durement que les deux autres. L'arbre de balises doit refléter l'ordre dans lequel le contenu a été logiquement créé, information dont vous ne disposez tout simplement plus une fois le fichier écrit. Poser PDFUACompliance active la sortie balisée, et l'API de structure lie chaque appel de dessin à son rôle sémantique au fil de l'écriture :

HotPDF construit l'arbre de balises PDF/UA en direct à mesure que chaque appel de dessin Delphi s'exécute, et le texte émis hors de BeginTaggedContent et EndTaggedContent reste invisible aux lecteurs d'écran
L'arbre de balises s'écrit pendant que vous dessinez, et le texte hors d'une paire balisée se rend très bien mais reste invisible aux lecteurs d'écran
Pdf.PDFUACompliance := True;     // active automatiquement le PDF balisé
Pdf.Lang := 'en-US';             // à poser explicitement ; vide, on retombe sur '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;

La défaillance à surveiller est le texte dessiné hors de toute paire BeginTaggedContent/EndTaggedContent. Il se rend parfaitement et reste invisible pour un lecteur d'écran, si bien qu'aucun testeur voyant ne l'attrape jamais ; le bug est livré et ne remonte que lorsqu'un véritable utilisateur de technologie d'assistance tombe dans le trou. Quand vos gabarits portent des noms de rôles de structure personnalisés, faites-les correspondre au jeu standard avec AddStructRoleMap('MyHead', 'H1') pour que les lecteurs conformes sachent ce qu'ils signifient. L'ISO 14289 exige aussi une langue déclarée. HotPDF retombe sur 'en' quand Lang est vide, mais c'est un filet de sécurité, pas une raison de laisser la vraie langue du document non posée

Vérification : fiez-vous au validateur, pas à la visionneuse

Une visionneuse qui ouvre votre fichier ne prouve rien sur la conformité, la vérification a donc sa place dans le chemin de livraison, avec des outils qui contrôlent la structure et non le rendu. Pour PDF/A et PDF/UA, veraPDF est le validateur libre de référence ; il rapporte les échecs par clause ISO, ce qui renvoie directement à la configuration ci-dessus. Pour PDF/X, les profils Preflight d'Adobe Acrobat restent le contrôle pratique, car la conformité de presse tient autant à l'intention colorimétrique qu'à la syntaxe

Le générateur fait sa part. À l'enregistrement, HotPDF réconcilie les indicateurs de fonctionnalités avec la version PDF configurée, rétrogradant en silence ce que la version ne peut pas exprimer, comme l'AES-256 qui retombe en AES-128 sous PDF 1.7. Les barrières de conformité dans EndDoc vont plus loin et lèvent carrément sur les contradictions dures, comme demander PDFACompliance avec du chiffrement. Ni l'une ni l'autre ne remplace le validateur externe. Elles empêchent seulement des configurations impossibles de l'atteindre

Les barrières de conformité HotPDF dans EndDoc attrapent les configurations impossibles avant que veraPDF valide le PDF/A et le PDF/UA et que Preflight d'Acrobat valide le PDF/X dans un chemin de livraison Delphi
HotPDF refuse les configurations contradictoires à EndDoc, et des validateurs indépendants décident de la conformité réelle

Une habitude paie régulièrement : versionnez tout le montage de conformité comme un tout. La version de HotPDF, la révision du gabarit, l'empreinte du profil ICC, la version du validateur qui a donné son quitus. La conformité dérive dès que l'un d'eux change sous les autres, et les audits les plus laids sont ceux où personne ne peut reconstituer quelle combinaison a produit un fichier d'archive vieux de cinq ans. Une seule fiche de configuration par lot règle cela définitivement

Enfin, faites tourner le validateur sur de la vraie sortie de production, jamais sur un échantillon bien rangé fabriqué à la main. Les échecs qui mordent viennent de données que personne n'avait anticipées : un logo client qui arrive en CMJN alors que l'intention dit RVB, une retouche de gabarit qui glisse une police non incorporée, un nouveau chemin de code qui dessine du texte hors de l'arbre de balises. Gardez un fichier connu comme mauvais de chaque incident passé en entrée de régression et la barrière de conformité reste honnête dans la durée. Pour le versant rendu de ces pipelines, voyez notre article sur la sortie de rapports, les polices et les images avec HotPDF ; pour câbler des validateurs dans un build, il existe une pièce compagnon sur l'automatisation des contrôles preflight PDF

Les propriétés de conformité, les OutputIntent et l'API de balisage utilisés dans ces exemples sont livrés avec le composant HotPDF pour Delphi, pour Delphi et C++Builder ; la page produit lie la référence complète de chaque appel montré ici