Technical Article

Grupni PDF preflight izveštaji u Delphi-ju pomoću PDFium Component CLI

Alat za grupni preflight je konzolni program bez prozora, usmeren na fasciklu sa PDF-ovima, koji proverava svaki od njih u odnosu na standarde usklađenosti koje navedete i ostavlja za sobom mašinski čitljiv dokaz o tome šta je pronašao. Niko ne sedi i ne gleda ga. Pokreće se u dva ujutru pod cron-om ili Windows Task Scheduler-om, ili kao kapija u CI cevovodu, a sledeća osoba kojoj je stalo do njegovog izlaza je ili planer koji čita izlazni kod ili revizor koji otvara izveštaj nedeljama kasnije. To menja značenje reči „ispravno“. Preflight motor PDFium komponente, PDF biblioteke sa izvornim kodom za Delphi, C++Builder i Lazarus, čini same pozive validacije gotovo trivijalnim. Rad koji odlučuje da li alat opravdava svoje postojanje nalazi se oko tih poziva: koji ste profil proverili, šta je izlazni kod rekao planeru i da li izveštaj koji bi uhvatio grešku još uvek postoji kada ga neko potraži.

Ugovor: šta planer zapravo može da vidi

CI pokretač ili Windows Task Scheduler vide tačno dve stvari iz vašeg alata: izlazni kod (exit code) i datoteke koje je ostavio za sobom. Linije dnevnika, boje konzole, prikaz napretka: sve je to za čoveka koji gleda uživo, a u dva ujutru to niko ne radi. Zato definišite rečnik izlaznih kodova pre nego što dodirnete API i držite se standardnih rešenja:

  • 0: svaka datoteka je usklađena sa svakim zahtevanim profilom
  • 1: najmanje jedna datoteka je proizvela nalaze validacije
  • 2: sam alat nije uspeo na najmanje jednoj datoteci (oštećen unos, zaključavanje, rušenje)

Razlika između kodova 1 i 2 je ona koju timovi preskaču i kasnije zažale. Oštećeni PDF koji se ne može otvoriti nije neuspeh validacije. Spojite to u kod 1 i brdo oštećenih skeniranja će se na vašim kontrolnim tablama prikazati kao iznenadni kolaps usklađenosti, šaljući nekoga da traži regresiju standarda koja se nikada nije dogodila, dok je stvarna priča pokvareni skener na početku lanca.

Još dve stavke pripadaju ugovoru. Prva je vremensko ograničenje (timeout) po datoteci. Patološki PDF, hiljade stranica sa duboko ugniježđenim strukturama objekata, može zadržati jedan prolaz validacije minutima, a noćni prozor za to nema strpljenja. Prekinite posao te datoteke na roku, računajte to kao neuspeh alata i nastavite sa grupom. Druga je karantinski direktorijum: pomerite svaki istekli ili neotvorivi unos u stranu umesto da ga ostavite na mestu. Tokom nekoliko meseci taj direktorijum tiho akumulira najgore dokumente koje vaši stvarni kupci šalju, a taj korpus vredi više za testiranje izdanja od bilo kog sintetičkog uzorka koji biste mogli ručno da napišete.

Izbor standarda i zašto je nivo usklađenosti važan

Nabrajanje TPdfPreflightStandard pokriva porodice koje se javljaju u praksi: ppsPdfA za ISO 19005 arhivsku usklađenost, ppsPdfUa za ISO 14289 pristupačnost, ppsPdfX za grafičku pripremu, plus ppsPdfE, ppsPdfR i ppsPdfVT za inženjering, raster i varijabilne podatke. Unutar porodice, motor čita nivo usklađenosti koji dokument tvrdi i prijavljuje ga po standardu u ConformanceName rezultata. Imenovanje porodice je retko dovoljno, jer u nivou leži stvarna razlika. PDF/A-2b obećava vizuelnu reproduktivnost i ništa više. PDF/A-3a dodaje zahtev za logičkim označavanjem strukture (tagging) i dozvoljava ugrađene izvorne datoteke, što je daleko teža prepreka za skenirani materijal koji uopšte nema stablo oznaka. Pogrešite ovo u bilo kom smeru i grupa će vas lagati. Ako vaša politika zadržavanja zapravo želi PDF/A-2b, ali vi odbacujete datoteke zbog nedostatka oznaka strukture, izveštaj će se napuniti nalazima koje niko nikada neće popraviti. Prihvatite bilo koju PDF/A oznaku bez provere nivoa i saglasićete se sa dokumentima koji ispunjavaju slabije zahteve od onih koje ste obećali. Mandati pristupačnosti od strane vladinih kupaca sve više dodaju PDF/UA na sve ovo, što ne dodaje nikakve troškove pokretanju jer BuildPdfPreflightReport (iz jedinice FPdfPreflightReport) prihvata skup standarda:

Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);

