Napišete mali validator. On otvara PDF, ide do samog kraja, pronalazi startxref, čita pomak, i očekuje da će sletjeti na ključnu riječ xref ispod koje se nalazi tablica unakrsnih referenci fiksne širine. Iz te tablice prikuplja pomake objekata, zatim skenira unatrag u potrazi za ključnom riječi trailer kako bi saznao /Root i /Size. Radi savršeno na svakoj datoteci koju ste generirali za njegovo testiranje. Zatim stigne datoteka koju je proizvela trenutna verzija Worda, ili biblioteka koja cilja PDF 1.5, i validator je proglasi neispravnom. Nema ključne riječi xref tamo gdje pomak pokazuje, nema rječnika trailer nigdje, a tablica objekata koju je validator izgradio gotovo je prazna. Datoteka je valjana. Validator je čita kroz petnaest godina staru leću
Ovo je pojedinačno najčešći razlog zašto PDF provjera na razini bajtova, napisana za klasični raspored, zakazuje na modernim dokumentima. Struktura o kojoj ovisi, tekstualna tablica unakrsnih referenci i ključna riječ trailer, postala je neobavezna u PDF-u 1.5 i često nedostaje. Dvije značajke su je zamijenile: tok unakrsnih referenci i sažeti objektni tok. Oba su opisana u ISO 32000-1, a validator koji ih ne poznaje vidi zdravu datoteku kao hrpu nedostajućih objekata
Što je PDF 1.5 promijenio u repu datoteke
ISO 32000-1 §7.5.8 definira tok unakrsnih referenci, a §7.5.7 definira objektni tok tipa /ObjStm. Zajedno omogućuju piscu da izostavi dvije strukture na koje se klasični parser oslanja. PDF 1.5 datoteka može završiti bez ikakve tablice xref. Umjesto nje, objekt na koji pokazuje startxref je obični stream objekt čiji rječnik nosi /Type /XRef, a taj tok sadrži podatke o unakrsnim referencama u kompaktnom binarnom obliku. Nema ni ključne riječi trailer, jer je trailer sad rječnik samog toka. Ključevi koje je klasični parser tražio, /Root, /Size i /ID, žive unutar tog rječnika
Druga promjena premješta same objekte. Umjesto da piše svaki posredni objekt na vlastitom pomaku u bajtovima, pisac može spakirati mnogo malih objekata, rječnike stranica, rječnike napomena, stablo strukture, u jedan objektni tok i sažeti cijeli spremnik Flateom. Pojedinačni objekti više nemaju pomak u bajtovima unutar datoteke. Imaju poziciju unutar sažetog bloka. Validator koji pretražuje sirove bajtove tražeći 1 0 obj nikad ih ne pronalazi, jer taj tekst postoji tek nakon dekompresije. Za klasični parser, pola je dokumenta jednostavno nestalo
Ključevi trailera su čist tekst, čak i u sažetoj datoteci
Umirujući dio je da čitanje trailera toka unakrsnih referenci ne zahtijeva dekompresiju ičega. Stream objekt piše se kao rječnik nakon kojeg slijedi ključna riječ stream i zatim sažeti bajtovi. Rječnik je čist tekst. Pa kad startxref pokazuje na tok unakrsnih referenci, bajtovi odmah nakon broja objekta izgledaju kao običan rječnik, a /Root, /Size i /ID sjede tamo otvoreno, prije nego što počne ključna riječ stream i Flate podaci
To znači da validator može saznati tri činjenice koje mu najviše trebaju, gdje je katalog, koliko objekata datoteka tvrdi da ima, i identifikator datoteke, raščlanjujući samo rječnik toka. Ne mora dekomprimirati podatke o unakrsnim referencama, i ne mora tumačiti binarne zapise unutar njih. Posao koji porazi naivan parser nije čitanje trailera; to je pronalaženje objekata. To su dva odvojiva problema, a rješavanje prvog je jeftino
Objektni tokovi: zaglavlje, pa Flate blob
Objektni tok je spremnik. Njegov rječnik nosi /Type /ObjStm, unos /N koji daje broj objekata spakiranih unutra, i unos /First koji daje pomak u bajtovima, unutar dekomprimiranih podataka, gdje počinje tijelo prvog objekta. Sažeti sadržaj, jednom dekomprimiran, počinje malim zaglavljem od /N parova cijelih brojeva. Svaki par je broj objekta i pomak tijela tog objekta u odnosu na /First. Nakon zaglavlja slijede sama tijela objekata, spojena jedno na drugo
Proširivanje jednog je mehaničko čim su bajtovi dekomprimirani. Pročitate rječnik da dobijete /N i /First, dekomprimirate tok Flate dekoderom, prođete kroz početne parove /N kako biste saznali koji broj objekta živi na kojem pomaku, a zatim izvučete svako tijelo kao da je obični posredni objekt. Jedina prava ovisnost je Flate dekoder, a njega već imate: Delphi isporučuje System.ZLib, a Free Pascal isporučuje jedinicu zstream, obje omotavaju zlib i dekomprimiraju sirovi Flate tok bez ikakvog koda treće strane. Rutina koja dodaje svaki izdvojeni objekt u tablicu objekata validatora čini da se ostatak validatora, dio koji prolazi /Root i provjerava stablo stranica, ponaša potpuno isto kao na klasičnoj datoteci
Što ne morate implementirati
Lako je precijeniti taj posao. Čitanje ključeva trailera iz sažete datoteke ne zahtijeva dekodiranje binarnih zapisa toka unakrsnih referenci. Tok unakrsnih referenci iz §7.5.8 koristi tri tipa zapisa, a zapis tipa 2, onaj koji kaže ovaj objekt živi unutar objektnog toka N na indeksu i
, jest ono što biste dekodirali da izgradite potpunu mapu pomaka. Ta mapa vam treba da razriješite proizvoljne objekte po broju. Ne treba vam da biste pročitali /Root, /Size i /ID, koji su u tekstualnom rječniku, i ne treba vam da biste proširili objektne tokove, jer svaki /ObjStm najavljuje vlastiti sadržaj kroz /N i /First
Također ne morate obrađivati PNG i TIFF prediktorske funkcije koje tok unakrsnih referenci može primijeniti kroz svoj /DecodeParms samo da biste dobili ključeve trailera. Prediktori filtriraju binarne retke unakrsnih referenci kako bi se bolje sažimali; nemaju nikakve veze s rječnikom koji prethodi toku. Minimalna nadogradnja koja čini klasični validator svjesnim modernog PDF-a stoga je malena: kad startxref sleti na tok umjesto na ključnu riječ xref, raščlanite rječnik toka za ključeve trailera, i proširite svaki /ObjStm objekt na koji naiđete kako bi njegov sadržaj ušao u tablicu objekata. Dekodiranje zapisa tipa 2 i prediktora zaseban je, veći zadatak koji možete odgoditi dok vam istinski ne zatreba razrješavanje proizvoljnih objekata
Zašto provjera sukladnosti prvo mora proširiti tokove
Ovo prestaje biti akademsko pitanje čim pokrenete provjeru profila. PDF/A ili PDF/X validator pregledava određene objekte: katalog dokumenta za niz /OutputIntents, tok /Metadata za XMP paket s pravim identifikatorom, svaki opisnik fonta za ugrađenu datoteku fonta, trailer za /ID. U sažetoj datoteci, većina tih objekata nalazi se unutar objektnih tokova. Validator koji nije proširio objektne tokove ne može vidjeti ključeve kataloga, ne može pronaći metapodatke, i ne može popisati fontove. Prijavit će savršeno sukladan dokument kao da mu nedostaje izlazna namjera, nedostaje XMP, i nedostaje pola strukture, jer dokazi koji su mu potrebni i dalje sjede u Flate bloku koji nikad nije dekomprimirao
Redoslijed je bitan. Proširivanje se mora dogoditi prije nego što se provjere pokrenu, ne usporedno s njima, jer svaka provjera pretpostavlja da može doći do objekta po broju. Ako provjeru profila spojite izravno na sirovo skeniranje bajtova, ona nasljeđuje sljepoću klasičnog parsera i proizvodi lažna kršenja upravo na modernim datotekama koje su najvjerojatnije dobro oblikovane, budući da su izašle iz alatnih lanaca dovoljno novih da uopće pišu tokove unakrsnih referenci
Prepuštanje raščlambe PDFium-u
PDFium komponenta raščlanjuje tokove unakrsnih referenci i objektne tokove kao dio učitavanja dokumenta, što je praktičan način da se izbjegne ručno pisanje koraka dekompresije i proširivanja. Kad učitate datoteku komponentom TPdf, objekti spakirani u spremnike /ObjStm već su razriješeni, a ulazne točke validacije vide potpuno prošireni dokument. ValidatePdfA vraća zapis TPdfAValidationResult čije je polje Conformance vrijednost tipa TPdfAConformance, poput pac1b ili pacNone, čije je polje Issues skup konkretnih pronađenih problema, i čija je metoda IsCompliant istinita samo kad je otkrivena razina sukladnosti i skup problema je prazan. Budući da su objekti prošireni tijekom učitavanja, niz /OutputIntents ili ugrađeni font koji je živio unutar objektnog toka bude pronađen, a ne prijavljen kao nedostajući
uses
PDFium, FPdfPdfa;
function CheckPdfA(const FileName: string): TPdfAValidationResult;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True; // raščlanjuje xref/objektne tokove pri učitavanju
Result := Pdf.ValidatePdfA; // vidi proširenu tablicu objekata
finally
Pdf.Free;
end;
end;
Isto vrijedi za ValidatePdfX, koja vraća TPdfXValidationResult istog oblika. Poanta usmjeravanja kroz PDFium je da se strukturna dekompresija opisana iznad dogodi jednom, ispravno, unutar učitavača, pa vaš kod za validaciju nikad ne vidi razliku između klasične datoteke i potpuno sažete. Oboje stižu validatoru kao razriješen skup objekata
function PdfXConformanceName(C: TPdfXConformance): string;
begin
case C of
pxc1a: Result := 'PDF/X-1a';
pxc3 : Result := 'PDF/X-3';
pxc4 : Result := 'PDF/X-4';
else
Result := 'none';
end;
end;
var
Pdf: TPdf;
R : TPdfXValidationResult;
Issue: TPdfXValidationIssue;
IssueCount: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'Press_Ready.pdf';
Pdf.Active := True;
R := Pdf.ValidatePdfX;
if R.IsCompliant then
Writeln('PDF/X conformance: ', PdfXConformanceName(R.Conformance))
else
begin
IssueCount := 0;
for Issue in R.Issues do // Issues je skup: prebroji njegove članove
Inc(IssueCount);
Writeln('Not conformant; issue count = ', IssueCount);
end;
finally
Pdf.Free;
end;
end;
Ako su bajtovi već u memoriji umjesto na disku, isti slijed učitaj-pa-validiraj radi kroz preopterećenje LoadDocument(const Data: TBytes), koje uzima sirov sadržaj datoteke i raščlanjuje njezine tokove unakrsnih referenci i objektne tokove na isti način kao putanja do datoteke. Pouka za ručno pisan validator je strukturno pravilo, a ne API: pročitajte ključeve trailera iz rječnika toka u čistom tekstu, proširite svaki /ObjStm Flate dekoderom prije nego što prođete kroz dokument, i tretirajte dekodiranje binarnih zapisa unakrsnih referenci kao zaseban, opcionalan posao kakav i jest
Kad je struktura proširena, validator može voditi ostatak radnog procesa nad njom. Za alat za preflight provjeru u naredbenom retku koji prijavljuje sukladnost kroz mapu ulaznih datoteka, pogledajte naš prolazak kroz izradu CLI izvještaja za serijski preflight. Kad je validacija vrata prije rastavljanja velikog dokumenta, tehnike iz našeg vodiča za razdvajanje PDF dokumenata u više datoteka prirodno se uparuju s ovdje prikazanim obrascem učitaj-pa-provjeri. Oboje se nadograđuje na površinu za učitavanje i validaciju PDFium komponente za Delphi i C++Builder