Technical Article

Automatizacija PDF preflight provera u Delphi-ju uz HotPDF

Fajl se čisto otvara na vašem računaru. Acrobat ga prikazuje, pregled pre štampe izgleda ispravno, svaka stranica je tu. Zatim odlazi u štamparski biro ili u arhivski sistem koji prihvata vašu mesečnu seriju, i vraća se odbijen: RGB slike u CMYK poslu, nedostatak /Trapped ključa, profil namene (output intent) koji ne odgovara štamparskoj mašini. Niko nije mogao da vidi bilo kakav problem na samom dokumentu. Problem je bio u neslaganju sa profilom, a taj profil je proveravan negde gde vi niste imali uvid. Preflight je naziv za tu proveru u grafičkoj pripremi, a pravo pitanje je gde joj je mesto kada PDF dokumenti dolaze iz vašeg Delphi koda, a ne sa dizajnerskog stola.

HotPDF vam ne daje preflight funkciju koju možete direktno pozvati. Komponenta sadrži prozor za preflight izveštaje u svom GUI demo primeru, ali iza toga ne postoji API koji servis ili skripta za izgradnju mogu pokrenuti, pa bi vas traženje takve metode odvelo na pogrešan trag. To zvuči kao nedostatak sve dok ne shvatite da je za fajlove koje sami generišete pozivanje validatora nad sopstvenim izlazom ionako pogrešan pristup. Vi već kontrolišete svako svojstvo koje bi validator pregledao. Koristan korak je učiniti generator nesposobnim da emituje neispravan fajl, a zatim to dokazati alatom koji niste vi napisali.

Zašto sopstveni izlaz proveravate drugačije

Tradicionalni preflight pretpostavlja tuđi fajl. Kreirao ga je neki dizajner, neka druga aplikacija ili nepoznat lanac izmena, i vi ga proveravate jer nemate pojma šta je unutra. Dokument koji je generisao vaš kod nije tuđ. Ugradnja fonta, prostor boja, profil namene, blok metapodataka: vaš program je odlučio o svemu tome nekoliko milisekundi pre nego što je fajl upisan na disk. Proveravati ga nakon toga da biste otkrili odluke koje ste sami doneli je uzaludan posao. Mnogo je lakše ograničiti te izbore tako da neusaglašeni fajl nikada i ne nastane.

Postoji i razlog kredibiliteta da se verifikacija prepusti spoljnim alatima. Biblioteka koja sama potvrđuje ispravnost svog izlaza je kao učenik koji sam ocenjuje svoj kontrolni zadatak. Kada klijentov arhivski sistem ili rasterizator (RIP) u štampariji odbiju vaš fajl, izjava „naša komponenta kaže da je sve u redu” nema nikakvu težinu. Presuda iz veraPDF-a ili Acrobat-a ima, jer druga strana koristi te iste alate.

Učinite usaglašenost podešavanjem, a ne listom za proveru

Sloj prevencije je samo stvar konfiguracije. Podesite PDFACompliance ili PDFXCompliance pre poziva BeginDoc i HotPDF će primenjivati odgovarajuća pravila tokom celog procesa generisanja: ugrađivaće fontove, pratiti upotrebu DeviceRGB i DeviceCMYK modela u odnosu na deklarisanu nameru i odbijati funkcije koje profil zabranjuje. Kontradiktornosti se pojavljuju kod EndDoc, gde se sigurnosne provere aktiviraju i prijavljuju greške, umesto da tiho isporuče nešto što će kasnije pasti. Kada se fajl sačuva, ista svojstva pokazuju šta je zaista primenjeno, što je podatak koji je najpotrebniji dnevniku rada vašeg sistema:

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

Upišite ove flegove u isti red dnevnika (log line) u kom se nalaze heš ulaznih podataka i verzija HotPDF-a. Onog dana kada se validator i vaš generator ne slože oko fajla, taj red će vam reći koji šablon je proizveo dokument i koja je verzija biblioteke bila učitana, pa se rasprava koja bi inače trajala celo popodne rešava običnom grep pretragom. Profili namene, ICC profili i tagovanje koji stoje iza ovih flegova detaljno su objašnjeni u vodiču za PDF/A, PDF/X i PDF/UA izvoz uz HotPDF.

Jeftin prvi filter za fajlove koje niste vi generisali

Nije svaki tok obrade isključivo generativan. Korisnici otpremaju PDF-ove, skeneri ih ubacuju u fascikle, partneri ih šalju u prilogu e-pošte. Propuštanje svakog od tih fajlova kroz kompletan strukturni validator troši vreme u redu za čekanje na fajlove koji se uopšte neće ni otvoriti. HotPDF-ov Direct File API čita dovoljno strukture fajla da odgovori na pitanje „da li je ovo uopšte upotrebljiv PDF” bez učitavanja celog stabla objekata, što ga čini odličnim mestom za brzu eliminaciju neispravnih fajlova:

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 činjenice o ovom API-ju određuju kako ćete ga koristiti. Prečica sa ravnom potrošnjom memorije važi samo za nešifrovani ulaz; ako prosledite lozinku funkciji DAOpenFileReadOnly, ona će tiho preći na kompletno parsiranje, pa bi fajl za koji znate da je šifrovan trebalo pre trijaže dešifrovati pomoću DecryptFile u privremenu čistu kopiju. Takođe, DAGetPageCount ne znači ništa ako rukovalac nije uspešno otvoren, tako da provera rukovaoca ostaje stroga, a nepozitivan rezultat znači odbacivanje, a ne ponovni pokušaj. Više ovakvih primera možete pronaći u članku o Direct File API-ju za tokove obrade velikih PDF-ova.

