Technical Article

Učitavanje hibridnih PDF dokumenata sa referencama iz Word-a i Excel-a u Delphi-ju

Otvorite PDF koji je proizveo Microsoft Word ili Excel, prelistajte ga i ništa ne izgleda neobično. Učitajte ga u Delphi program, pročitajte broj stranica nazad i broj je tačan. Zatim ga ponovo sačuvajte sa uključenim šifrovanjem i zadatak ne uspeva sa greškom EListError, ili se izlaz otvara uz upozorenje o oštećenoj unakrsnoj referenci. Datoteka nikada nije bila oštećena. Reč je o datoteci sa hibridnim referencama, a sama struktura koja omogućava pregledaču starom petnaest godina da je otvori je struktura koja poražava učitavač koji prestane sa čitanjem prerano.

Ovo je jedan od najčešćih načina na koji cevovod za PDF koji je prošao svaki interni test nailazi na datoteku koju ne može uspešno da učita i ponovo sačuva (round-trip). Svi ulazi su generisani interno, tako da nikada nisu bili hibridni. Prva hibridna datoteka stiže onog dana kada kupac prosledi fakturu izvezenu iz tabele.

Šta Word i Excel zapravo upisuju

ISO 32000-1 opisuje raspored hibridnih referenci u §7.5.8.4. Aplikacija koja želi funkcije PDF 1.5 verzije, kao što su tokovi objekata (object streams), a da pritom i dalje omogući čitaču PDF 1.4 verzije da otvori datoteku, upisuje informacije o unakrsnim referencama dva puta. Postoji klasična tabela unakrsnih referenci sa ASCII redovima fiksne širine koji su završavali svaki PDF do verzije 1.4, i postoji tok unakrsnih referenci koji indeksira ostatak. Trailer (završni deo) klasičnog odeljka nosi unos /XRefStm čija je vrednost bajt-ofset tog toka.

Podela rada je namerna. Objekti koje stari čitač mora da dohvati, uključujući katalog i stablo stranica, adresabilni su iz klasične tabele. Objekti koji su sklopljeni u kompresovane tokove objekata označeni su kao slobodni u klasičnoj tabeli, sa unosom tipa f, tako da ih čitač 1.4 verzije jednostavno preskače i nikada ne nailazi na strukturu koju ne može da analizira. Njihove stvarne lokacije žive samo u toku unakrsnih referenci. Potpis takve datoteke je njen kraj: kratak klasičan odeljak, često ništa više od xref praćenog zaglavljem pododeljka 0 0, čiji trailer ukazuje na /XRefStm gde se nalaze stvarni podaci za oporavak.

Zašto tačan broj stranica ne dokazuje ništa

Pošto su katalog i stablo stranica namerno dostupni iz klasične tabele, učitavač koji čita samo tu tabelu pronalazi /Root, prolazi kroz stablo stranica i prijavljuje tačan broj stranica. Sve što je starom čitaču potrebno je prisutno, pa datoteka izgleda zdravo. Objekti koji nedostaju su oni spakovani u tokove objekata: rečnici polja AcroForm-a, elementi strukture tagovanog PDF-a, dugi niz malih rečnika koji nikada nisu morali biti vidljivi naslednom pregledaču.

Ovu prazninu ne primećujete sve dok nešto ne dodirne te objekte, a kompletno ponovno čuvanje dodiruje sve njih. Prolazak kroz dokument radi ponovnog šifrovanja ili ponovnog upisivanja je upravo operacija koja traži svaki broj objekta redom, zbog čega se simptom pojavljuje u trenutku čuvanja, a ne u trenutku učitavanja, daleko od svog uzroka.

Zamka je detektor koji vidi xref i zaustavlja se

Jednostavan način da se odluči kako je datoteka indeksirana je praćenje startxref-a i pregled prvih bajtova na koje ukazuje. Ključna reč xref označava klasičnu tabelu; objekat toka označava tok unakrsnih referenci. Taj test je ispravan za svaku datoteku koja se opredeljuje za jednu šemu. Pogrešan je za hibridnu datoteku, čiji startxref cilja na klasičan odeljak isključivo u svrhu zadovoljavanja starih čitača, dok je /XRefStm u trailer-u tog odeljka mesto gde je većina dokumenta zapravo indeksirana. Detektor koji vraća "classic" na prvi xref na koji naiđe nikada ne čita /XRefStm, pa svaki objekat koji živi samo u toku postaje nevidljiv.

var
  Pdf: THotPDF;
  PageCount: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('Invoice_XLS.pdf');  // count is correct
    // inspect or edit the loaded document here
    Pdf.SaveLoadedDocument('Invoice_secured.pdf');     // walks every object
  finally
    Pdf.Free;
  end;
end;

