Teknisk artikel

PDF/X-1a, X-3 og X-4-output i Delphi med PDFlibPas

PDFlibPas producerer PDF/X-printfiler fra Delphi gennem ét kald, TPDFlib.SetPDFXMode, som vælger PDF/X-1a:2003, PDF/X-3:2003 eller PDF/X-4. Kaldet skriver XMP pdfxid-identifikationen, tilføjer et GTS_PDFX output intent, erklærer /Trapped /False, fastlåser PDF-versionen og afviser derefter alle tegnekald, den valgte del forbyder. CheckFileCompliance med test 3 reviderer den gemte fil

Det er det korte svar. Det lange betyder noget, for trykkerier afviser ikke filer for at være grimme, de afviser dem i preflight, typisk en fredag, typisk på noget, den genererende kode kunne have opdaget: et RGB-logo i et X-1a-job, en font, der blev refereret i stedet for indlejret, en side uden trimmestørrelse. PDF/X findes for at gøre de fejl strukturelle frem for visuelle, og bibliotekens side af opgaven er at stoppe en i at fremstille dem fra starten

Hvilken PDF/X-del bør en Delphi-applikation sigte efter?

Vælg PDF/X-4, medmindre det modtagende workflow siger andet, og vælg PDF/X-1a, når det gør. De tre dele adskiller sig på præcis tre akser: farve, transparens og basis-PDF-version, og hver akse mapper på en gate inde i PDFlibPas:

  • PDF/X-1a:2003 (ISO 15930-4): PDF 1.4-syntaks, kun CMYK, gråtoner og spotfarver, ingen ICC-baseret farve, ingen transparens. Filen er allerede separeret i trykfarver, så printeren laver slet ingen farvekonvertering
  • PDF/X-3:2003 (ISO 15930-6): stadig PDF 1.4 og stadig ingen transparens, men farvestyrede spaces er tilladt, og device RGB accepteres, når output intentet selv beskriver en RGB-tilstand
  • PDF/X-4 (ISO 15930-7): PDF 1.6-syntaks med live-transparens og optional content (lag). Det er den del, moderne RIP'er er bygget til, for fladning af transparens på vejen ud er, hvor meget af den gamle X-1a-smerte kom fra

X-1a er stadig overalt i annoncelevering og i ældre print-workflows, der vil have en fil, de kan image uden at tænke. X-4 er det bedre tekniske valg, når printshoppen accepterer det: transparensen overlever intakt, og farvebeslutningerne sker ved RIP'en med den rigtige trykprofil i stedet for i ens kode. Modenumrene er henholdsvis 1, 2 og 3, og GetInformation(203) rapporterer, hvilken mode der er aktiv. PDFlibPas definerer også højere modenumre for PDF/X-6-familien på PDF 2.0, som følger andre versions- og output intent-regler og ligger uden for dette indhold

Hvad SetPDFXMode skriver ind i filen

TPDFlib.SetPDFXMode håndterer identifikationen og den boilerplate, en PDF/X-læser tjekker først, så programkoden kun skal levere den rigtige trykprofil og indholdet. Konkret gør et successfuldt kald fire ting:

  • Skriver editions-strengen ind i XMP som pdfxid:GTS_PDFXVersion (PDF/X-1a:2003, PDF/X-3:2003 eller PDF/X-4). 2003-udgaverne identificerer delen komplet gennem den streng, så der ikke skrives nogen separat conformance-egenskab
  • Tilføjer et output intent, hvis dokumentet ikke har ét, og sætter dets subtype til /S /GTS_PDFX, den subtype, PDF/X-læsere kigger efter (ISO 32000-1 §14.11.5 definerer output intent-dictionaryen)
  • Sætter /Trapped /False i dokumentets information dictionary, fordi skriveren aldrig foretager trapping, og PDF/X kræver, at trapping-tilstanden erklæres i stedet for at stå ukendt
  • Fastlægger versionsbasen: 1.4 for X-1a og X-3, 1.6 for X-4

To flere reparationer sker ved gem. Enhver side uden en TrimBox får en svarende til sin MediaBox, da PDF/X kræver en erklæret færdig størrelse på hver side (ISO 32000-1 §14.11.2 dækker sidegrænserne), og annotationer får det printflag, standarden forventer. TrimBox-fallbacken er et sikkerhedsnet, ikke en layoutbeslutning. Et fuldkantsjob har brug for en ægte trim box inde i en større media box, og guiden til TrimBox-, BleedBox- og CropBox-standarder i PDFlibPas gennemgår, hvordan man sætter de bokse, og hvordan arv kan overraske

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]);

    // Erstat standardprofilen med den rigtige trykbetingelse
    if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
      raise Exception.Create('Could not load the CMYK output profile');

    // Embed = 1 indlejrer hele fontprogrammet; PDF/X accepterer ikke 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);           // procesrød, ingen RGB
    Pdf.DrawText(72, 720, 'Spring catalog, page 1');

    Pdf.SaveToFile(OutFile);
  finally
    Pdf.Free;
  end;
