Technický článek

Automatizace předletových kontrol PDF v Delphi s HotPDF

Soubor se na vašem počítači čistě otevře. Acrobat jej ukáže, náhled tisku vypadá správně, každá stránka je na místě. Pak jde do tiskového studia, nebo do archivačního systému, který zpracovává vaši měsíční dávku, a vrátí se odmítnutý: RGB obrázky v zakázce CMYK, chybějící klíč /Trapped, output intent, který neodpovídá tiskárně. Na dokumentu nebylo nic špatně vidět. Byl chybný proti profilu, a profil se kontroloval někde, kde jste nebyli. Preflight je prepress název pro tuto kontrolu, a skutečná otázka zní, kam patří, když PDF vycházejí z vašeho vlastního kódu v Delphi, ne z desktopu nějakého designéra

HotPDF vám nedává funkci preflight, kterou byste zavolali. Komponenta nese okno reportu preflight ve svém GUI demu, ale za ním není žádné API, které by mohla zavolat služba nebo build skript, a předstírat opak by vás poslalo hledat metodu, která tam není. To zní jako díra, dokud si nevšimnete, že u souborů, které generujete sami, je volání validátoru na váš vlastní výstup stejně špatný tvar řešení. Už teď ovládáte každou vlastnost, kterou by validátor zkoumal. Užitečné rozdělení je udělat generátor neschopný vygenerovat vadný soubor, a pak to dokázat nástrojem, který jste nenapsali sami

Diagram preflight pipeline v Delphi, kde nastavení souladu HotPDF brání špatným PDF během generování a veraPDF plus Acrobat Preflight externě prokážou výsledek
Prevence zapeče pravidla PDF/A a PDF/X do samotné generace HotPDF, zatímco externí validátory dodávají verdikt, který si generátor nemůže ohodnotit sám

Proč vlastní výstup kontrolujete jinak

Tradiční preflight předpokládá cizí soubor. Vytvořil jej nějaký designér, nějaká jiná aplikace, nějaký neznámý řetěz úprav, a vy jej zkoumáte, protože netušíte, co je uvnitř. Dokument, který vytvořil váš kód, cizí není. Vkládání fontů, barevný prostor, output intent, blok metadat: o tom všem rozhodl váš program pár milisekund předtím, než soubor dopadl na disk. Kontrolovat jej dodatečně, abyste objevili volby, které jste právě udělali, je zbytečná práce navíc. Levnější tah je tyto volby omezit tak, aby nevyhovující soubor nikdy nevznikl a nebylo co chytat

Existuje i důvod kredibility, proč držet ověřování externí. Knihovna, která žehná vlastnímu výstupu, si opravuje vlastní zkoušku. Když zákaznický archivační systém nebo RIP tiskárny váš soubor odmítne, věta „naše komponenta říká, že je v pořádku" nemá žádnou váhu. Verdikt od veraPDF nebo Acrobatu ji má, protože druhá strana používá stejné nástroje

Udělejte ze shody nastavení, ne checklist

Preventivní vrstva je jen konfigurace. Nastavte PDFACompliance nebo PDFXCompliance před BeginDoc a HotPDF podrží odpovídající pravidla pro celý průběh generování: vkládá fonty, sleduje použití DeviceRGB a DeviceCMYK proti deklarovanému output intentu a odmítá funkce, které profil zakazuje. Rozpory vyplavou u EndDoc, kde brány shody vyhodí výjimku místo tichého odeslání něčeho, co později selže po proudu. Jakmile je soubor uložený, tytéž vlastnosti přečtou zpět to, co bylo skutečně vynuceno, což je právě ten jeden fakt, který váš protokol pipeline nejvíc potřebuje:

// Po EndDoc: zaznamená vynucené profily do metadat běhu
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

Tyto příznaky umístěte na stejný řádek protokolu jako hash vstupních dat a verzi HotPDF. V den, kdy se validátor a váš generátor o souboru neshodnou, vám tento řádek řekne, která šablona jej vytvořila a jaký build knihovny byl načtený, a hádka, která by jinak sežrala celé odpoledne, se stane jedním grepem. Output intenty, profily ICC a tagování, které za těmito příznaky stojí, jsou rozepsané v průvodci výstupem PDF/A, PDF/X a PDF/UA v HotPDF

Levná první brána pro soubory, které jste nevygenerovali sami

Ne každá pipeline je čistě generativní. Zákazníci nahrávají PDF, skenery je odkládají do složky, partneři je přikládají k e-mailu. Prohnat každý takový soubor přes plný strukturální validátor plýtvá časem fronty na souborech, které se ani neotevřou. Direct File API v HotPDF přečte dost struktury souboru, aby zodpovědělo „je tohle vůbec použitelné PDF", aniž by načítalo celý strom objektů, což z něj dělá dobré místo pro rychlé selhání:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // strukturálně nečitelné: do karantény, nevalidovat
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

O tomto API rozhodují dva fakty, jak jej obalíte. Zkratka s plochou pamětí platí jen pro nešifrovaný vstup; podejte DAOpenFileReadOnly heslo a potichu sklouzne k plnému parsování, takže soubor, o kterém víte, že je šifrovaný, by měl před tříděním projít DecryptFile do prosté pracovní kopie. A DAGetPageCount nic neznamená u handle, který se čistě neotevřel, takže kontrola handle zůstává přísná a nekladný výsledek je odmítnutí, ne důvod zkusit to znovu. Víc těchto vzorů žije v článku o Direct File API pro workflow s velkými PDF

