Technical Article

Avtomatizacija preverjanja PDF Preflight v Delphi s HotPDF

Datoteka se na vašem računalniku odpre brez težav. Acrobat jo prikaže, predogled tiskanja je pravilen, vsaka stran je na svojem mestu. Nato pa jo pošljete v tiskarno ali v arhivski sistem, ki sprejme vašo mesečno serijo, in vrne se zavrnjena: slike RGB v opravilu CMYK, manjka ključ /Trapped ali pa barvni namen (output intent) ne ustreza tiskarskemu stroju. Z dokumentom na videz ni bilo nič narobe. Njegova struktura pa ni ustrezala profilu in ta profil je bil preverjen nekje, kjer vas ni bilo. Preflight je izraz za tovrstno preverjanje v pripravi na tisk, glavno vprašanje pa je, kam to preverjanje spada, kadar dokumenti PDF nastajajo v vaši lastni kodi Delphi in ne na oblikovalčevem namizju.

HotPDF vam ne ponuja pripravljene funkcije za preflight, ki bi jo lahko preprosto poklicali. Komponenta sicer vsebuje okno s preflight poročilom v svoji grafični predstavitvi (GUI demo), vendar v ozadju ni API-ja, ki bi ga lahko poklicala storitev ali skript za gradnjo. Iskanje takšne metode bi bilo zaman. To morda deluje kot pomanjkljivost, dokler ne ugotovite, da je klicanje preverjevalnika nad lastnimi izhodi pri datotekah, ki jih ustvarite sami, pravzaprav nesmiselno. Sami namreč nadzorujete vsako lastnost, ki bi jo preverjevalnik pregledal. Smiselna delitev je v tem, da generatorju preprečite ustvarjanje neustreznih datotek, nato pa to dokažete z neodvisnim orodjem.

Why you check your own output differently

Tradicionalni preflight predvideva datoteko neznanega izvora. Ustvaril jo je nek oblikovalec, druga aplikacija ali neznano zaporedje urejanj, vi pa jo pregledate, ker nimate predstave o njeni notranjosti. Dokument, ki ga ustvari vaša koda, pa ni neznanka. Vgradnjo pisav, barvni prostor, izhodni namen, blok metapodatkov: vse to je vaš program določil nekaj milisekund pred zapisom na disk. Naknadno preverjanje odločitev, ki ste jih pravkar sprejeli, je izguba časa. Bolj učinkovito je te odločitve omejiti že med ustvarjanjem, tako da neskladna datoteka sploh ne more nastati.

Obstaja tudi razlog verodostojnosti za zunanje preverjanje. Knjižnica, ki sama potrjuje lastne izhode, ocenjuje svoj lasten izpit. Ko arhivski sistem stranke ali tiskarski RIP zavrne vašo datoteko, trditev "naša komponenta pravi, da je vse v redu" nima nobene teže. Odločitev orodij veraPDF ali Acrobat pa jo ima, saj druga stran uporablja enaka orodja.

Naj bo skladnost nastavitev in ne kontrolni seznam

Preprečevalni sloj je zgolj stvar konfiguracije. Nastavite PDFACompliance ali PDFXCompliance pred klicem BeginDoc in HotPDF bo uveljavil ustrezna pravila za celoten postopek ustvarjanja: vgradil bo pisave, spremljal uporabo DeviceRGB in DeviceCMYK glede na deklarirani izhodni namen ter zavrnil funkcije, ki jih profil prepoveduje. Protislovja se pokažejo ob klicu EndDoc, kjer se sprožijo izjeme skladnosti, namesto da bi tiho poslali datoteko, ki bo kasneje zavrnjena. Ko je datoteka shranjena, te lastnosti izpišejo, kaj je bilo dejansko uveljavljeno, kar je ključni podatek za vaš dnevnik izvajanja:

// After EndDoc: record the enforced profiles with the run metadata
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

A cheap first gate for files you did not generate

Vsak procesni tok ni namenjen le ustvarjanju. Stranke prenašajo datoteke PDF, skenerji jih odlagajo v mape, partnerji jih priložijo e-pošti. Pošiljanje vsake od teh datotek skozi celoten strukturni preverjevalnik po nepotrebnem porablja čas čakalne vrste za datoteke, ki se sploh ne morejo odpreti. HotPDF Direct File API prebere dovolj strukture datoteke, da odgovori na vprašanje "ali je to sploh uporabna datoteka PDF", ne da bi naložil celotno drevo objektov, kar je idealno za hitro zavrnitev neustreznih datotek:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // structurally unreadable: quarantine, do not validate
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

Dve dejstvi o tem API-ju določata način njegove uporabe. Bližnjica za nizko porabo pomnilnika velja le za nešifrirane vnose; če metodi DAOpenFileReadOnly posredujete geslo, tiho preide na celotno razčlenjevanje, zato mora šifrirana datoteka pred triažo najprej skozi DecryptFile v navadno delovno kopijo. Več tovrstnih vzorcev najdete v članku o Direct File API za velike PDF delovne tokove.

