Article technique

Validation PDF/X en Delphi avec le composant PDFium

Le composant PDFium pour Delphi valide les documents PDF/X prêts pour l'impression via la méthode TPdf.ValidatePdfX, qui implémente le contrôle ISO 15930 sur deux niveaux : huit vérification de contenu au niveau des octets (chiffrement LZW interdit, JavaScript, champs de formulaire, références OPI, absence de TrimBox, clé Trapped non définie, etc.) et une passe sur le modèle d'objet PDFium exploitant FPDFFont_GetIsEmbedded pour s'assurer que les polices sont bien intégrées dans chaque objet texte de chaque page. Le résultat est un enregistrement TPdfXValidationResult précisant le niveau de conformité détecté et listant chaque anomalie sous forme d'une énumération typée, permettant à votre application Delphi d'expliquer à un client pourquoi son fichier sera rejeté par l'imprimeur avant la phase de gravure des plaques

Si vous avez déjà envoyé un fichier à un imprimeur professionnel pour vous le voir renvoyer avec un rejet laconique — « pas de TrimBox », « polices non intégrées », « Trapped non défini » —, vous connaissez le coût d'une détection tardive. Le PDF/X est le pendant prépresse du PDF/A : alors que le PDF/A d'archivage garantit qu'un document s'affichera à l'identique dans plusieurs décennies, le PDF/X garantit qu'un document se prêtera à la séparation, à l'insolation et au massicotage à l'identique sur le RIP d'un tiers demain matin. Ces deux normes partagent des technologies communes (identification XMP, OutputIntents, profils ICC intégrés) mais répondent à des besoins distincts, ce qui explique pourquoi le composant fournit des validateurs séparés pour chacune — le volet PDF/A est détaillé dans la validation prépresse PDF/A avec le composant PDFium

Qu'exige réellement l'ISO 15930 d'un PDF prêt pour l'impression ?

L'ISO 15930 a pour but de rendre possible l'échange aveugle : un concepteur transmet un fichier à un imprimeur avec lequel il n'a jamais communiqué, et l'imprimeur peut générer la sortie correcte sans appel téléphonique, sans e-mail pour police manquante et sans lien brisé vers une image restée sur l'ordinateur du concepteur. Chaque règle de la norme sert cet objectif. Les polices doivent être intégrées car le RIP récepteur ne dispose pas nécessairement des licences requises. Les références externes sont interdites car le fichier doit être autosuffisant. Les fonctionnalités interactives sont exclues car l'encre n'a pas de gestionnaire d'événement « onclick »

Le composant PDFium reconnaît trois familles de conformité et les indique via l'énumération TPdfXConformance dans le résultat de la validation : pxc1a pour le PDF/X-1a:2001 (ISO 15930-1, le socle strict CMJN et tons directs sur PDF 1.3/1.4), pxc3 pour le PDF/X-3:2002 (ISO 15930-3, qui accepte le RVB, le Lab et la couleur gérée par profil ICC), et pxc4 pour le PDF/X-4:2010 (ISO 15930-7, qui autorise la transparence active et les calques sur une base PDF 1.6). Un fichier ne portant aucune identification PDF/X renvoie la valeur pxcNone, une réponse utile en soi : le document ne s'est jamais revendiqué comme prêt pour l'impression, et toutes les autres anomalies relevées par le validateur expliquent les étapes requises pour y parvenir

Ces interdictions s'expliquent d'elles-mêmes lorsque l'on adopte le point de vue d'un éditeur de RIP. Le filtre /LZWDecode est interdit dans toutes les variantes de PDF/X pour éviter qu'un lecteur conforme ne dépende d'un filtre ayant un lourd historique de licences et de compatibilité ; le codage Flate remplit le même rôle sans ces contraintes. Le JavaScript, les champs de formulaires AcroForm et les dictionnaires d'actions complémentaires /AA sont exclus car un fichier d'impression doit être une description figée des marques sur le papier — tout ce qui pourrait modifier l'aspect à l'ouverture brise la garantie selon laquelle l'épreuve papier correspond à l'impression finale. Les espaces réservés OPI (Open Prepress Interface) sont interdits car ils constituent, par conception, des références à des images haute résolution stockées ailleurs, et cet « ailleurs » est précisément ce que l'échange aveugle cherche à éviter

Pourquoi les imprimeurs rejettent-ils les PDF sans TrimBox ?

La TrimBox (zone de rognage) définit la page finie — le rectangle restant après la coupe du massicot. La MediaBox (zone de média), présente sur chaque page PDF, représente simplement la feuille brute : elle englobe le fond perdu, les repères de coupe, les repères d'alignement et les barres de couleur. Les logiciels d'imposition positionnent les pages sur une plaque de presse d'après leurs TrimBoxes ; sans elle, l'opérateur doit deviner où s'arrête réellement votre carte de visite, et une erreur de jugement risque de couper le fond perdu ou de laisser un liseré blanc sur un côté. C'est pourquoi l'ISO 15930 exige une TrimBox (ou une ArtBox) sur chaque page, et why ValidatePdfX lève l'anomalie pvxiMissingTrimBox lorsqu'aucune clé /TrimBox n'est trouvée sur une page du document