Sa detektorom koji rano izlazi, učitavanje izgleda u redu, a ponovno čuvanje je trenutak u kom se odsutni objekti oglašavaju. Rešenje nije čitanje više bajtova na početku; rešenje je prepoznavanje hibridnog trailer-a i praćenje /XRefStm-a pre nego što se odluči da je datoteka završena.

Redosled spajanja se ne može pregovarati

Nakon što su pročitana oba indeksa, oni se mogu kombinovati samo u jednom smeru. Tok unakrsnih referenci mora se prvo spojiti, a klasični unosi se popunjavaju oko njega. Razlog je mala obmana u srcu formata. Hibridna datoteka označava svoje kompresovane objekte kao slobodne u klasičnoj tabeli kako bi ih stari čitači ignorisali. Učitavač koji poštuje pravilo "prvo viđeno pobeđuje" i čita prvo klasičnu tabelu zabeležiće te brojeve objekata kao slobodne, a zatim će odbaciti unose toka koji ih zapravo lociraju, jer su mesta već zauzeta. Obrnite redosled i unosi tipa 2 iz toka, od kojih je svaki broj toka objekta plus indeks, osvojiće mesta koja treba da poseduju, a klasični unosi će se smestiti oko njih.

Ista disciplina štiti od toga da starija revizija vaskrsne obrisani objekat. Inkrementalna ažuriranja se povezuju unazad kroz /Prev, a slobodni unos tipa 0 je sentinel da je noviji odeljak povukao broj objekta. Kasnijem, starijem odeljku u lancu ne sme se dozvoliti da prepise taj sentinel zastarelom lokacijom. Tretirajte prvo viđeno kao merodavno za oznake slobodnog prostora i obrisani objekat ostaje obrisan; tretirajte to nemarno i istorija same datoteke može reanimirati sadržaj koji je najnovija revizija uklonila.

Šta ovo znači u HotPDF-u

Endžin rešava hibridne referentne datoteke umesto vas, i to čini na svakoj putanji koja mora da analizira podatke o unakrsnim referencama. Učitajte dokument pomoću LoadFromFile ili LoadFromStream, napravite izmene i pozovite SaveLoadedDocument; ili pokrenite jednokratnu operaciju kao što je EncryptFile koja čita ulaz i upisuje izlaz. U oba slučaja, oporavak čita /XRefStm, spaja odeljak toka ispred klasičnih unosa i razrešava objekte koji žive u tokovima pre nego što ih upis nabroji. Putanja šifrovanja AES-256 je mesto gde se problem prvi put pokazao, jer šifrovanje dokumenta ponovo upisuje svaki objekat i samim tim zahteva da svaki objekat već bude lociran.

// One-shot: read the hybrid input, write an AES-256 encrypted copy
Pdf.EncryptFile('Letter_DOC.pdf', 'Letter_secured.pdf',
  'owner-secret', '', aes256, [prPrint, prFillAnnotations]);

Detalj koji vredi zapamtiti nalazi se iznad samog API-ja. Datoteke koje stižu iz Word-a, Excel-a, PowerPoint-a i dugog niza putanja "Sačuvaj kao PDF" su rutinski hibridne, tako da učitavač koji testirate samo na sopstvenom izlazu možda nikada neće naići na njih tokom testiranja. Popunite svoje test primere dokumentima izvezenim iz stvarnih Office aplikacija, a ne samo datotekama koje je proizveo vaš sopstveni kod.

Provera sumnjive datoteke

Dva pregleda brzo rešavaju pitanje. Otvorite datoteku u heksadecimalnom prikazu i pročitajte bajtove nakon poslednjeg startxref-a; hibridna datoteka prikazuje kratak klasičan odeljak čiji rečnik trailer-a sadrži /XRefStm. Ili uporedite broj objekata koji prijavljuje kompletna analiza sa najvećim brojem objekta koji /Size deklariše u trailer-u. Velika razlika znači da se objekti kriju u tokovima koje učitavač nije otvorio, što je isti nedostatak koji se kasnije pretvara u neuspeh prilikom čuvanja.

Pisčeva strana ove priče, kako se tokovi objekata i kompresovane unakrsne reference uopšte proizvode, pokrivena je u našem članku o tokovima objekata i inkrementalnim ažuriranjima. Kada je hibridna datoteka o kojoj je reč takođe veoma velika, tehnike učitavanja u vodiču kroz Direct File API za velike PDF tokove rada omogućavaju vam da je pregledate bez učitavanja celog sadržaja u memoriju. Oba se prirodno povezuju sa oporavkom opisanim ovde, koji se isporučuje kao deo HotPDF komponente za Delphi i C++Builder zajedno sa API-jima za učitavanje, uređivanje, šifrovanje i potpisivanje koji su pokriveni na drugim mestima na ovom blogu.