PDFlibPas erzeugt PDF/X-Druckdateien aus Delphi mit einem einzigen Aufruf, TPDFlib.SetPDFXMode, der PDF/X-1a:2003, PDF/X-3:2003 oder PDF/X-4 wählt. Der Aufruf schreibt die XMP-pdfxid-Identifikation, fügt einen GTS_PDFX-Output-Intent hinzu, deklariert /Trapped /False, fixiert die PDF-Version und verweigert von da an jeden Zeichenaufruf, den der gewählte Part verbietet. CheckFileCompliance mit Test 3 auditiert die gespeicherte Datei
Das ist die Kurzantwort. Die lange zählt, weil Druckereien Dateien nicht wegen hässlicher Optik zurückweisen, sondern im Preflight, meist an einem Freitag, meist wegen etwas, das der erzeugende Code hätte abfangen können: ein RGB-Logo in einem X-1a-Job, ein Font, der referenziert statt eingebettet wurde, eine Seite ohne Zuschnittgröße. PDF/X existiert, um diese Fehler strukturell statt visuell zu machen, und die Bibliotheksseite der Aufgabe ist, dass Sie sie gar nicht erst erzeugen
Welchen PDF/X-Part sollte eine Delphi-Anwendung anvisieren?
Nehmen Sie PDF/X-4, solange der empfangende Workflow nichts anderes verlangt, und PDF/X-1a, wenn er es tut. Die drei Parts unterscheiden sich auf genau drei Achsen: Farbe, Transparenz und Basis-PDF-Version; und jede Achse mappt auf ein Gate in PDFlibPas:
- PDF/X-1a:2003 (ISO 15930-4): PDF-1.4-Syntax, nur CMYK, Grau- und Sonderfarben, keine ICC-basierte Farbe, keine Transparenz. Die Datei ist bereits in Druckfarben separiert, sodass die Druckerei gar keine Farbumwandlung mehr macht
- PDF/X-3:2003 (ISO 15930-6): weiterhin PDF 1.4 und weiterhin keine Transparenz, aber farbverwaltete Farbräume sind erlaubt, und Device-RGB wird akzeptiert, wenn der Output-Intent selbst eine RGB-Bedingung beschreibt
- PDF/X-4 (ISO 15930-7): PDF-1.6-Syntax, mit echter Transparenz und Optionalem Content (Ebenen). Das ist der Part, für den moderne RIPs gebaut sind, denn das Flatten von Transparenz auf dem Weg hinaus war die Quelle viel des alten X-1a-Schmerzes
X-1a ist in der Werbemittel-Auslieferung und in älteren Druck-Workflows, die eine Datei wollen, die sie ohne Nachdenken belichten können, nach wie vor überall. X-4 ist die bessere Ingenieurswahl, wo immer die Druckerei ihn akzeptiert: Die Transparenz überlebt unangetastet, und die Farbentscheidungen fallen am RIP mit dem echten Druckprofil statt in Ihrem Code. Die Mode-Nummern sind 1, 2 und 3, und GetInformation(203) meldet den aktiven Mode zurück. PDFlibPas definiert auch höhere Mode-Nummern für die PDF/X-6-Familie auf PDF 2.0, die anderen Versions- und Output-Intent-Regeln folgen und hier außer Scope bleiben
Was SetPDFXMode in die Datei schreibt
TPDFlib.SetPDFXMode übernimmt die Identifikation und den Boilerplate, die ein PDF/X-Reader zuerst prüft, sodass der Anwendungscode nur das echte Druckprofil und den Content liefern muss. Konkret tut ein erfolgreicher Aufruf vier Dinge:
- Schreibt den Editions-String als
pdfxid:GTS_PDFXVersionins XMP (PDF/X-1a:2003,PDF/X-3:2003oderPDF/X-4). Die 2003er-Editionen identifizieren den Part vollständig über diesen String, deshalb wird keine separate Conformance-Property geschrieben - Fügt einen Output-Intent hinzu, wenn das Dokument keinen hat, und setzt seinen Subtypen auf
/S /GTS_PDFX, den Subtypen, nach dem PDF/X-Reader suchen (ISO 32000-1 §14.11.5 definiert das Output-Intent-Dictionary) - Setzt
/Trapped /Falseim Document-Information-Dictionary, denn der Writer trappt nie, und PDF/X verlangt, dass der Trapping-Zustand deklariert wird statt unbekannt zu bleiben - Fixiert die Versionsbasis: 1.4 für X-1a und X-3, 1.6 für X-4
Zwei weitere Reparaturen passieren beim Speichern. Jede Seite ohne TrimBox bekommt eine, die ihrer MediaBox entspricht, denn PDF/X verlangt eine deklarierte Fertiggröße auf jeder Seite (ISO 32000-1 §14.11.2 behandelt die Seitenbegrenzungen), und Annotationen bekommen das Print-Flag, das der Standard erwartet. Der TrimBox-Fallback ist ein Sicherheitsnetz, keine Layout-Entscheidung. Ein Full-Bleed-Job braucht ein echtes Trim Box innerhalb einer größeren Media Box, und der Guide zu TrimBox-, BleedBox- und CropBox-Defaults in PDFlibPas erklärt, wie man diese Boxen setzt und wie Vererbung Sie überraschen kann
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]);
// Default-Profil ersetzen durch die echte Druckbedingung
if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
raise Exception.Create('Could not load the CMYK output profile');
// Embed = 1 bettet das komplette Font-Programm ein; PDF/X akzeptiert keine 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); // Prozessrot, kein RGB
Pdf.DrawText(72, 720, 'Spring catalog, page 1');
Pdf.SaveToFile(OutFile);
finally
Pdf.Free;
end;
end;
Die Reihenfolge in diesem Sketch ist absichtlich. LoadOutputIntentProfile ersetzt das Profil eines bereits existierenden Output-Intents, muss also laufen, nachdem SetPDFXMode einen angelegt hat. Der Aufruf kann auch keinen Intent aus dem Nichts zaubern: Auf einem Dokument ohne Output-Intent liefert er schlicht 0 zurück
Warum muss der Default-Output-Intent ersetzt werden?
Der Output-Intent, den SetPDFXMode für die Modes 1 bis 3 hinzufügt, deklariert DeviceCMYK, aber die ICC-Bytes dahinter sind das mitgelieferte sRGB-Profil der Bibliothek, dessen Komponentenanzahl auf 4 umgeschrieben wurde. Dieser Default existiert aus Kompatibilitätsgründen und reicht, um die Datei strukturell zu PDF/X zu machen, doch das eingebettete Profil beschreibt keine Druckmaschine. Ein Preflight-Tool, das in den Profil-Header schaut, sieht ein RGB-Profil mit CMYK-Label, und eine Druckerei, die sich fürs Proofing auf den Intent verlässt, bekommt Unsinn
Behandeln Sie LoadOutputIntentProfile(FileName, 'DeviceCMYK') deshalb als Pflicht für echte Druckarbeit und übergeben das ICC-Profil, das Ihr Druckdienstleister für Papier und Maschine nennt. TPDFlib.LoadOutputIntentProfile tauscht den DestOutputProfile-Stream aus und aktualisiert /N, den Rest des Intent-Dictionary lässt es unverändert. Insbesondere tragen Output-Condition-Identifier und beschreibende Strings des Default-Intents weiterhin sRGB-typische Werte; wenn Ihre Druckerei also auf den Identifier matcht statt auf das eingebettete Profil, klären Sie vorher, was sie prüft. Das ist eine echte Lücke der 2003er-Defaults, kein Fehler Ihres Codes
Stolperfall eins: die Version, die Sie nicht gewählt haben
Ein neues PDFlibPas-Dokument startet beim Writer-Default von PDF 1.5, was über der 1.4-Basis liegt, die PDF/X-1a und PDF/X-3 verlangen. TPDFlib.SetPDFXMode löst das im Normalfall lautlos auf: Ist die Save-Version nicht gelockt, senkt er die Dokumentversion auf die Part-Basis und macht weiter. Die erste Implementierung hat stattdessen verweigert, und jeder SetPDFXMode(1) auf einem frischen Dokument gab 0 zurück – ein gutes Beispiel für eine Regel, die auf dem Papier korrekt und in der Praxis unbrauchbar ist
Die Verweigerung existiert weiterhin für den einen Fall, in dem sie richtig ist. Hat etwas zuvor LockSaveVersion mit einer Version über der Basis gerufen, gibt SetPDFXMode 0 mit LastErrorCode 601 zurück, denn der Aufrufer hat die Version absichtlich festgenagelt, und die Bibliothek überstimmt das nicht. In geteiltem Document-Setup-Code, wo ein Helper die Version aus unabhängigen Gründen lockt, stirbt ein X-1a-Job genau hier:
Pdf := TPDFlib.Create;
try
Pdf.LockSaveVersion('1.7'); // gesetzt von einem generischen "House-Defaults"-Helper
if Pdf.SetPDFXMode(1) = 0 then
// LastErrorCode = 601: 1.7 ist gelockt, PDF/X-1a braucht 1.4.
// Für Druckjobs den Lock weglassen. PDF/X-4 ist hier kein Ausweg:
// seine 1.6-Basis liegt ebenfalls unter dem 1.7-Lock und failt genauso.
LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
Pdf.Free;
end;
Dasselbe Gate erklärt eine verwandte Grenze. Der PDF/X-Mode ist ein Schalter zur Dokumenterstellung: SetPDFXMode funktioniert auf einem neuen Dokument und gibt bei einem aus einer Datei geladenen 0 zurück, denn die Bibliothek kann für Content, den sie nicht selbst geschrieben hat, nicht garantieren. Ein bestehendes PDF nach PDF/X zu konvertieren ist ein anderer Job, der Inspektion und Reparatur braucht, kein Flag
Stolperfall zwei: Gates, die lautlos verweigern
Sobald ein PDF/X-Mode aktiv ist, verweigert PDFlibPas jeden verbotenen Content-Aufruf genau in dem Moment, in dem Sie ihn machen, statt die Datei später am Preflight scheitern zu lassen. Die Verweigerungen umfassen Device-RGB-Fills und -Strokes (ganz X-1a; X-3 und X-4, außer der Output-Intent ist RGB), ICC-basierte Farben in X-1a, Transparenz und Blend-Modes in X-1a und X-3, Optionalen Content in X-1a und X-3, JavaScript-Actions, eingebettete Datei-Anhänge, den LZW-Bildfilter, Standard-14-Fonts, die un eingebettet bleiben würden, und Public-Key-Verschlüsselung. Passwort-Verschlüsselung über Encrypt wird zur Erstellungszeit nicht gegated, aber der Validator markiert jede verschlüsselte Datei, halten Sie Druckjobs also unverschlüsselt
Was die Verweigerungen nicht teilen, ist ein Fehlercode – und das ist die Falle. Die Farbraum-Gates in SetFillColor, SetLineColor, die ICC-basierten Farbaufrufe und EncryptPublicKey setzen LastErrorCode auf 601, den Conformance-Code der Bibliothek, den auch die PDF/A-Gates nutzen. Die Transparenz-, JavaScript-, Anhang-, LZW- und AddStandardFont-Gates geben schlicht 0 zurück und lassen LastErrorCode unangetastet, was heißt, dass er noch einen Wert von einem früheren Aufruf halten kann. Code, der nach einem Batch von Zeichenaufrufen nur LastErrorCode prüft, verpasst die Hälfte der Verweigerungen – prüfen Sie jeden Rückgabewert:
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 in X-1a / X-3
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
'app.alert(1)', 0), 'JavaScript link'); // 0 in jedem Part
Fonts verdienen eine eigene Warnung. AddStandardFont gibt unter PDF/X 0 zurück, weil die Bibliothek die Standard-14-Outlines nicht mitliefert und sie daher nicht einbetten kann. AddTrueTypeFont ist anders: Im PDF/A-Mode stuft er Embed = 0 für Sie zu einem vollständigen Embed auf, im PDF/X-Mode tut er das nicht, also landet ein referenzierter Systemfont in der Datei, und nur der Validator erwischt es. Übergeben Sie selbst 1 (vollständig) oder 2 (Subset) oder rufen einmal beim Setup SetEmbedAllFonts(1), damit eine verirrte 0 nicht durchrutscht
Wie prüfen Sie eine PDF/X-Datei, bevor sie zur Druckerei geht?
Fahren Sie CheckFileCompliance mit ComplianceTest = 3 über die gespeicherte Datei. TPDFlib.CheckFileCompliance gibt 0 zurück, wenn er nichts gefunden hat, sonst ein String-List-Handle, dessen Items Zeilen mit dem Präfix PDFX- sind. Das PDF/X-Audit liest die Datei durch denselben Streaming-Reader wie das PDF/A- und PDF/UA-Preflight in PDFlibPas und prüft die Regeln, die ein Druck-Preflight zuerst prüft:
- Die Datei ist nicht verschlüsselt, das XMP trägt eine bekannte
GTS_PDFXVersion, und die PDF-Version überschreitet die Part-Basis nicht - Genau ein Output-Intent existiert, sein Subtyp ist
GTS_PDFX, und er trägt ein Ziel-Profil und einen Output-Condition-Identifier - Das Information-Dictionary deklariert
/Trapped - Jede Seite hat eine TrimBox, die innerhalb der MediaBox liegt, und eine BleedBox, wenn vorhanden, umschließt die TrimBox
- Jeder auf einer Seite verwendete Font hat ein eingebettetes Programm, wobei Type0-Fonts über ihren Descendant-Font geprüft werden
- Keine verbotenen Annotationstypen (RichMedia, Screen, 3D, Movie, Sound, FileAttachment), keine JavaScript-Open-Action oder document-level-JavaScript, keine eingebetteten Dateien und kein LZWDecode-Filter irgendwo im Objektgraphen
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 // Items sind 1-basiert
Report.Add(Pdf.GetStringListItem(ListId, I));
finally
Pdf.ReleaseStringList(ListId);
end;
end;
Seien Sie sich im Klaren, was dieses Bestehen bedeutet. Der PDFlibPas-Validator auditiert die oben gelisteten Strukturregeln, und das sind die, die Generatorfehler erwischen. Er ist kein farbakkurates Preflight: Er prüft keine Farbauftragssumme, keine Bildauflösung und nicht, ob das eingebettete Profil zur Bedingung passt, die Ihre Druckerei fährt. Nutzen Sie ihn als Gate in Ihrer Build- oder Export-Pipeline und überlassen die maschinenspezifischen Prüfungen dem Preflight-Profil der Druckerei – das fahren sie sowieso
FAQ
Unterstützt PDFlibPas PDF/X-4-Transparenz?
Ja. In SetPDFXMode(3) erlaubt PDFlibPas Transparenz, Blend-Modes und Optional-Content-Groups und schreibt eine PDF-1.6-Datei. Dieselben Aufrufe geben unter PDF/X-1a und PDF/X-3 0 zurück
Kann PDFlibPas ein bestehendes PDF nach PDF/X konvertieren?
Nein. SetPDFXMode greift nur auf Dokumente, die in der aktuellen TPDFlib-Session erzeugt wurden, und gibt bei einer geladenen Datei 0 zurück. Ein bestehendes PDF können Sie weiterhin mit CheckFileCompliance und Test 3 auditieren
Welchen Fehlercode liefert ein verweigerter PDF/X-Aufruf?
Farbraum- und Public-Key-Verschlüsselungs-Verweigerungen setzen LastErrorCode auf 601. Andere Gates wie Transparenz, JavaScript und Standard-14-Fonts geben nur 0 zurück, testen Sie also immer das Funktionsergebnis
PDF/X-Ausgabe, der PDF/X-Validator und der Rest des Druck- und Archivierungs-Werkzeugs sind Teil von PDFlibPas, der nativen Object-Pascal-PDF-Bibliothek von losLab für Delphi. Editionen, Plattformen und die vollständige API-Referenz finden Sie auf der PDFlibPas Delphi PDF Library Produktseite