Technický článek

Výstup PDF/X-1a, X-3 a X-4 v Delphi s PDFlibPas

PDFlibPas vyrábí tiskové soubory PDF/X z Delphi jediným voláním TPDFlib.SetPDFXMode, které vybere PDF/X-1a:2003, PDF/X-3:2003 nebo PDF/X-4. Volání zapíše identifikaci XMP pdfxid, přidá výstupní záměr GTS_PDFX, deklaruje /Trapped /False, fixuje verzi PDF a od té chvíle odmítá kreslicí volání, kterým vybraný díl zakazuje. CheckFileCompliance s testem 3 zkontroluje uložený soubor

To je krátká odpověď. Ta delší má význam, protože tiskárny neodmítají soubory za to, že jsou ošklivé, odmítají je v preflightu, obvykle v pátek, obvykle kvůli něčemu, co generující kód mohl zachytit: RGB logo v zakázce X-1a, font odkázaný místo vloženého, stránka bez formátu ořezu. PDF/X existuje proto, aby z nich udělalo chyby strukturální, nikoli vizuální, a na straně knihovny jde o to, abyste je vůbec nevytvořili

Který díl PDF/X si má aplikace v Delphi vybrat?

Vyberte PDF/X-4, pokud příjemce říká něco jiného, a PDF/X-1a, když to říká. Tři díly se liší přesně ve třech osách — barva, průhlednost a základní verze PDF — a každá osa se promítne do jedné brány uvnitř PDFlibPas:

  • PDF/X-1a:2003 (ISO 15930-4): syntaxe PDF 1.4, pouze barvy CMYK, šedá a spot, žádné barvy založené na ICC, žádná průhlednost. Soubor je už rozseknutý do tiskových barev, takže tiskárna neprovádí žádnou konverzi barev
  • PDF/X-3:2003 (ISO 15930-6): stále PDF 1.4 a stále bez průhlednosti, ale barevně řízené prostory jsou povolené a device RGB se přijímá, když výstupní záměr sám popisuje RGB podmínku
  • PDF/X-4 (ISO 15930-7): syntaxe PDF 1.6 s živou průhledností a volitelným obsahem (vrstvami). To je díl, pro který jsou moderní RIPy stavěné, protože zplošťování průhlednosti na výstupu bylo zdrojem velké části staré bolesti X-1a

X-1a je stále všude v reklamní produkci a ve starších tiskových tocích, které chtějí soubor, jenž vyexponují bez přemýšlení. X-4 je lepší inženýrská volba, kdykoli to tiskárna přijme: průhlednost zůstane nedotčená a barevná rozhodnutí se dějí na RIPu s reálným tiskovým profilem, ne ve vašem kódu. Čísla módů jsou 1, 2 a 3, a GetInformation(203) ohlásí aktivní mód zpět. PDFlibPas navíc definuje vyšší čísla módů pro rodinu PDF/X-6 na PDF 2.0, ta ale následují jiná pravidla pro verzi a výstupní záměr a tady jsou mimo rozsah

Co SetPDFXMode zapíše do souboru

TPDFlib.SetPDFXMode obstará identifikaci a pevnou kostru, kterou čtečka PDF/X kontroluje jako první, takže aplikační kód musí dodat jen reálný tiskový profil a obsah. Konkrétně úspěšné volání udělá čtyři věci:

  • Zapíše řetězec edice do XMP jako pdfxid:GTS_PDFXVersion (PDF/X-1a:2003, PDF/X-3:2003 nebo PDF/X-4). Edice 2003 identifikují díl tímto řetězcem úplně, takže se žádná samostatná vlastnost vyhovování nezapisuje
  • Přidá výstupní záměr, pokud ho dokument nemá, a nastaví jeho podtyp na /S /GTS_PDFX, podtyp, který čtečky PDF/X hledají (slovník výstupního záměru definuje ISO 32000-1 §14.11.5)
  • Nastaví /Trapped /False v informačním slovníku dokumentu, protože zapisovač nikdy netrapuje a PDF/X vyžaduje, aby stav trappingu byl deklarovaný, nikoli neznámý
  • Fixuje základnu verze: 1.4 pro X-1a a X-3, 1.6 pro X-4

Dvě další opravy se dějí při ukládání. Každá stránka bez TrimBox dostane jeden rovný svému MediaBox, protože PDF/X vyžaduje deklarovaný hotový formát na každé stránce (ISO 32000-1 §14.11.2 pokrývá hranice stránek), a anotace dostanou příznak tisku, který norma očekává. Fallback na TrimBox je pojistka, ne layoutové rozhodnutí. Zakázka do ořezu potřebuje skutečný trim box uvnitř většího media boxu a průvodce výchozími hodnotami TrimBox, BleedBox a CropBox v PDFlibPas rozebírá, jak tyto boxy nastavit a jak vás dědičnost může překvapit

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

    // Nahraďte výchozí profil skutečnou tiskovou podmínkou
    if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
      raise Exception.Create('Could not load the CMYK output profile');

    // Embed = 1 vloží celý fontový program; PDF/X nepřijme 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);           // procesová červeň, žádné RGB
    Pdf.DrawText(72, 720, 'Spring catalog, page 1');

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

