PDFlibPas produit des fichiers print PDF/X depuis Delphi en un seul appel, TPDFlib.SetPDFXMode, qui sélectionne PDF/X-1a:2003, PDF/X-3:2003 ou PDF/X-4. L'appel écrit l'identification XMP pdfxid, ajoute un output intent GTS_PDFX, déclare /Trapped /False, verrouille la version PDF, puis refuse désormais les appels de dessin que la partie choisie interdit. CheckFileCompliance avec le test 3 audite le fichier enregistré
Voilà pour la réponse courte. La longue compte, parce que les imprimeurs ne rejettent pas les fichiers parce qu'ils sont moches : ils les rejettent au preflight, souvent un vendredi, souvent pour quelque chose que le code générateur aurait pu attraper : un logo RGB dans un job X-1a, une police référencée au lieu d'être embarquée, une page sans format de coupe. PDF/X existe pour rendre ces échecs structurels plutôt que visuels, et le rôle de la bibliothèque est justement de vous empêcher de les produire
Quelle partie PDF/X viser depuis une application Delphi ?
Choisissez PDF/X-4 sauf si le flux en aval dit le contraire, et choisissez PDF/X-1a quand c'est le cas. Les trois parties diffèrent sur exactement trois axes, la couleur, la transparence et la version PDF de base, et chaque axe correspond à un verrou dans PDFlibPas :
- PDF/X-1a:2003 (ISO 15930-4) : syntaxe PDF 1.4, uniquement CMJN, gris et tons directs, pas de couleur ICC, pas de transparence. Le fichier est déjà séparé en couleurs offset, donc l'imprimeur ne fait aucune conversion colorimétrique
- PDF/X-3:2003 (ISO 15930-6) : toujours PDF 1.4 et toujours pas de transparence, mais les espaces gérés colorimétriquement sont permis, et le RGB périphérique est accepté quand l'output intent lui-même décrit une condition RGB
- PDF/X-4 (ISO 15930-7) : syntaxe PDF 1.6, avec transparence vivante et contenus optionnels (calques). C'est la partie pour laquelle les RIP modernes sont conçus, car c'est l'aplatissement de la transparence en sortie qui a causé une bonne partie des vieilles souffrances du X-1a
Le X-1a est encore partout dans la diffusion publicitaire et dans les vieux flux d'impression qui veulent un fichier qu'ils peuvent imposer sans réfléchir. Le X-4 est le meilleur choix d'ingénierie dès que l'imprimeur l'accepte : la transparence survit intacte, et les décisions de couleur se prennent au RIP avec le vrai profil de presse plutôt que dans votre code. Les numéros de mode sont 1, 2 et 3 respectivement, et GetInformation(203) renvoie le mode actif. PDFlibPas définit aussi des numéros de mode supérieurs pour la famille PDF/X-6 sur PDF 2.0, qui suivent d'autres règles de version et d'output intent et sortent du cadre ici
Ce que SetPDFXMode écrit dans le fichier
TPDFlib.SetPDFXMode gère l'identification et le passe-partout qu'un lecteur PDF/X vérifie en premier, si bien que le code applicatif n'a plus qu'à fournir le vrai profil de presse et le contenu. Concrètement, un appel réussi fait quatre choses :
- Écrit la chaîne d'édition dans le XMP comme
pdfxid:GTS_PDFXVersion(PDF/X-1a:2003,PDF/X-3:2003ouPDF/X-4). Les éditions 2003 identifient la partie complètement par cette chaîne, donc aucune propriété de conformité séparée n'est écrite - Ajoute un output intent si le document n'en a pas et met son sous-type à
/S /GTS_PDFX, le sous-type que les lecteurs PDF/X cherchent (ISO 32000-1 §14.11.5 définit le dictionnaire d'output intent) - Met
/Trapped /Falsedans le dictionnaire d'informations du document, parce que le générateur ne fait jamais de trapping et que PDF/X exige que l'état de trapping soit déclaré plutôt que laissé inconnu - Fixe la version de base : 1.4 pour X-1a et X-3, 1.6 pour X-4
Deux autres réparations ont lieu à l'enregistrement. Chaque page sans TrimBox en reçoit une égale à sa MediaBox, puisque PDF/X exige un format fini déclaré sur chaque page (ISO 32000-1 §14.11.2 couvre les limites de page), et les annotations reçoivent le drapeau d'impression attendu par la norme. Ce repli TrimBox est un filet de sécurité, pas une décision de mise en page. Un job au bord perdant a besoin d'un vrai trim box à l'intérieur d'une media box plus grande, et le guide des valeurs par défaut TrimBox, BleedBox et CropBox dans PDFlibPas explique comment poser ces boîtes et comment l'héritage peut vous surprendre
uses
PDFlibrary;
procedure BuildX1aAd(const OutFile, PressProfile: string);
var
Pdf: TPDFlib;
begin
Pdf := TPDFlib.Create;
try
if Pdf.SetPDFXMode(1) <> 1 then // 1 = PDF/X-1a:2003
raise Exception.CreateFmt('PDF/X mode refused (error %d)',
[Pdf.LastErrorCode]);
// Remplacez le profil par défaut par la vraie condition de presse
if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
raise Exception.Create('Could not load the CMYK output profile');
// Embed = 1 embarque le programme de police complet ; PDF/X refuse 0
if Pdf.AddTrueTypeFont('Arial {1252}', 1) = 0 then
raise Exception.Create('Font could not be loaded for embedding');
Pdf.SetTextSize(24);
Pdf.SetFillColorCMYK(0, 1, 1, 0); // rouge quadri, pas de RGB
Pdf.DrawText(72, 720, 'Spring catalog, page 1');
Pdf.SaveToFile(OutFile);
finally
Pdf.Free;
end;
end;
L'ordre de ce croquis est délibéré. LoadOutputIntentProfile remplace le profil d'un output intent qui existe déjà, donc il doit tourner après que SetPDFXMode en a créé un. L'appel ne peut pas non plus faire apparaître un intent de nulle part : sur un document sans output intent, il renvoie simplement 0
Pourquoi l'output intent par défaut doit-il être remplacé ?
L'output intent que SetPDFXMode ajoute pour les modes 1 à 3 déclare DeviceCMYK, mais les octets ICC derrière sont le profil sRGB livré avec la bibliothèque, avec le nombre de composantes réécrit à 4. Ce défaut existe pour la compatibilité ascendante et suffit à rendre le fichier structurellement PDF/X, pourtant le profil embarqué ne décrit aucune presse. Un outil de preflight qui inspecte l'en-tête du profil verra un profil RGB portant une étiquette CMYK, et un imprimeur qui se fie à l'intent pour l'épreuve reçoit du n'importe quoi
Traitez donc LoadOutputIntentProfile(FileName, 'DeviceCMYK') comme obligatoire pour du vrai travail d'impression, et passez le profil ICC que votre prestataire nomme pour le papier et la presse. TPDFlib.LoadOutputIntentProfile remplace le flux DestOutputProfile et met à jour /N, et il laisse le reste du dictionnaire d'intent tel qu'écrit. En particulier, l'identifiant de condition de sortie et les chaînes descriptives de l'intent par défaut portent encore des valeurs de style sRGB ; si votre imprimeur filtre sur l'identifiant plutôt que sur le profil embarqué, confirmez avec lui ce qu'il vérifie avant d'expédier. C'est une vraie lacune des défauts de l'ère 2003, pas quelque chose que votre code a mal fait
Piège numéro un : la version que vous n'avez pas choisie
Un nouveau document PDFlibPas démarre au défaut du générateur, PDF 1.5, qui est au-dessus de la base 1.4 qu'exigent PDF/X-1a et PDF/X-3. TPDFlib.SetPDFXMode règle cela silencieusement dans le cas courant : si la version d'enregistrement n'a pas été verrouillée, il abaisse la version du document à la base de la partie et continue. La première implémentation refusait au lieu de cela, et chaque SetPDFXMode(1) sur un document neuf renvoyait 0, un bel exemple de règle correcte sur le papier et inutilisable en pratique
Le refus existe toujours pour le seul cas où il a raison. Si quelque chose a appelé plus tôt LockSaveVersion avec une version au-dessus de la base, SetPDFXMode renvoie 0 avec LastErrorCode 601, parce que l'appelant a fixé la version exprès et que la bibliothèque ne passera pas outre. Dans du code de préparation de document partagé, où un utilitaire verrouille la version pour des raisons sans rapport, c'est exactement là qu'un job X-1a meurt :
Pdf := TPDFlib.Create;
try
Pdf.LockSaveVersion('1.7'); // posé par un utilitaire générique de "défauts maison"
if Pdf.SetPDFXMode(1) = 0 then
// LastErrorCode = 601 : 1.7 verrouillé, PDF/X-1a exige 1.4.
// Passez le verrou pour les jobs print. PDF/X-4 n'est pas une échappatoire :
// sa base 1.6 est aussi sous le verrou 1.7 et échoue de la même façon.
LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
Pdf.Free;
end;
Le même verrou explique une limite voisine. Le mode PDF/X est un commutateur de création de document : SetPDFXMode fonctionne sur un document neuf et renvoie 0 sur un document chargé depuis un fichier, parce que la bibliothèque ne peut pas répondre du contenu qu'elle n'a pas écrit. Convertir un PDF existant en PDF/X est un autre travail qui demande inspection et réparation, pas un drapeau
Piège numéro deux : des verrous qui refusent en silence
Quand un mode PDF/X est actif, PDFlibPas refuse chaque appel de contenu que la partie choisie interdit, au moment où vous le faites, plutôt que de laisser le fichier échouer au preflight plus tard. Les refus couvrent les remplissages et tracés RGB périphérique (tout le X-1a ; X-3 et X-4 sauf si l'output intent est RGB), les couleurs ICC en X-1a, la transparence et les modes de fusion en X-1a et X-3, le contenu optionnel en X-1a et X-3, les actions JavaScript, les pièces jointes embarquées, le filtre d'image LZW, les polices Standard 14 qui resteraient non embarquées, et le chiffrement à clé publique. Le chiffrement par mot de passe via Encrypt n'est pas verrouillé à la création, mais le validateur signale tout fichier chiffré, alors gardez les jobs print sans chiffrement
Ce que les refus ne partagent pas, c'est un code d'erreur, et voilà le piège. Les verrous d'espace colorimétrique dans SetFillColor, SetLineColor, les appels de couleur ICC et EncryptPublicKey mettent LastErrorCode à 601, le code de conformité de la bibliothèque que les verrous PDF/A utilisent aussi. Les verrous transparence, JavaScript, pièce jointe, LZW et AddStandardFont se contentent de renvoyer 0 et laissent LastErrorCode intact, ce qui veut dire qu'il peut encore contenir une valeur d'un appel antérieur. Un code qui ne vérifie que LastErrorCode après une salve d'appels de dessin ratera la moitié des refus ; vérifiez donc chaque valeur de retour :
procedure Require(Ok: Integer; const What: string);
begin
if Ok = 0 then
raise EPrintConformance.CreateFmt('%s refused under PDF/X', [What]);
end;
Require(Pdf.SetFillColorCMYK(0, 0, 0, 1), 'CMYK fill');
Require(Pdf.SetTransparency(40), 'transparency'); // 0 en X-1a / X-3
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
'app.alert(1)', 0), 'JavaScript link'); // 0 dans toutes les parties
Les polices méritent leur propre avertissement. AddStandardFont renvoie 0 sous PDF/X parce que la bibliothèque ne livre pas les contours des Standard 14 et ne peut donc pas les embarquer. AddTrueTypeFont fait autrement : en mode PDF/A, il promeut un Embed = 0 en embarquement complet pour vous, mais en mode PDF/X il ne le fait pas, donc une police système référencée part dans le fichier et seul le validateur l'attrape. Passez 1 (complet) ou 2 (sous-ensemble) vous-même, ou appelez SetEmbedAllFonts(1) une fois à l'initialisation pour qu'un 0 égaré ne se glisse pas
Comment vérifier un fichier PDF/X avant de l'envoyer à l'imprimeur ?
Lancez CheckFileCompliance avec ComplianceTest = 3 sur le fichier enregistré. TPDFlib.CheckFileCompliance renvoie 0 quand il n'a rien relevé, et sinon un handle de liste de chaînes dont les éléments sont des lignes préfixées PDFX-. L'audit PDF/X lit le fichier via le même lecteur en flux que le preflight PDF/A et PDF/UA dans PDFlibPas, et il vérifie les règles qu'un preflight print vérifie en premier :
- Le fichier n'est pas chiffré, le XMP porte un
GTS_PDFXVersionconnu, et la version PDF ne dépasse pas la base de la partie - Exactement un output intent existe, son sous-type est
GTS_PDFX, et il porte un profil de destination et un identifiant de condition de sortie - Le dictionnaire d'informations déclare
/Trapped - Chaque page a un TrimBox situé à l'intérieur de la MediaBox, et une BleedBox, quand elle existe, englobe le TrimBox
- Chaque police utilisée sur une page a un programme embarqué, les polices Type0 étant vérifiées via leur police descendante
- Aucun type d'annotation interdit (RichMedia, Screen, 3D, Movie, Sound, FileAttachment), aucune action JavaScript à l'ouverture ni JavaScript au niveau du document, aucun fichier embarqué, et aucun filtre LZWDecode nulle part dans le graphe d'objets
function PreflightPDFX(Pdf: TPDFlib; const FileName: string;
Report: TStrings): Boolean;
var
ListId, I: Integer;
begin
ListId := Pdf.CheckFileCompliance(FileName, '', 3, 0); // 3 = PDF/X
Result := ListId = 0;
if Result then
Exit;
try
for I := 1 to Pdf.GetStringListCount(ListId) do // les éléments sont indexés à partir de 1
Report.Add(Pdf.GetStringListItem(ListId, I));
finally
Pdf.ReleaseStringList(ListId);
end;
end;
Soyez clair sur ce que signifie ce succès. Le validateur PDFlibPas audite les règles structurelles listées ci-dessus, et ce sont elles qui attrapent les erreurs de génération. Ce n'est pas un preflight colorimétriquement exact : il ne vérifie ni la couverture d'encre totale, ni la résolution des images, ni si le profil embarqué correspond à la condition que tourne votre imprimeur. Utilisez-le comme verrou dans votre pipeline de build ou d'export, et laissez les contrôles propres à la presse au profil de preflight de l'imprimeur, qu'il fera tourner de toute façon
FAQ
PDFlibPas gère-t-il la transparence PDF/X-4 ?
Oui. Avec SetPDFXMode(3), PDFlibPas autorise la transparence, les modes de fusion et les groupes de contenu optionnel, et écrit un fichier PDF 1.6. Ces mêmes appels renvoient 0 sous PDF/X-1a et PDF/X-3
PDFlibPas peut-il convertir un PDF existant en PDF/X ?
Non. SetPDFXMode ne s'applique qu'aux documents créés dans la session TPDFlib courante et renvoie 0 pour un fichier chargé. Vous pouvez toujours auditer n'importe quel PDF existant avec CheckFileCompliance et le test 3
Quel code d'erreur renvoie un appel PDF/X refusé ?
Les refus d'espace colorimétrique et de chiffrement à clé publique mettent LastErrorCode à 601. Les autres verrous, comme la transparence, le JavaScript et les polices Standard 14, se contentent de renvoyer 0 ; testez donc toujours le résultat de la fonction
La sortie PDF/X, le validateur PDF/X et le reste de la boîte à outils print et archivage font partie de PDFlibPas, la bibliothèque PDF native en Object Pascal de losLab pour Delphi. Voir la page produit de la PDF Library Delphi PDFlibPas pour les éditions, les plateformes et la référence API complète