La clé /Trapped répond à une autre problématique de production. Le piégeage (trapping) est la technique prépresse consistant à faire chevaucher légèrement les couleurs adjacentes de sorte qu'un léger défaut d'alignement des plaques n'ouvre pas de liserés blancs. L'imprimeur doit savoir si ce travail a déjà été réalisé : piéger un fichier déjà traité doublerait les chevauchements, et ignorer cette étape sur un fichier non traité expose à des écarts visibles. Le PDF/X exige donc que le dictionnaire Info contienne explicitement /Trapped /True ou /Trapped /False — l'absence de cette clé ou la valeur /Unknown impose une inspection humaine du fichier, précisément la discussion que l'échange aveugle cherche à éliminer. Le composant signale cette absence par le drapeau pvxiTrappedNotSet

Exécuter la validation sur deux niveaux avec TPdf.ValidatePdfX

La méthode TPdf.ValidatePdfX ne prend aucun paramètre et renvoie un enregistrement TPdfXValidationResult contenant trois membres : Conformance (la variante PDF/X détectée), Issues (un ensemble Pascal de valeurs TPdfXValidationIssue) et un assistant IsCompliant. En interne, elle sérialise le document chargé dans un flux en mémoire, exécute l'inspection au niveau des octets, puis parcourt le modèle d'objet PDFium pour la vérification de l'intégration de chaque police. Un filtre de validation minimal ressemble à ceci :

uses PDFium, FPdfPdfx;

procedure CheckPrintReadiness(const FileName: string);
var
  Pdf: TPdf;
  Res: TPdfXValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;

    Res := Pdf.ValidatePdfX;
    Writeln('Detected conformance: ',
      Ord(Res.Conformance)); // pxc1a, pxc3, pxc4, pxcNone...

    if Res.IsCompliant then
      Writeln('PDF/X checks passed')
    else
    begin
      if pvxiMissingTrimBox in Res.Issues then
        Writeln('REJECT: no /TrimBox on the pages');
      if pvxiTrappedNotSet in Res.Issues then
        Writeln('REJECT: /Trapped missing or /Unknown');
      if pvxiPdfiumFontNotEmbedded in Res.Issues then
        Writeln('REJECT: a page uses a non-embedded font');
      if pvxiLzwForbidden in Res.Issues then
        Writeln('REJECT: LZWDecode filter present');
    end;
  finally
    Pdf.Free;
  end;
end;

L'ensemble Issues étant un ensemble Pascal ordinaire, vous pouvez le segmenter selon les besoins de votre flux de travail — rejeter les anomalies structurelles critiques, traiter le message pvxiMissingTitle (un SHOULD de la norme, non un MUST) comme un simple avertissement et consigner le reste. Ce même type d'enregistrement alimente le générateur de rapports du composant ; ainsi, si vous préférez générer un document lisible par un humain plutôt que de traiter des énumérations, la logique présentée dans la génération d'un rapport de validation par lots en ligne de commande avec le composant PDFium s'applique à l'identique pour le PDF/X

Ce que détecte le niveau octet — et ce qui lui échappe

Le traitement au niveau des octets est une lecture des jetons de structure du document, les corps des flux étant ignorés, ainsi une image JPEG contenant par hasard la séquence d'octets /JavaScript ne risque pas de déclencher un faux positif. Outre les contrôles de marqueurs (XMP pdfxid:GTS_PDFXVersion, OutputIntent avec profil ICC intégré, /ID de fin, interdiction du chiffrement), la passe sur le contenu ajoute huit contrôles, associés chacun à une valeur d'énumération :

  • pvxiLzwForbidden — présence d'un filtre `/LZWDecode` dans le fichier (interdit dans toutes les variantes de PDF/X)
  • pvxiJavaScriptForbidden — présence d'une action ou d'un arbre de noms `/JavaScript`
  • pvxiFormFieldsForbidden — présence d'un dictionnaire `/AcroForm` ou d'une entrée `/XFA`
  • pvxiAdditionalActions — présence d'un dictionnaire d'actions complémentaires `/AA`
  • pvxiEmbeddedFilesForbidden — présence de `/EmbeddedFiles` ou d'une annotation `/FileAttachment`
  • pvxiOpiForbidden — présence d'une entrée `/OPI` ou `/Alternates` faisant référence à du contenu d'image externe
  • pvxiMissingTrimBox — absence de `/TrimBox` sur les pages
  • pvxiTrappedNotSet — la clé `/Trapped` est absente ou définie à `/Unknown`