Pořadí v té skice je záměrné. LoadOutputIntentProfile nahrazuje profil výstupního záměru, který už existuje, takže musí běžet poté, co SetPDFXMode jeden vytvořil. Volání také neumí záměr vykouzlit z ničeho: na dokumentu bez výstupního záměru prostě vrátí 0

Proč je třeba výchozí výstupní záměr nahradit?

Výstupní záměr, který SetPDFXMode přidává pro módy 1 až 3, deklaruje DeviceCMYK, ale ICC bajty za ním jsou přibalený sRGB profil knihovny s přepsaným počtem komponent na 4. Ta výchozí hodnota tu je kvůli zpětné kompatibilitě a stačí, aby byl soubor strukturálně PDF/X, jenže vložený profil nepopisuje žádný tisk. Preflight nástroj, který koukne do hlavičky profilu, uvidí RGB profil v ceduli CMYK a tiskař, který se na záměr spoléhá při proofu, dostane nesmysl

Takže LoadOutputIntentProfile(FileName, 'DeviceCMYK') berte jako povinnost pro skutečnou tiskovou práci a předejte ICC profil, který váš tiskový dodavatel jmenuje pro papír a stroj. TPDFlib.LoadOutputIntentProfile vymění proud DestOutputProfile a aktualizuje /N, zbytek slovníku záměru nechá tak, jak byl zapsán. Konkrétně identifikátor výstupní podmínky a popisné řetězce výchozího záměru stále nesou hodnoty ve stylu sRGB, takže pokud vaše tiskárna páruje podle identifikátoru a ne podle vloženého profilu, ověřte si před odesláním, co kontrolují. To je skutečná mezera ve výchozích hodnotách z doby 2003, ne chyba vašeho kódu

Past číslo jedna: verze, kterou jste nevybrali

Nový dokument PDFlibPas startuje na zapisovačově výchozí verzi PDF 1.5, což je nad základnou 1.4, kterou PDF/X-1a a PDF/X-3 vyžadují. TPDFlib.SetPDFXMode to v běžném případě řeší potichu: pokud ukládací verze není uzamčená, sníží verzi dokumentu na základnu dílu a pokračuje. První implementace místo toho odmítala a každé SetPDFXMode(1) na čerstvém dokumentu vracelo 0 — dobrá ukázka pravidla, které je na papíře správné a v praxi k ničemu

Odmítnutí pořád existuje pro ten jediný případ, kdy je na místě. Pokud něco dřívějšího zavolalo LockSaveVersion s verzí nad základnou, vrátí SetPDFXMode 0 s LastErrorCode 601, protože volající verzi uzamkl záměrně a knihovna to nepřebije. Ve sdíleném kódu nastavení dokumentu, kde pomocník zamyká verzi z nesouvisejících důvodů, přesně tady zakázka X-1a umírá:

Pdf := TPDFlib.Create;
try
  Pdf.LockSaveVersion('1.7');     // nastavil generický pomocník s firemními výchozími hodnotami
  if Pdf.SetPDFXMode(1) = 0 then
    // LastErrorCode = 601: 1.7 je uzamčeno, PDF/X-1a potřebuje 1.4.
    // U tiskových zakázek zámek přeskočte. PDF/X-4 tady není útočiště:
    // jeho základna 1.6 je také pod zámkem 1.7 a selhává stejně.
    LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
  Pdf.Free;
end;

Táž brána vysvětluje související omezení. Mód PDF/X je přepínač při tvorbě dokumentu: SetPDFXMode funguje na novém dokumentu a na dokumentu načteném ze souboru vrací 0, protože knihovna nemůže ručit za obsah, který nenapsala. Převod existujícího PDF na PDF/X je jiná práce, která potřebuje prohlídku a opravy, ne příznak

Past číslo dvě: brány, které odmítají potichu

Jakmile je mód PDF/X aktivní, PDFlibPas odmítá každé obsahové volání, kterému vybraný díl zakazuje, v okamžiku volání, místo aby nechal soubor později propadnout preflightem. Odmítnutí pokrývají plnění a tahy device RGB (celé X-1a; u X-3 a X-4, pokud výstupní záměr není RGB), ICC barvy v X-1a, průhlednost a blend módy v X-1a a X-3, volitelný obsah v X-1a a X-3, JavaScriptové akce, vložené přílohy, obrazový filtr LZW, fonty Standard 14, které by zůstaly nevložené, a šifrování veřejným klíčem. Heslové šifrování přes Encrypt se při tvorbě neguje, ale validátor označí jakýkoli zašifrovaný soubor, takže tiskové zakázky nechávejte nešifrované