Jedan poziv procenjuje oba standarda i vraća jedan konsolidovani zapis izveštaja.

Zašto prazna lista nalaza nije uspeh

Izveštaj nabraja nalaze po standardu, a prazna lista problema znači samo „nisu pronađeni problemi u standardima koji su zapravo pokrenuti“. To je uža tvrdnja od „datoteka je usklađena sa standardom do kojeg vam je stalo“, i jaz između ta dva je mesto gde grupni preflight tiho propada. Greška u konfiguraciji koja izbacuje ppsPdfA iz skupa proizvodi potpuno istu praznu listu problema kao i zaista čista datoteka. Zato tretirajte tišinu kao sumnjivu. Prođite kroz Report.Results i potvrdite dve stvari za svaki standard koji ste nameravali da proverite: da unos rezultata za njega uopšte postoji i da je njegova zastavica IsCompliant, odgovarajuća sa Status = pfsPass, istinita. Noćni posao koji izjednačava „bez nalaza“ sa „spremno za arhivu“ bez potvrde koji su standardi ocenjeni jeste klasičan način da folder neusaglašenih datoteka prolazi mesecima, sve dok spoljni revizor ne otvori jednu pomoću veraPDF-a i cela arhiva ne dođe u pitanje.

Druga zamka se krije u tome šta je nalaz uopšte. Svaki TPdfPreflightIssue nosi Code, Category, Description i Recommendation, i imenuje pravilo koje je prekršeno, a ne stranicu ili objekat. To je izbor dizajna sa posledicama po petlju povratnih informacija. Izveštaj govori timu koji proizvodi datoteke koja klasa nedostatka postoji, neuvoženi font ili nedostajući XMP identifikator, a pronalaženje specifičnog problematičnog objekta je posao alata za remedijaciju nizvodno, a ne validatora. Izgradite svoje potrošače izveštaja na stabilnim vrednostima Code, nikada na ljudski čitljivom tekstu opisa, koji se može preformulisati između izdanja bez upozorenja.

Datoteke izveštaja za mašine i za osobu na dežurstvu

Zapis izveštaja piše iste nalaze u pet formata: SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile i SaveMarkdownToFile, svaki sa odgovarajućom funkcijom tipa ToJson kada želite string u memoriji umesto na disku. Oduprite se želji da izaberete samo jedan. Pišite JSON za cevovod, tako da ga CI može priložiti zapisu posla i analizirati kodove problema i statuse po standardu bez struganja teksta. Pišite HTML za osobu koja biva pozvana, jer se otvara u bilo kom čitaču bez ikakvih alata. Ova dva zajedno koštaju jednu dodatnu liniju po datoteci i štede vašem dežurnom inženjeru najgori zadatak u grupnoj obradi, a to je dešifrovanje sirovog JSON bloba u dva ujutru da bi saznao koja je datoteka pukla. Jedna disciplina je važnija od izbora formata: izvedite svako ime izveštaja iz imena ulazne datoteke, nikada iz vremenskog žiga, inače će dva paralelna pokretanja ispreplesti izveštaje koje više ne možete povezati sa njihovim ulazima.

Pragovi ozbiljnosti pripadaju konfiguraciji, a ne kodu. Anotacija bez alternativnog opisa je težak neuspeh za PDF/UA portal i zanemarljiva zabeleška za internu arhivu, a ipak je to identičan nalaz u oba slučaja. Izložite nivo neuspeha po profilu tako da se politika može promeniti bez ponovnog kompajliranja, i utisnite nivo koji je bio na snazi u sam rezime posla. Sledećeg kvartala niko se neće sećati pod kojim je pragom radio prošlogodišnji posao, a rezime je jedino mesto gde to sećanje preživljava.

Izolovanje datoteka tako da jedan loš PDF ne može potopiti grupu

