Une imprimerie de production vous renvoie votre tirage de relevés de 80 000 pages avec un refus en une ligne : « pas de PDF/VT, le RIP ne peut pas mettre en cache ». Le fichier s'ouvre sans problème dans tous les lecteurs de votre poste, les couleurs sont correctes, les données ont été fusionnées comme il faut. Rien de tout cela n'est ce que la presse numérique demande. L'impression variable à haut débit dépend du fait que la presse puisse reconnaître que le bloc du logo client à la page 1 est, octet pour octet, le même objet que celui de la page 40 000, l'afficher une seule fois, puis le réutiliser. PDF/VT est la norme qui rend cette promesse vérifiable par la machine, et « l'apparence est correcte » est précisément le piège, parce que la structure lue par le RIP est invisible à l'écran
PDFiumPas expose cette structure via une petite surface sur TPdf: SaveAsPdfVT écrit, ValidatePdfVT vérifie. Cet article porte sur ce que ces deux méthodes déposent réellement sur disque et inspectent, où l'ISO 16612-2 est plus stricte qu'il n'y paraît, et quelles parties constituent de véritables ancres structurelles plutôt qu'un preflight complet que vous pourriez facturer à un client
Ce que standardise PDF/VT, et pourquoi PDF/X vient d'abord
PDF/VT (ISO 16612-2:2010) n'est pas un format de fichier nouveau. C'est une couche de métadonnées d'optimisation greffée sur un fichier PDF/X, et cet ordre est essentiel. La norme définit trois niveaux de conformité, mais seuls deux d'entre eux nomment un fichier PDF : PDF/VT-1, un document autonome, et PDF/VT-2, un modèle de jeu de fichiers où les pages référencent des ressources externes partagées. Le troisième jeton que vous pouvez voir, PDF/VT-2s, n'est pas du tout une valeur de niveau de fichier ; il vit dans un en-tête de flux MIME décrit à l'annexe A. Si vous trouvez du code qui inscrit GTS_PDFVTVersion = "PDF/VT-2s" dans le XMP d'un document, ce code est incorrect
La règle non négociable pour un fichier unique est la base PDF/X. L'ISO 16612-2 §6.2.1 exige que chaque fichier PDF/VT-1 soit aussi un fichier PDF/X-4 valide. Le jeu de fichiers PDF/VT-2, selon §6.2.2, doit au contraire reposer sur PDF/X-4p, PDF/X-5g ou PDF/X-5pg. C'est pourquoi un rédacteur PDF/VT ne peut pas se contenter d'ajouter quelques clés d'identifiant : il doit embarquer tout l'ensemble de marqueurs PDF/X-4, ce qui signifie un OutputIntent, un profil de destination ICC intégré, les entrées XMP et Info du document correspondantes, un trailer /ID, et pas de chiffrement. En omettre un seul vous laisse avec un fichier qui prétend être PDF/VT et échoue dès qu'un consommateur conforme vérifie la base. PDFiumPas traite la couche PDF/X-4 comme partie intégrante de l'enregistrement PDF/VT, donc vous n'appelez pas d'abord un SaveAsPdfX séparé ; l'injecteur écrit les deux couches en une seule passe
Écrire un fichier avec SaveAsPdfVT
L'appel minimal n'a besoin que d'un document actif, car TPdfVTSaveOptions.Default fournit un profil ICC sRGB intégré et une conformité pvc1. L'enregistrement se déroule en trois étapes en interne : il retire toute sécurité (injecter des marqueurs en clair dans un flux d'objets chiffré le corromprait), il relie le dictionnaire Info existant du document et le trailer /ID dans l'ensemble de marqueurs pour que les valeurs XMP et Info concordent, puis il ajoute les objets PDF/X-4 et PDF/VT via une mise à jour incrémentielle
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
if Pdf.LoadFromFile('statements-merged.pdf') then
begin
// Default options: built-in sRGB OutputIntent, PDF/VT-1, synthesised DPart
if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
Writeln('PDF/VT-1 written')
else
Writeln('Save failed (document not active?)');
end;
finally
Pdf.Free;
end;
end;
Pour une sortie de production réelle, vous voudrez presque toujours remplacer l'OutputIntent par la caractérisation de votre presse, plutôt que par le repli sRGB générique. Fournissez les octets ICC et les identifiants de condition via TPdfVTSaveOptions:
var
Pdf: TPdf;
Opt: TPdfVTSaveOptions;
Icc: TBytes;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('directmail-merged.pdf');
Icc := LoadIccProfile('GRACoL2013_CRPC6.icc'); // your own loader
Opt := TPdfVTSaveOptions.Default;
Opt.Conformance := pvc1; // pvc2 is normalised to pvc1 on write
Opt.IccProfileData := Icc;
Opt.OutputConditionIdentifier := 'CGATS21_CRPC6';
Opt.OutputCondition := 'Commercial print, coated, CRPC6';
Opt.RegistryName := 'http://www.color.org';
Opt.Title := 'Spring 2026 Direct Mail Run';
Opt.Trapped := ptvFalse; // PDF/X Info /Trapped state
Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
finally
Pdf.Free;
end;
end;
Un détail de cet extrait est un garde-fou volontaire plutôt qu'une limitation discutable. Définir Opt.Conformance := pvc2 ne produit pas de fichier PDF/VT-2. Le rédacteur normalise toute requête non-pvc1 requête vers pvc1, parce que PDF/VT-2 est un format de jeu de fichiers et qu'un rédacteur mono-fichier qui ajoute un seul document de sortie ne peut physiquement pas assembler l'ensemble de ressources externes exigé par le §6.2.2. La pvc2 valeur existe pour le chemin de lecture, afin que ValidatePdfVT puisse reconnaître et signaler un document de jeu de fichiers existant ; ce n'est pas une cible d'écriture
L'arbre DPart : structurer ce que le RIP lit réellement
Le cœur de PDF/VT est la hiérarchie Document Part (DPart). C'est elle qui permet à une presse de découper un long tirage en enregistrements, de regrouper les enregistrements par destinataire ou par lot postal, et d'associer des métadonnées de Document Part pour que l'équipement en aval puisse router et facturer chaque élément. L'ISO 16612-2 §6.5 décrit le câblage : le catalogue porte un /DPartRoot, le nœud DPart racine porte /DPartRootNode et un /NodeNameList nommant chaque niveau de la hiérarchie, les DPart feuilles couvrent des plages de l'arbre des pages, et chaque page qui appartient à un part pointe vers sa feuille via une entrée de niveau page /DPart
Lorsque votre document source contient déjà une hiérarchie exploitable, SaveAsPdfVT la conserve. Quand ce n'est pas le cas, le rédacteur synthétise un minimum : un seul DPart au niveau du document qui couvre l'arbre des pages courant dans l'ordre, avec une /DPart rétro-référence ajoutée à chaque objet de page actif et une hiérarchie à un seul niveau /NodeNameList [/Document]. Soyez honnête avec vous-même sur ce qu'est cet arbre minimal. C'est une ancre structurelle qui satisfait aux exigences de forme du §6.5 ; ce n'est pas une métadonnée métier. Il ne peut pas inventer des destinataires, des limites de pli ou des lots de produits, parce que cette information n'existait pas dans la source. Si vous disposez de données par destinataire, vous êtes censé construire vous-même un arbre DPart plus profond et étendre le /NodeNameList pour qu'il corresponde aux niveaux que vous créez
Une validation qui va au-delà de la simple présence des clés
ValidatePdfVT renvoie un TPdfVTValidationResult enregistrement avec trois choses : la conformité détectée Conformance, un ensemble de Issues, et un IsCompliant d'aide qui n'est vrai que lorsque la conformité correspond à un niveau réel et que l'ensemble des problèmes est vide. L'énumération des problèmes est volontairement précise, si bien qu'un échec vous indique quelle clause vous avez manquée plutôt qu'un simple "invalide" :
var
Pdf: TPdf;
Res: TPdfVTValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('statements-pdfvt.pdf');
Res := Pdf.ValidatePdfVT;
if Res.IsCompliant then
Writeln('PDF/VT compliant: ', VTLevelName(Res.Conformance))
else
begin
if pvviMissingDPartRoot in Res.Issues then
Writeln('DPart hierarchy missing or unusable');
if pvviMissingPdfXIdentifier in Res.Issues then
Writeln('PDF/X-4 base identifier absent');
if pvviMissingOutputIntent in Res.Issues then
Writeln('OutputIntent / ICC profile missing');
if pvviEncryptionPresent in Res.Issues then
Writeln('Encrypted - PDF/X forbids this');
end;
finally
Pdf.Free;
end;
end;
Les deux contrôles qu'il vaut la peine de comprendre en profondeur sont l'appariement de conformité et le parcours DPart, car tous deux étaient autrefois trop permissifs et ont été resserrés pour coller à la spécification. Côté appariement, le validateur fait une correspondance exacte, pas un « n'importe quel PDF/X fera l'affaire » : un fichier PDF/VT-1 n'est accepté que sur une base PDF/X-4, et un fichier PDF/VT-2 uniquement sur PDF/X-4p, PDF/X-5g ou PDF/X-5pg. Un marqueur PDF/VT-1 posé sur une base PDF/X-1a est signalé, pas laissé passer
Le parcours DPart est l'endroit où se trouve l'essentiel de la rigueur. Il ne suffit pas que le catalogue possède une /DPartRoot clé, car un objet vide forgé ou dépourvu de liens vers les pages ne peut toujours pas être consommé. HasValidDPartHierarchy et la fonction récursive ValidateDPartNode tracent toute la structure : elles suivent les liens parents, rejettent les enfants en double et les cycles, imposent que /Start et /DParts soient mutuellement exclusifs, et exigent que les plages de pages feuille couvrent l'arbre des pages en ordre profondeur d'abord, chaque /DPart de page pointant vers la feuille qui le contient. Tous ces défauts internes se résument au seul pvviMissingDPartRoot indicateur de problème plutôt que d'étendre l'énumération publique, alors considérez ce seul drapeau comme « l'hiérarchie DPart est inutilisable », et non littéralement comme « la clé racine est absente »
Trois pièges syntaxiques désormais imposés par le validateur
Des passages successifs sur le tableau 4 du §6.5 ont révélé des formes que les versions précédentes acceptaient mais que la norme n'autorise pas. Ce sont le genre d'erreurs qu'un arbre DPart construit à la main commet facilement, donc elles méritent d'être signalées explicitement :
/DPartsest un tableau de tableaux, pas un tableau plat. Chaque élément du tableau externe doit lui-même être un tableau de références indirectes. Un/DParts [9 0 R]plat est rejeté ; la forme conforme est/DParts [[9 0 R] [10 0 R]]. Cela empêche une structure non hiérarchique de se faire passer pour un niveau valide/Endne marque qu'une véritable plage multi-page. Un DPart feuille peut porter/Endseulement s'il possède aussi/Start, et/Enddoit se situer après/Startdans l'ordre de l'arbre de pages. Un/Start 3 0 R /End 3 0 Rrend désormais l'hiérarchie inutilisable au lieu d'être interprété comme une partie d'une seule page/NodeNameListdoivent rester valides en tant que NMTOKEN XML après le décodage des noms PDF. Un nom comme/Bad#20Namese développe en un nom contenant un espace, ce qui n'est pas un jeton valide. L'implémentation effectue une vérification ASCII légère (lettres, chiffres,.,-,_,:et des octets non ASCII) qui détecte les erreurs d'espacement et de délimiteur sans rejeter les noms localisés légitimes ou spécifiques à un fournisseur
Marqueurs XMP : deux façons d'écrire la même propriété
L'identification PDF/VT se trouve dans XMP sous l'espace de noms pdfvtid, plus précisément GTS_PDFVTVersion et GTS_PDFVTModDate, aux côtés des xmp:CreateDate et xmp:ModifyDate. Une subtilité qui provoque de faux rapports « manquant » chez les lecteurs naïfs est que chacun de ces éléments peut être sérialisé de deux façons : sous forme de texte d'élément (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) ou comme attribut RDF sur l'élément de description. PDFiumPas lit les deux formes, donc un fichier écrit par un autre outil au format attribut n'est pas pénalisé. Il applique aussi la règle de cohérence du §6.3 selon laquelle GTS_PDFVTModDate doit être égal à xmp:ModifyDate; une discordance déclenche pvviModDateMismatch
Une autre règle de la même clause : une valeur GTS_PDFVTVersion inconnue de pvcUnknown est conservée comme pvcNone plutôt que d'être rabattue sur pvcNone signifie « aucun marqueur PDF/VT, un PDF ordinaire », tandis que pvcUnknown signifie « quelque chose a estampillé une version que ce validateur ne reconnaît pas » (le PDF/VT-2s cas parmi eux). Confondre les deux masquerait un fichier mal formé dans la même catégorie qu'un document ordinaire
Où s'arrête la garantie
Il vaut la peine d'être précis sur la limite de ce que ces méthodes promettent, parce que la conformité à l'impression de données variables a des enjeux financiers réels. Les contrôles DPart et d'appariement constituent une validation structurelle au niveau des octets. Ils confirment que le squelette d'optimisation, les marqueurs de base PDF/X-4, l'OutputIntent et le XMP sont présents et cohérents en interne. Ce n'est pas un preflight PDF/X-4 au niveau du contenu : ils ne vérifient pas que chaque couleur reste dans la condition de sortie déclarée, que toutes les polices sont incorporées, ni qu'aucun cas limite interdit de mélange de transparence ne s'est glissé. Pour un travail que vous mettez sur une presse contractuelle, associez la validation structurelle de PDFiumPas à un moteur de preflight PDF/X dédié et à un tirage de test, comme vous le feriez pour n'importe quelle autre affirmation de conformité. La couche structurelle attrape les échecs qui cassent silencieusement la mise en cache du RIP ; elle constitue la moitié d'un contrôle complet, pas son ensemble
Si vous intégrez ces contrôles dans une porte de publication plus large, la même approche d'analyse au niveau des octets sous-tend les autres travaux de la bibliothèque sur les normes, notamment la validation des flux d'objets et de références croisées avant qu'un fichier n'atteigne le preflight, ainsi que la discipline des objets partagés derrière les tampons de page réutilisables avec des Form XObjects qui rendent d'emblée un document compatible avec le RIP. Les API de sauvegarde et de validation PDF/VT et PDF/X décrites ici font partie du composant PDFium VCL pour Delphi et C++Builder, dont la page produit contient la référence complète de conformité