veraPDF, spouštěný jako součást buildu

Pro cokoli, co si nárokujete jako PDF/A nebo PDF/UA, je veraPDF validátor, který se má zapojit. Běží bez GUI, přijme dávku, vygeneruje XML nebo JSON a pojmenuje každé selhání podle klauzule ISO, takže selhání pravidla proti ISO 19005-1 klauzuli 6.2.2 ukáže přímo zpět na nastavení generátoru místo toho, aby vás nechalo hádat. Řídit jej z Delphi je obyčejné řízení procesu:

function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
  Cmd: string;
  SI: TStartupInfo;
  PI: TProcessInformation;
begin
  Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
    [PdfFile, ReportFile]);
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
      CREATE_NO_WINDOW, nil, nil, SI, PI) then
    RaiseLastOSError;
  try
    WaitForSingleObject(PI.hProcess, 120000);  // omezí čekání na soubor
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

Tento timeout se vyplácí. Poškozený soubor dokáže zahnat kterýkoli parser do kouta, ze kterého se nikdy nevrátí, a neomezené čekání uvnitř workeru fronty stáhne dolů zbytek fronty s sebou. Čekání omezte, dejte timeoutu vlastní chybový kód a soubor odložte pro člověka. Když čtete výsledek, parsujte XML pro identifikátory pravidel, ne pro text čitelný pro člověka. ID pravidel přežijí upgrade validátoru; znění zpráv ne, a stabilní kód je něco, podle čeho podpůrný inženýr může prohledat staré tikety

To, jak dávku spouštíte, je stejně důležité jako to, zda každý soubor projde. Jeden proces na soubor, ne jeden na dávku, aby vás jedovatý vstup stál jen timeout toho souboru a nic víc. Omezte počet procesů validátoru na počet jader, protože sestavování reportu XML je vázané na CPU a přeplnění jen trhá. A na vstupu nastavte strop velikosti, protože dvougigabajtová naskenovaná kniha ovládne frontu bez ohledu na to, jak trpělivý parser je. Nic z toho není preflight v přísném slova smyslu. Je to rozdíl mezi bránou, která přežije objem na konci měsíce, a takovou, kterou vypnou první noc, kdy ve dvě ráno zastaví pipeline

Diagram dávkové brány Delphi, která spustí jeden proces veraPDF na PDF pod ohraničeným timeoutem, těží ID pravidel z XML místo zpráv a archivuje každou sestavu vedle jejího souboru
Vstupní hlídač zaškrtne zatížení fronty a čerstvý worker veraPDF na každý soubor zabrání otrávenému vstupu zaseknout build

PDF/X je tam, kde tohle nefunguje. veraPDF jej nevaliduje, takže funkční kontrolou zůstává Preflight v Acrobatu s profilem ISO 15930, který jmenovala vaše tiskárna. Acrobat chce člověka, což znamená vzorkování, ne úplné pokrytí: první soubor z nové šablony, plus malý náhodný výběr z každé dávky, zatímco automatizovaná brána obstará vše, co lze zvládnout bez člověka. Vzorkovaná kontrola, která skutečně běží, poráží kompletní automatizaci, která navždy zůstane napůl dodělaná

Report, který budete chtít i za rok

Brána preflight se vyplácí dvakrát. Jednou, když u dveří zastaví vadný soubor, a podruhé mnohem později, když se někdo zeptá, proč konkrétní soubor prošel. Tato druhá chvíle je ta, která by měla určovat formát, protože je to ta, kde vás tenký report nechá na holičkách. U každého kontrolovaného souboru uchovávejte hash vstupu, příznaky shody generátoru a verzi knihovny z výše uvedeného řádku protokolu, jméno a verzi validátoru, profil, proti kterému se kontrolovalo, výsledek prošel/neprošel a ID selhaných pravidel s čísly stránek, kdekoli je validátor uvádí. Ukládejte tento report vedle souboru, který popisuje. Umístěte jej do odděleného systému a tento systém bude vyřazen z provozu dřív, než archiv, který dokumentuje

Výjimky je taky potřeba zapisovat. Když zákazník trvá na odeslání souboru, který se bráně nelíbí, odpovědí není uvolnit pravidlo pro všechny. Zaznamenejte, kdo tento soubor schválil, na jakém základě a do jakého data, a pak tuto výjimku připojte k jeho reportu. Výjimka se jménem a datem vypršení je rozhodnutí, které někdo vlastní. Kontrola zakomentovaná „dočasně" je incident čekající na své datum

Ještě jeden návyk se vyplácí sám: když soubor selže, zkopírujte jej do pojmenované regresní složky dřív, než se ho kdokoli dotkne. Téměř každý problém preflight, který stojí za ladění, se dá vystopovat k jednomu konkrétnímu vstupu, a týmy, které si tyto vstupy uchovávají, opraví opakující se chybu za hodinu místo čekání, až se znovu vynoří v produkci. Vlastnosti shody a Direct File API ukázané zde jsou součástí HotPDF Delphi Component pro Delphi a C++Builder, jehož dokumentace pokrývá každé volání v plném rozsahu