Otvorite PDF koji su proizveli Microsoft Word ili Excel, prelistajte ga i ništa ne izgleda neobično. Učitajte ga u Delphi program, pročitajte broj stranica natrag, i broj je točan. Zatim ga ponovno spremite s uključenom enkripcijom i posao ne uspije s EListError, ili se izlaz otvori uz upozorenje o oštećenoj unakrsnoj referenci. Datoteka nikada nije bila oštećena. To je datoteka hibridne reference, a sama struktura koja omogućuje petnaest godina starom pregledniku da je otvori je struktura koja poražava učitavač (loader) koji prerano prestane s čitanjem
Ovo je jedan od najčešćih načina na koji PDF cjevovod koji je prošao svaki interni test nailazi na datoteku koju ne može ponovno spremiti u istom stanju (round-trip). Svi ulazi bili su generirani unutar tvrtke, pa nikada nisu bili hibridni. Prva hibridna datoteka stiže onog dana kada kupac proslijedi račun izvezen iz proračunske tablice
Što Word i Excel zapravo pišu
ISO 32000-1 opisuje izgled hibridne reference u §7.5.8.4. Aplikacija koja želi značajke PDF-a 1.5 poput tokova objekata (object streams), dok i dalje dopušta čitaču PDF-a 1.4 da otvori datoteku, piše informacije o unakrsnim referencama dvaput. Postoji klasična tablica unakrsnih referenci, redovi ASCII znakova fiksne širine koji su završavali svaki PDF do verzije 1.4, a postoji i tok unakrsnih referenci koji indeksira ostatak. Prikolica (trailer) klasičnog odjeljka nosi unos /XRefStm čija je vrijednost pomak (offset) tog toka u bajtovima
Podjela rada je namjerna. Objekti do kojih stari čitač mora doći, katalog i stablo stranica među njima, mogu se adresirati iz klasične tablice. Objekti koji su složeni u komprimirane tokove objekata označeni su kao slobodni u klasičnoj tablici, s unosom tipa f, tako da ih čitač 1.4 izravno preskače i nikada ne posrne na strukturi koju ne može parsirati. Njihove stvarne lokacije žive samo u toku unakrsnih referenci. Potpis takve datoteke je njezin rep: kratak klasični odjeljak, često ništa više od xref praćenog zaglavljem pododjeljka 0 0, čija prikolica pokazuje na /XRefStm gdje se nalaze stvarni podaci za oporavak
Zašto točan broj stranica ne dokazuje ništa
Budući da su katalog i stablo stranica namjerno dostupni iz klasične tablice, učitavač koji čita samo tu tablicu pronalazi /Root, hoda po stablu stranica i prijavljuje točan broj stranica. Sve što starom čitaču treba je prisutno, tako da datoteka izgleda zdravo. Objekti koji su nestali su oni spakirani u tokove objekata: rječnici polja AcroForm, strukturni elementi označenog (tagged) PDF-a, dugačak rep malih rječnika koji nikada nisu ni morali biti vidljivi starom pregledniku
Nećete primijetiti prazninu dok nešto ne dotakne te objekte, a potpuno ponovno spremanje dodiruje sve njih. Hodanje kroz dokument radi ponovnog kriptiranja ili ponovnog pisanja je upravo ona operacija koja traži svaki broj objekta redom, zbog čega simptom ispliva na površinu za vrijeme spremanja, a ne za vrijeme učitavanja, daleko od svog uzroka
Zamka je detektor koji vidi xref i stane
Jeftin način da se odluči kako je datoteka indeksirana je pratiti startxref i pregledati prve bajtove na koje pokazuje. Ključna riječ xref znači klasičnu tablicu; objekt toka znači tok unakrsnih referenci. Taj test je ispravan za bilo koju datoteku koja se drži jedne sheme. Pogrešan je za hibridnu datoteku, čiji cilj na startxref ima klasični odjeljak s jedinom svrhom zadovoljavanja starih čitača, dok je /XRefStm u prikolici tog odjeljka mjesto gdje se većina dokumenta zapravo indeksira. Detektor koji vrati "klasični" na prvi xref koji sretne nikada ne čita /XRefStm, i svaki objekt koji živi samo u toku postaje nevidljiv
var
Pdf: THotPDF;
PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('Invoice_XLS.pdf'); // broj je točan
// ovdje pregledajte ili uredite učitani dokument
Pdf.SaveLoadedDocument('Invoice_secured.pdf'); // hoda kroz svaki objekt
finally
Pdf.Free;
end;
end;
S postavljenim detektorom ranog izlaza, učitavanje izgleda u redu, a ponovno spremanje je mjesto gdje se odsutni objekti oglašavaju. Rješenje nije pročitati više bajtova na početku; rješenje je prepoznati hibridnu prikolicu i pratiti /XRefStm prije nego što se odluči da je datoteka gotova
Redoslijed spajanja ne može se pregovarati
Kada su pročitana oba indeksa, mogu se kombinirati samo u jednom smjeru. Tok unakrsnih referenci prvo se mora spojiti, s klasičnim unosima popunjenim oko njega. Razlog je mala obmana u srcu formata. Hibridna datoteka označava svoje komprimirane objekte kao slobodne u klasičnoj tablici kako bi ih stari čitači zanemarili. Učitavač koji poštuje pravilo "prvi viđen pobjeđuje" i prvo čita klasičnu tablicu zapisat će te brojeve objekata kao slobodne, a zatim će odbaciti unose iz toka koji ih zapravo lociraju, jer su mjesta već zauzeta. Obrnite redoslijed i unosi tipa 2 iz toka, od kojih je svaki broj toka objekta plus indeks, osvajaju mjesta koja bi trebali posjedovati, a klasični unosi se smještaju oko njih
Ista disciplina čuva od starije revizije koja bi oživjela izbrisani objekt. Inkrementalna ažuriranja ulančavaju se unatrag kroz /Prev, a slobodni unos tipa 0 je stražar (sentinel) koji označava da je noviji odjeljak povukao taj broj objekta. Kasnijem, starijem odjeljku u lancu ne smije se dopustiti da prepiše tog stražara zastarjelom lokacijom. Tretirajte prvog viđenog kao autoritet za slobodne markere i izbrisani objekt ostaje izbrisan; tretirajte to nepažljivo i vlastita povijest datoteke oživjet će sadržaj koji je najnovija revizija uklonila
Što to znači u HotPDF-u
Mehanizam umjesto vas rješava datoteke s hibridnim referencama, i to radi na svakoj putanji koja mora parsirati podatke unakrsnih referenci. Učitajte dokument pomoću LoadFromFile ili LoadFromStream, napravite svoje promjene i pozovite SaveLoadedDocument; ili pokrenite jednokratnu operaciju poput EncryptFile koja čita ulaz i piše izlaz. U oba slučaja oporavak čita /XRefStm, spaja odjeljak toka ispred klasičnih unosa i rješava objekte koji žive u tokovima prije nego što ih pisanje nabroji. Putanja AES-256 enkripcije je mjesto gdje se problem prvi put pokazao, jer kriptiranje dokumenta ponovno piše svaki objekt i stoga zahtijeva da je svaki objekt već lociran
// Jednokratno: pročitajte hibridni ulaz, zapišite AES-256 kriptiranu kopiju
Pdf.EncryptFile('Letter_DOC.pdf', 'Letter_secured.pdf',
'owner-secret', '', aes256, [prPrint, prFillAnnotations]);
Detalj koji vrijedi ponijeti sa sobom nalazi se uzvodno od API-ja. Datoteke koje pristižu iz Worda, Excela, PowerPointa i s dugačkog popisa "Spremi kao PDF" cjevovoda rutinski su hibridne, pa učitavač koji vježbate samo protiv izlaza vašeg vlastitog generatora možda nikada neće sresti takvu datoteku tijekom testiranja. Zasadite svoje alate za testiranje dokumentima izvezenim iz pravih Office aplikacija, a ne samo s datotekama koje je vaš vlastiti kôd proizveo
Provjera datoteke u koju sumnjate
Dvije inspekcije brzo rješavaju to pitanje. Otvorite datoteku u heksadecimalnom prikazu i pročitajte bajtove nakon konačnog startxref; hibridna datoteka pokazuje kratak klasični odjeljak čiji rječnik prikolice sadrži /XRefStm. Ili usporedite broj objekata koje prijavi puno parsiranje s najvećim brojem objekta koji /Size deklarira u prikolici. Velika praznina znači da se objekti skrivaju u tokovima koje učitavač nije otvorio, što je isti manjak koji se kasnije pretvara u neuspjeh pri spremanju
Rep tipičnog Excel izvoza čini prvu provjeru konkretnom. Sve nakon konačne ključne riječi xref običan je ASCII, pa se potpis može pročitati izravno iz heksadecimalnog prikaza (prikazani pomaci samo za ilustraciju, dodane napomene):
xref
0 0 % prazan klasični pododjeljak: nema redova uopće
trailer
<< /Size 216 % jedan iza najvišeg broja objekta u upotrebi
/Root 1 0 R
/Info 15 0 R
/ID [<5C9A...> <5C9A...>]
/XRefStm 87325 % pomak toka unakrsnih referenci u bajtovima
>>
startxref
88710 % pokazuje na gornji klasični odjeljak
%%EOF
Pododjeljak 0 0 je znak: klasična tablica s nula unosa postoji samo da bi nosila prikolicu, a prikolica postoji uglavnom da bi rekla /XRefStm 87325. Detektor koji se zaustavlja na ključnoj riječi xref je, u ovom trenutku, vidio indeks ničega. Kada biste radije skriptirali provjeru nego je gledali odokativno, marker uvijek sjedi unutar posljednjih nekoliko kilobajta datoteke, tako da je ograničeno čitanje unatrag dovoljno
// Vraća /XRefStm pomak s repa datoteke, ili -1 ako
// marker nije prisutan (datoteka nije hibridna, ili uopće nije PDF)
function FindXRefStm(const FileName: string): Int64;
var
FS: TFileStream;
Tail: AnsiString;
Len, P: Integer;
begin
Result := -1;
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Len := 2048; // prikolica živi u repu
if FS.Size < Len then
Len := Integer(FS.Size);
FS.Position := FS.Size - Len; // ograničeno čitanje unatrag: maks 2 KB
SetLength(Tail, Len);
FS.ReadBuffer(Tail[1], Len);
finally
FS.Free;
end;
P := Pos(AnsiString('/XRefStm'), Tail);
if P = 0 then
Exit; // nema hibridnog markera u repu
Inc(P, Length('/XRefStm'));
while (P <= Len) and (Tail[P] in [' ', #9, #13, #10]) do
Inc(P); // preskoči razmak nakon ključa
Result := 0;
while (P <= Len) and (Tail[P] in ['0'..'9']) do
begin
Result := Result * 10 + Ord(Tail[P]) - Ord('0');
Inc(P);
end;
end;
// Korištenje: nenegativan rezultat imenuje bajt na kojem počinje tok
if FindXRefStm('Invoice_XLS.pdf') >= 0 then
Writeln('hibridna referentna datoteka: za ponovno spremanje bit ce potreban odjeljak /XRefStm');
Tretirajte probu kao trijažu, a ne kao parsiranje: ona vam govori koje datoteke u seriji zaslužuju pažnju prije nego što se pokrene zadatak ponovnog spremanja, i ništa više. Ono što učitavač mora zatim učiniti s pomakom koji pronađe, prateći lanac odjeljaka, spajajući unose iz toka ispred onih klasičnih, poštujući stražare slobodnih unosa, objašnjeno je korak po korak u našem pratećem članku o rukovanju PDF-ovima hibridnih referenci iz Office aplikacija
Stranica ovog narativa koja se odnosi na pisca, kako se tokovi objekata i komprimirane unakrsne reference uopće proizvode, obrađena je u našem članku o tokovima objekata i inkrementalnim ažuriranjima. Kada je dotična hibridna datoteka ujedno i vrlo velika, tehnike učitavanja u API prohodu Direct File za rad s velikim PDF-ovima omogućuju vam da je pregledate bez učitavanja cijele stvari u memoriju. Oboje se prirodno sljubljuje s ovdje opisanim oporavkom, koji se isporučuje kao dio HotPDF komponente za Delphi i C++Builder, uz API-je za učitavanje, uređivanje, kriptiranje i potpisivanje obrađene na drugim mjestima na ovom blogu