end;

Rækkefølgen i det udkast er bevidst. LoadOutputIntentProfile erstatter profilen i et output intent, der allerede findes, så det skal køre, efter SetPDFXMode har oprettet ét. Kaldet kan heller ikke trylle et intent frem ud af ingenting: på et dokument uden output intent returnerer det blot 0

Hvorfor skal standard-output intentet erstattes?

Det output intent, SetPDFXMode tilføjer for mode 1 til 3, erklærer DeviceCMYK, men ICC-bytene bag er bibliotekets medfølgende sRGB-profil med komponentantallet omskrevet til 4. Den standard findes af hensyn til bagudkompatibilitet og er nok til at gøre filen strukturelt PDF/X, men den indlejrede profil beskriver ingen trykpresse. Et preflight-værktøj, der inspicerer profil-headeren, vil se en RGB-profil med CMYK-skilt på, og en trykker, der stoler på intentet til korrektur, får volapyk

Behandl derfor LoadOutputIntentProfile(FileName, 'DeviceCMYK') som obligatorisk til rigtigt printarbejde, og giv ICC-profilen, som ens printleverandør angiver for papiret og pressen. TPDFlib.LoadOutputIntentProfile bytter DestOutputProfile-streamen og opdaterer /N og lader resten af intent-dictionaryen stå, som den er skrevet. Især output condition identifier og de beskrivende strenge i standard-intentet bærer stadig sRGB-agtige værdier, så hvis trykkeriet matcher på identifikatoren frem for på den indlejrede profil, så bekræft hos dem, hvad de tjekker, før man sender af sted. Det er en reel lak i standarderne fra 2003-æraen, ikke noget, ens kode gjorde forkert

Fælde ét: den version, man ikke selv valgte

Et nyt PDFlibPas-dokument starter på skriverens standard på PDF 1.5, som ligger over den 1.4-base, PDF/X-1a og PDF/X-3 kræver. TPDFlib.SetPDFXMode løser det stille i det almindelige tilfælde: er gem-versionen ikke låst, sænker den dokumentversionen til delens base og fortsætter. Den første implementering nægtede i stedet, og alle SetPDFXMode(1) på et friskt dokument returnerede 0, hvilket er et godt eksempel på en regel, der er korrekt på papir og ubrugelig i praksis

Nægtelsen findes stadig i det ene tilfælde, hvor den har ret. Har noget tidligere kaldt LockSaveVersion med en version over basen, returnerer SetPDFXMode 0 med LastErrorCode 601, fordi kalderen fastlåste versionen med vilje, og biblioteket vil ikke tilsidesætte det. I delt dokumentopsætningskode, hvor en helper låser versionen af urelaterede grunde, er det netop her, et X-1a-job dør:

Pdf := TPDFlib.Create;
try
  Pdf.LockSaveVersion('1.7');     // sat af en generisk "house defaults"-helper
  if Pdf.SetPDFXMode(1) = 0 then
    // LastErrorCode = 601: 1.7 er låst, PDF/X-1a kræver 1.4.
    // Spring låsen over til printjobs. PDF/X-4 er ingen redning her:
    // dens 1.6-base ligger også under 1.7-låsen og fejler på samme måde.
    LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
  Pdf.Free;
end;

Samme gate forklarer en relateret begrænsning. PDF/X-mode er en dokumentoprettelses-kontakt: SetPDFXMode virker på et nyt dokument og returnerer 0 på et, der er indlæst fra en fil, fordi biblioteket ikke kan stå inde for indhold, det ikke selv skrev. At konvertere en eksisterende PDF til PDF/X er et andet stykke arbejde, der kræver inspektion og reparation, ikke et flag

Fælde to: gates, der nægter i stilhed

Når en PDF/X-mode er aktiv, nægter PDFlibPas hvert indholdskald, den valgte del forbyder, i det øjeblik man laver det, i stedet for at lade filen strande i preflight senere. Nægtelserne dækker device RGB-fyld og -streger (hele X-1a; X-3 og X-4, medmindre output intentet er RGB), ICC-baserede farver i X-1a, transparens og blend modes i X-1a og X-3, optional content i X-1a og X-3, JavaScript-handlinger, indlejrede filvedhæftninger, LZW-billedfilteret, Standard 14-fonte, der ville forblive uindlejrede, og public key-kryptering. Adgangskodekryptering gennem Encrypt bliver ikke gatet ved oprettelsestidspunktet, men validatoren flagger enhver krypteret fil, så hold printjobs ukrypterede