L'analyse au niveau des octets est rapide et ne nécessite pas de moteur de rendu, mais elle présente un angle mort concernant les polices : à ce niveau, l'inspecteur ne peut appliquer qu'une heuristique approximative — il signale une anomalie uniquement s'il ne trouve aucun programme de police intégrée dans le fichier. Un document contenant neuf polices intégrées et une police système oubliée semblera correct lors d'une simple lecture d'octets. C'est pour combler cette faille que le second niveau existe

L'intégration des polices via le modèle d'objet PDFium

Le niveau modèle d'objet du composant PDFium résout précisément la question des polices. Après la passe au niveau des octets, TPdf.ValidatePdfX parcourt chaque page, demande la liste des objets avec FPDFPage_CountObjects et, pour chaque objet texte, récupère le handle de police via FPDFTextObj_GetFont et interroge FPDFFont_GetIsEmbedded. Une seule police non intégrée dans le document ajoute la valeur pvxiPdfiumFontNotEmbedded à l'ensemble d'anomalies. Le parcours s'interrompt dès que l'anomalie est confirmée — arrêt de l'analyse des objets de la page et arrêt du chargement des pages suivantes —, ainsi sur un catalogue de 300 pages non conforme, le verdict tombe souvent dès la première page

Deux limites à connaître. Premièrement, ce niveau exige le chargement de la bibliothèque PDFium et requiert des builds exportant la fonction FPDFFont_GetIsEmbedded ; si cette fonction n'est pas exportée, le contrôle est ignoré sans générer d'échec, évitant ainsi des rejets erronés avec des DLL anciennes. Deuxièmement, le contrôle répond par oui ou non sur l'intégration — il ne distingue pas l'intégration complète du sous-ensemblement, et n'inspecte pas le jeu de glyphes. Si un fichier échoue et que vous devez identifier quelle police pose problème sur quelle page, les techniques décrites dans l'analyse des propriétés de polices PDF avec PDFium en Delphi complètent précisément la réponse booléenne du validateur

Valider des flux sans charger de document — ni de DLL

L'inspecteur au niveau des octets est également disponible sous forme de fonction autonome, ValidatePdfXCompliance(Source: TStream) dans l'unité FPdfPdfx, écrite en pur Object Pascal sans dépendance à la DLL PDFium. Cela permet de l'intégrer dans des contextes qui n'accueillent pas de moteur de rendu : une validation rapide lors d'un chargement sur un serveur Web, un script d'intégration continue vérifiant des fichiers générés ou un service Lazarus s'exécutant sur une plateforme où vous préférez ne pas déployer de binaires natifs. Transmettez-lui simplement un flux :

uses Classes, FPdfPdfx;

function QuickPdfXGate(const FileName: string): Boolean;
var
  Fs: TFileStream;
  Res: TPdfXValidationResult;
begin
  Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfXCompliance(Fs);
    Result := Res.IsCompliant and (Res.Conformance <> pxcNone);
  finally
    Fs.Free;
  end;
end;

Le compromis est clair : la fonction autonome exécute les vérifications de marqueurs et les huit contrôles de contenu, mais pas la vérification des polices via le modèle PDFium, son verdict sur les polices s'en remettant à l'heuristique approximative. Une bonne architecture utilise ValidatePdfXCompliance comme filtre initial peu coûteux et réserve la méthode complète TPdf.ValidatePdfX aux fichiers validés par cette première étape

Où s'arrête ce validateur et où commence une validation prépresse complète

L'honnêteté est de rigueur pour les outils prépresse, voici donc les limites. ValidatePdfX vérifie les marqueurs d'identification, les interdictions structurelles, les clés de géométrie des pages, la déclaration de piégeage (Trapped) et l'intégration des polices sur chaque objet texte. Elle ne mesure pas le taux d'encrage total (TAC), ne valide pas la conformité de chaque espace colorimétrique (la règle du CMJN exclusif du PDF/X-1a, par exemple), ne vérifie pas la résolution des images par rapport à la trame, et n'analyse pas la surimpression ni l'aplatissement de la transparence — ces tâches requièrent un moteur prépresse complet gérant la colorimétrie, et la documentation de l'unité conseille de s'y associer pour une certification finale. Ce contrôle sur deux niveaux vous offre la détection rapide, en quelques millisecondes dans votre code Delphi, des 80 % de rejets structurels classiques, vous évitant de les découvrir le lendemain dans un e-mail de votre imprimeur

Les deux niveaux de validation, les API d'injection de marqueurs PDF/X pour générer des fichiers conformes, ainsi que les validateurs PDF/A, PDF/UA, PDF/E et PDF/VT basés sur la même architecture, sont intégrés au composant PDFium Component pour Delphi et C++Builder — un outil unique, du rendu au contrôle prépresse