Co odmítnutí nesdílejí, je chybový kód — a v tom je past. Brány barevných prostorů v SetFillColor, SetLineColor, u volání ICC barev a EncryptPublicKey nastaví LastErrorCode na 601, knihovní kód vyhovování, který používají i brány PDF/A. Brány průhlednosti, JavaScriptu, příloh, LZW a AddStandardFont jen vrátí 0 a LastErrorCode nechají netknuté, což znamená, že může stále držet hodnotu z dřívějšího volání. Kód, který po dávce kreslicích volání kontroluje jen LastErrorCode, přehlédne polovinu odmítnutí, takže kontrolujte každou návratovou hodnotu:

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 v X-1a / X-3
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
  'app.alert(1)', 0), 'JavaScript link');           // 0 v každém dílu

Fonty si zaslouží vlastní varování. AddStandardFont vrací 0 pod PDF/X, protože knihovna obrysy Standard 14 nedodává a nemůže je vložit. AddTrueTypeFont je jiný případ: v módu PDF/A povýší Embed = 0 na plné vložení za vás, ale v módu PDF/X ne, takže odkázaný systémový font se dostane do souboru a zachytí ho jen validátor. Předejte 1 (plně) nebo 2 (podmnožinu) sami, nebo zavolejte jednou při nastavení SetEmbedAllFonts(1), aby se náhodná 0 neprošla

Jak ověřit soubor PDF/X, než jde do tisku?

Spusťte CheckFileCompliance s ComplianceTest = 3 na uloženém souboru. TPDFlib.CheckFileCompliance vrací 0, když nezaznamenal žádné nálezy, jinak handle seznamu řetězců, jehož položky jsou řádky s prefixem PDFX-. Audit PDF/X čte soubor stejným streamovacím čtečkem jako preflight PDF/A a PDF/UA v PDFlibPas a kontroluje pravidla, která tiskový preflight kontroluje jako první:

  • Soubor není zašifrovaný, XMP nese známé GTS_PDFXVersion a verze PDF nepřekračuje základnu dílu
  • Existuje právě jeden výstupní záměr, jeho podtyp je GTS_PDFX a nese cílový profil a identifikátor výstupní podmínky
  • Informační slovník deklaruje /Trapped
  • Každá stránka má TrimBox ležící uvnitř MediaBox a BleedBox, je-li přítomen, obklopuje TrimBox
  • Každý font použitý na stránce má vložený program, přičemž fonty Type0 se kontrolují přes jejich potomka
  • Žádné zakázané typy anotací (RichMedia, Screen, 3D, Movie, Sound, FileAttachment), žádná otevírací akce JavaScript ani JavaScript na úrovni dokumentu, žádné vložené soubory a žádný filtr LZWDecode kdekoli v objektovém grafu
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       // položky jsou od 1
      Report.Add(Pdf.GetStringListItem(ListId, I));
  finally
    Pdf.ReleaseStringList(ListId);
  end;
end;

Mějte jasno v tom, co ten průchod znamená. Validátor PDFlibPas audituje strukturální pravidla uvedená výše a právě ta odchytají chyby generátoru. Není to barevně věrný preflight: nekontroluje celkové pokrytí barvou, rozlišení obrazů ani to, zda vložený profil odpovídá podmínce, kterou vaše tiskárna jede. Použijte ho jako bránu ve svém buildu nebo exportní pipeline a strojově specifické kontroly nechte na preflight profilu tiskárny — ten si stejně pustí

FAQ

Podporuje PDFlibPas průhlednost PDF/X-4?

Ano. V SetPDFXMode(3) dovoluje PDFlibPas průhlednost, blend módy a skupiny volitelného obsahu a zapíše soubor PDF 1.6. Táž sada volání vrací 0 pod PDF/X-1a a PDF/X-3

Umí PDFlibPas převést existující PDF na PDF/X?

Ne. SetPDFXMode se vztahuje jen na dokumenty vytvořené v aktuální relaci TPDFlib a pro načtený soubor vrací 0. Jakékoli existující PDF ale můžete auditovat pomocí CheckFileCompliance a testu 3

Jaký chybový kód vrátí odmítnuté volání PDF/X?

Odmítnutí barevných prostorů a šifrování veřejným klíčem nastaví LastErrorCode na 601. Ostatní brány, jako průhlednost, JavaScript a fonty Standard 14, jen vrátí 0, takže vždy testujte výsledek funkce

Výstup PDF/X, validátor PDF/X a zbytek tiskového a archivačního nářadí jsou součástí PDFlibPas, nativní Object Pascal PDF knihovny losLab pro Delphi. Na produktové stránce PDFlibPas Delphi PDF Library najdete edice, platformy a kompletní referenci API