Det, nægtelserne ikke deler, er en fejlkode, og det er fælden. Farvespace-gates i SetFillColor, SetLineColor, de ICC-baserede farvekald og EncryptPublicKey sætter LastErrorCode til 601, den library conformance-kode, PDF/A-gates også bruger. Transparens-, JavaScript-, vedhæftnings-, LZW- og AddStandardFont-gates returnerer blot 0 og rører ikke LastErrorCode, hvilket betyder, at den stadig kan holde en værdi fra et tidligere kald. Kode, der kun tjekker LastErrorCode efter en bunke tegnekald, overser halvdelen af nægtelserne, så tjek hver enkelt returværdi:

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 i X-1a / X-3
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
  'app.alert(1)', 0), 'JavaScript link');           // 0 i hver del

Fonte fortjener deres egen advarsel. AddStandardFont returnerer 0 under PDF/X, fordi biblioteket ikke leverer Standard 14-outlineerne og derfor ikke kan indlejre dem. AddTrueTypeFont er anderledes: i PDF/A-mode forfremmer den Embed = 0 til en fuld indlejring for en, men i PDF/X-mode gør den ikke, så en refereret systemfont kommer med i filen, og kun validatoren opdager den. Giv selv 1 (fuld) eller 2 (subset), eller kald SetEmbedAllFonts(1) én gang ved opsætning, så en løs 0 ikke kan snyde igennem

Hvordan verificerer man en PDF/X-fil, før den sendes til print?

Kør CheckFileCompliance med ComplianceTest = 3 på den gemte fil. TPDFlib.CheckFileCompliance returnerer 0, når den ikke registrerede fund, og ellers et string list-handle, hvis elementer er linjer med præfikset PDFX-. PDF/X-revisionen læser filen gennem samme streaming-læser som PDF/A- og PDF/UA-preflight i PDFlibPas, og den tjekker reglerne, som et print-preflight tjekker først:

  • Filen er ikke krypteret, XMP'en bærer en kendt GTS_PDFXVersion, og PDF-versionen overskrider ikke delens base
  • Præcis ét output intent findes, dets subtype er GTS_PDFX, og det bærer en destinationsprofil og en output condition identifier
  • Information dictionaryen erklærer /Trapped
  • Hver side har en TrimBox, der ligger inde i MediaBoxen, og en BleedBox, når den findes, omslutter TrimBoxen
  • Enhver font, der bruges på en side, har et indlejret program, hvor Type0-fonte tjekkes gennem deres efterkommende font
  • Ingen forbudte annotationstyper (RichMedia, Screen, 3D, Movie, Sound, FileAttachment), ingen JavaScript open action eller dokumentniveau-JavaScript, ingen indlejrede filer og intet LZWDecode-filter nogen steder i objektgrafen
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       // elementerne er 1-baserede
      Report.Add(Pdf.GetStringListItem(ListId, I));
  finally
    Pdf.ReleaseStringList(ListId);
  end;
end;

Vær klar over, hvad det pass betyder. PDFlibPas-validatoren reviderer de strukturelle regler, der er listet ovenfor, og det er dem, der fanger generatorfejl. Den er ikke et farveakkurat preflight: den tjekker ikke samlet farvedækning, billedopløsning eller om den indlejrede profil matcher den betingelse, ens trykkeri kører. Brug den som gaten i ens build- eller export-pipeline, og lad de pressespecifikke tjek ligge hos trykkeriets preflight-profil, hvilket de alligevel kommer til at køre

FAQ

Understøtter PDFlibPas PDF/X-4-transparens?

Ja. I SetPDFXMode(3) tillader PDFlibPas transparens, blend modes og optional content groups og skriver en PDF 1.6-fil. De samme kald returnerer 0 under PDF/X-1a og PDF/X-3

Kan PDFlibPas konvertere en eksisterende PDF til PDF/X?

Nej. SetPDFXMode gælder kun dokumenter oprettet i den aktuelle TPDFlib-session og returnerer 0 for en indlæst fil. Man kan stadig revidere enhver eksisterende PDF med CheckFileCompliance og test 3

Hvilken fejlkode returnerer et nægtet PDF/X-kald?

Farvespace- og public key-krypteringsnægtelser sætter LastErrorCode til 601. Andre gates, som transparens, JavaScript og Standard 14-fonte, returnerer kun 0, så test altid funktionsresultatet

PDF/X-output, PDF/X-validatoren og resten af print- og arkiverings-værktøjet er en del af PDFlibPas, losLabs native Object Pascal PDF-bibliotek til Delphi. Se PDFlibPas Delphi PDF Library-produktsiden for udgaver, platforme og den fulde API-reference