procedure RunPreflightBatch(const InputDir, ReportDir: string;
  out FilesWithFindings, ToolFailures: Integer);
var
  SR: TSearchRec;
  Pdf: TPdf;
  Report: TPdfPreflightReport;
begin
  FilesWithFindings := 0;
  ToolFailures := 0;
  if FindFirst(InputDir + '*.pdf', faAnyFile, SR) = 0 then
  try
    repeat
      Pdf := TPdf.Create(nil);   // fresh instance per file: no state bleed
      try
        try
          Pdf.FileName := InputDir + SR.Name;
          Pdf.Active := True;
          if not Pdf.Active then  // load failures are silent, not raised
            raise EPdfError.Create('Cannot open ' + SR.Name);
          Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
          Report.SaveJsonToFile(ReportDir + ChangeFileExt(SR.Name, '.json'));
          Report.SaveHtmlToFile(ReportDir + ChangeFileExt(SR.Name, '.html'));
          if Report.TotalIssueCount > 0 then
            Inc(FilesWithFindings);
        except
          on E: Exception do
          begin
            Inc(ToolFailures);   // exit-code-2 territory, not a validation verdict
            WriteLn(ErrOutput, SR.Name + ': ' + E.Message);
          end;
        end;
      finally
        Pdf.Free;
      end;
    until FindNext(SR) <> 0;
  finally
    FindClose(SR);
  end;
end;

Tri namerna izbora žive u toj petlji. Svež TPdf po datoteci garantuje da jedan dokument koji korumpira stanje motora ne može otrovati datoteke koje ga prate. Eksplicitna provera Active nalazi svoje mesto jer Active := True guta greške učitavanja umesto da ih podiže; uklonite zaštitu i skraćena datoteka pluta dalje u poziv validacije pre nego što padne negde nizvodno sa obmanjujućom porukom. Unutrašnji try..except živi unutar opsega po datoteci sa namerom, tako da pojedinačni izuzetak povećava brojač neuspeha i petlja se nastavlja. Želite čiste izveštaje za 4.999 dobrih datoteka čak i kada je datoteka 5.000 uništena. I oba formata izveštaja se upisuju na disk pre nego što se donese presuda, što znači da dokazi preživljavaju čak i ako algoritam kasnije u logici rezimea pogrešno izbroji.

Mapiranje izlaznog koda se zatim svodi na nekoliko linija u datoteci projekta:

begin
  RunPreflightBatch(ParamStr(1), ParamStr(2), Findings, Failures);
  if Failures > 0 then
    Halt(2)
  else if Findings > 0 then
    Halt(1);
  // falling through exits with 0: every file conformed
end.

Šta preflight neće učiniti za vas

Motor otkriva; on ne popravlja. Nalaz o neuvoženom fontu ili prostoru boja zavisnom od uređaja jeste radni nalog za onoga ko proizvodi datoteke, a validator nema načina da to popravi na licu mesta. Zato planirajte petlju povratnih informacija namerno. Izveštaji moraju da slete tamo gde ih tim koji proizvodi zapravo čita, inače se isti nalazi ponovo pojavljuju svake noći dok neko konačno ne pita zašto se stopa usaglašenosti nikada ne poboljšava. Takođe se isplati unakrsno proveriti uzorak presuda u odnosu na nezavisni validator, veraPDF za PDF/A ili Acrobat-ov preflight za PDF/X, pre nego što ih spoljni revizor unakrsno proveri za vas. Kada se dva motora ne slažu oko stvarne korisničke datoteke, taj dokument nije smetnja; to je upravo slučaj regresije koji je nedostajao vašem testiranju izdanja. Zadržite ga, imenujte ga i pokrenite ga pri svakom build-u.

Još jedno uparivanje vredi znati. Isti motor za validaciju pokreće interaktivne provere u korisničkom interfejsu za pregled, tako da ovaj CLI bez prozora i radni sto za pregled prihvata PDF-a mogu deliti jedan vokabular validacije umesto da se vremenom razilaze. I pošto [ppsPdfA, ppsPdfUa] procenjuje pristupačnost u istom prolazu, PDF/UA strana grupe se uredno poravnava sa radom na strani čitača kao što je izgradnja pristupačnog PDF čitača u Delphi-ju. Profili, formati izveštaja i kompletan preflight API dokumentovani su na stranici proizvoda za PDFium Component.