veraPDF, voden kot del gradnje

Za vse datoteke, za katere trdite, da ustrezajo standardoma PDF/A ali PDF/UA, je veraPDF pravo orodje za preverjanje. Deluje brez grafičnega vmesnika (headless), sprejme serijo datotek, vrne format XML ali JSON in poimenuje vsako napako po ustreznem členu standarda ISO. Napaka po členu 6.2.2 standarda ISO 19005-1 vas tako usmeri neposredno na nastavitev generatorja, namesto da bi ugibali vzrok. Zagon iz okolja Delphi je preprost nadzor procesa:

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);  // bound the wait per file
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

Ta časovna omejitev (timeout) se hitro obrestuje. Poškodovana datoteka lahko vsak razčlenjevalnik pripelje v slepo ulico, iz katere se ne vrne, neskončno čakanje znotraj delovnega procesa čakalne vrste vačne vrste pa potegne za seboj celotno vrsto. Omejite čakanje, dodelite časovni omejitvi lastno kodo napake in datoteko odložite za ročni pregled. Pri branju rezultatov razčlenize XML po identifikatorjih pravil in ne po človeku berljivem besedilu. Identifikatorji pravil preživijo posodobitve preverjevalnikov, besedila sporočil pa ne, stabilna koda pa je nekaj, po čemer lahko inženir za podporo išče pretekle zahtevke.

Način izvajanja serije je prav tako pomemben kot uspešnost preverjanja posamezne datoteke. Uporabite en proces na datoteko in ne enega na celotno serijo, tako da vas uničen vnos stane le časovne omejitve te datoteke in nič več. Omejite število procesov preverjevalnika na število procesorskih jeder, saj je gradnja poročila XML procesorsko zahtevna in preveliko število sočasnih procesov le upočasni sistem. Določite tudi zgornjo mejo velikosti na vhodu, saj bo dvogigabajtna skenirana knjiga popolnoma obremenila čakalno vrsto ne glede na potrpežljivost razčlenjevalnika. Nič od tega ni preflight v strogem pomenu besede. To je razlika med vrati, ki preživijo obremenitve ob koncu meseca, in tistimi, ki jih izklopite že prvo noč, ko ob dveh zjutraj ustavijo celoten postopek.

Pri standardu PDF/X pa veraPDF naleti na omejitve, saj ga ne preverja. Dejanski preizkus je še vedno Acrobatov Preflight s profilom ISO 15930, ki ga določi tiskarna. Acrobat zahteva človeško posredovanje, relativno vzorčenje namesto polne pokritosti: preverjanje prve datoteke iz nove predloge in manjši naključni vzorec iz vsake serije, medtem ko samodejna vrata poskrbijo za vse ostalo, kar je mogoče preveriti brez človeka. Vzorčeno preverjanje, ki se dejansko izvaja, je boljše od popolne avtomatizacije, ki ostane napol dokončana.

Poročilo, ki ga boste želeli videti tudi čez eno leto

Vrata preflight se obrestujejo dvakrat. Prvič, ko ustavijo slabo datoteko pri vhodu, in drugič, precej kasneje, ko nekdo vpraša, zakaj je bila določena datoteka sploh prepuščena. Ta drugi trenutek je tisti, ki bi moral določati obliko poročila, saj vas skopo poročilo takrat pusti na cedilu. Za vsako preverjeno datoteko shranite zgostitev (hash) vhoda, generatorjeve zastavice skladnosti in različico knjižnice iz zgoraj opisane vrstice dnevnika, ime in različico preverjevalnika, preverjeni profil, uspeh ali neuspeh ter identifikatorje neuspelih pravil s številkami strani, če jih preverjevalnik posreduje. To poročilo shranite poleg datoteke, ki jo opisuje. Če ga shranite v ločen sistem, bo ta sistem verjetno ugasnjen še pred arhivom, ki ga dokumentira.

Tudi izjeme je treba dokumentirati. Če stranka vztraja pri pošiljanju datoteke, ki je vrata ne odobrijo, rešitev ni v tem, da sprostite pravilo za vse. Zabeležite, kdo je odobril to datoteko, iz kakšnih razlogov in do kdaj, ter to izjemo priložite njenemu poročilu. Izjema z imenom in rokom veljavnosti je odločitev, za katero nekdo odgovarja. Preverjanje, ki je "začasno" zakomentirano v kodi, pa je le incident, ki čaka na svoj trenutek.

Še ena navada se hitro povrne: ko datoteka ne uspe, jo kopirajte v imenovano mapo za regresijo, preden se je kdor koli dotakne. Skoraj vsaka preflight težava, ki jo je vredno odpravljati, se nanaša na en določen vhod. Ekipe, ki te vhode shranijo, odpravijo ponovitev v eni uri, namesto da bi čakale, da se ponovno pojavi v produkciji. Lastnosti skladnosti in Direct File API, prikazani tukaj, so del komponente HotPDF za Delphi in C++Builder, katere dokumentacija v celoti pokriva vsak klic.