veraPDF, pokrenut kao deo procesa izgradnje

Za sve što deklarišete kao PDF/A ili PDF/UA, veraPDF je validator koji treba ugraditi. On radi u pozadini bez korisničkog interfejsa (headless), prima seriju fajlova, generiše XML ili JSON i imenuje svaki neuspeh prema ISO klauzuli, tako da pad pravila u odnosu na ISO 19005-1 klauzulu 6.2.2 upućuje direktno na podešavanje generatora, umesto da vas ostavi da nagađate. Pokretanje ovog procesa iz Delphi-ja je obična kontrola sistemskih 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;

Taj tajmaut (timeout) opravdava svoje postojanje. Neispravan fajl može naterati bilo koji parser u petlju iz koje nikada neće izaći, a neograničeno čekanje unutar radnog procesa povlači ceo red za obradu sa sobom. Ograničite vreme čekanja, dodelite tajmautu sopstveni kod greške i ostavite fajl po strani za ručnu proveru. Kada čitate rezultat, parsirajte XML tražeći identifikatore pravila, a ne tekst čitljiv ljudima. ID brojevi pravila ostaju isti i nakon ažuriranja validatora, dok se formulacija poruka menja, a stabilan kod je nešto po čemu inženjer podrške može lako pretraživati stare tikete.

Način na koji pokrećete seriju važan je koliko i to da li svaki pojedinačni fajl prolazi proveru. Pokrećite jedan proces po fajlu, a ne jedan po seriji, kako bi vas problematičan ulaz koštao samo tajmauta tog fajla i ničeg više. Ograničite broj procesa validatora na broj procesorskih jezgara, jer je pravljenje XML izveštaja procesorski zahtevno i preveliki broj paralelnih procesa samo usporava rad. Takođe, postavite gornju granicu veličine na samom ulazu, jer će skenirana knjiga od dva gigabajta preuzeti ceo red za čekanje bez obzira na to koliko je parser strpljiv. Ništa od toga nije preflight u strogom smislu. To je razlika između filtera koji uspešno preživljava opterećenja na kraju meseca i onog koji biva isključen prve noći kada zaustavi obradu u 2 sata ujutru.

PDF/X je mesto gde ova automatizacija nailazi na prepreku. veraPDF ne verifikuje ovaj standard, tako da je praktična provera i dalje Acrobat Preflight sa ISO 15930 profilom koji je odredila vaša štamparija. Acrobat zahteva ljudsku interakciju, što podrazumeva uzorkovanje umesto pune pokrivenosti: proveru prvog fajla sa novog šablona i mali slučajni uzorak iz svake serije, dok automatski filter rešava sve što se može rešiti bez čoveka. Provera na bazi uzorka koja se zaista sprovodi bolja je od potpune automatizacije koja zauvek ostane napola završena.

Izveštaj koji ćete želeti da imate i nakon godinu dana

Preflight filter se isplati dvostruko. Prvi put kada zaustavi neispravan fajl na samom ulazu, a drugi put mnogo kasnije, kada neko pita zašto je određeni fajl uopšte pušten. Taj drugi trenutak je onaj koji bi trebalo da diktira format izveštavanja, jer vas u tom momentu štur izveštaj ostavlja bez odgovora. Za svaki provereni fajl sačuvajte heš ulaza, generatorove flegove usaglašenosti i verziju biblioteke iz gornjeg dnevnika, naziv i verziju validatora, profil prema kome je proveren, ishod (prolaz/pad) i ID brojeve neuspešnih pravila sa brojevima stranica kad god ih validator pruža. Čuvajte taj izveštaj pored fajla koji opisuje. Stavite ga u zaseban sistem i taj sistem će verovatno biti ugašen pre nego što se ugasi arhiva koju on dokumentuje.

Izuzeci takođe moraju biti evidentirani. Kada klijent insistira na slanju fajla koji filter odbacuje, rešenje nije ublažavanje pravila za sve. Zabeležite ko je odobrio taj fajl, na kom osnovu i do kog datuma, a zatim priložite to izuzeće (waiver) njegovom izveštaju. Izuzeće sa imenom i rokom važenja je odluka iza koje neko stoji. Provera koja je privremeno isključena komentarom u kodu je incident koji samo čeka svoj trenutak.

Još jedna navika se isplati sama od sebe: kada fajl padne na proveri, kopirajte ga u imenovani regresioni direktorijum pre nego što ga bilo ko izmeni. Skoro svaki preflight problem vredan otklanjanja grešaka vodi poreklo od jednog specifičnog ulaza, a timovi koji čuvaju te ulaze rešavaju problem za sat vremena, umesto da čekaju da se on ponovo pojavi u produkciji. Svojstva usaglašenosti i Direct File API prikazani ovde deo su HotPDF komponente za Delphi i C++Builder, čija dokumentacija detaljno pokriva svaki poziv.