Tehnički članak

Rukovanje hibridno-referentnim PDF-ovima iz Office aplikacija u Delphiju

Izvezite dokument iz programa Microsoft Word ili Excel koristeći opciju Sačuvaj kao PDF i datoteka na disku će, najčešće, biti hibridno-referentna datoteka. Ona nosi svoje informacije o unakrsnim referencama dvaput: jednom kao klasičnu tabelu fiksne širine koja je završavala svaki PDF do verzije 1.4, i jednom kao komprimovani tok unakrsnih referenci od kojeg većina dokumenta zapravo zavisi. Jedan jedini ključ trejlera, /XRefStm, spaja ta dva prikaza zajedno, a da li će alat videti ceo dokument svodi se na to da li prati taj ključ

Ovaj članak posmatra hibridne datoteke sa strane potrošnje: kako izgledaju bajtovi na kraju datoteke, kako se dva prikaza razilaze tokom uređivanja, i kako Delphi cevovod može da detektuje i rutira hibridne ulaze. Kako učitavač (loader) spaja prikaze i zašto o redosledu nema pregovora, tema je našeg HotPDF članka o učitavanju hibridno-referentnih datoteka; ovaj članak se bavi prepoznavanjem samog rasporeda

Zašto Office izvozi pišu indeks dvaput

PDF 1.5 je uveo dve funkcije koje su promenile oblik datoteke: tokove unakrsnih referenci (cross-reference streams), koji skladište indeks objekta kao komprimovane binarne podatke umesto kao tabelu u običnom tekstu, i tokove objekata (object streams), koji pakuju mnogo malih objekata u jedan Flate-komprimovani kontejner. Pisač (writer) koji ih koristi proizvodi manje datoteke, ali PDF 1.4 čitač ne može da otvori rezultat, jer su strukture na koje se on oslanja, ključna reč xref i rečnik trailer, nestale

ISO 32000-1 §7.5.8.4 definiše kompromis. Hibridno-referentna datoteka zapisuje oboje: klasičnu tabelu unakrsnih referenci koja adresira objekte do kojih stari čitač mora da dopre, uključujući katalog i stablo stranica, kao i tok unakrsnih referenci koji indeksira sve ostalo. Objekti složeni u tokove objekata označeni su kao slobodni u klasičnoj tabeli, tako da ih 1.4 čitač preskače bez žalbi; njihove stvarne lokacije postoje samo u toku. Klasični trejler zatim nosi /XRefStm ključ koji drži ofset bajtova tog toka. Stari pregledač nikada ne čita taj ključ i renderuje datoteku iz prikaza tabele. Moderan pregledač ga prati i vidi kompletan dokument. Word i Excel emituju tačno ovakav raspored godinama, i to je razlog zašto hibridne datoteke nisu egzotičan ivični slučaj, već veliki udeo onoga što poslovni cevovodi primaju

Kako izgleda rep hibridne datoteke

Raspored je najlakše razumeti iz bajtova. Evo repa male hibridne datoteke, sa skraćenim ofsetima; u stvarnom Office izvozu, vrednost /XRefStm je obično veliki ofset blizu kraja datoteke. Redosled čitanja je prolazak koji počinje od repa opisan u našem pregledu strukture PDF datoteke: nađite %%EOF, pročitajte startxref, skočite na tabelu

% ... body objects, including object streams and, at byte 116,
% the cross-reference stream (a stream object with /Type /XRef) ...

xref                    % classic section: what startxref points at
0 4
0000000000 65535 f      % slot 0: head of the free list, always present
0000000017 00000 n      % object 1: the catalog, visible to any reader
0000000000 65535 f      % object 2: marked free -- lives in an object stream
0000000000 65535 f      % object 3: same; only the stream view locates it
trailer
<<
  /Size 4
  /Root 1 0 R
  /XRefStm 116          % byte offset of the cross-reference stream
>>
startxref
7164                    % byte offset of the 'xref' keyword above
%%EOF

Dva detalja u ovom ispisu nose ceo mehanizam. Prvo, startxref pokazuje na klasičnu sekciju, i to namerno: to je adresa na koju stari čitač mora da sleti. Tok unakrsnih referenci je dostupan samo preko /XRefStm ključa unutar rečnika trejlera, tako da parser koji nikada ne traži taj ključ nikada i ne sazna da tok postoji. Drugo, objekti 2 i 3 su laži benigne vrste. Klasična tabela ih deklariše kao slobodne, ali to su stvarni objekti koji se nalaze unutar komprimovanog kontejnera; oznaka 'slobodno' je ono što sprečava 1.4 čitač da se spotakne o unose koje ne može da koristi. Potrošač koji veruje samo klasičnom prikazu zaključiće da veći deo ovog dokumenta ne postoji

Kako se dva prikaza razilaze

Hibridna datoteka koja je tek izašla iz Word-a je interno dosledna: oba prikaza opisuju isti dokument, svaki u svom deklarisanom obimu. Problem počinje kada se datoteka uređuje alatom koji razume samo jedan od prikaza. Zamislite uslužni program za pečatiranje koji dodaje inkrementalno ažuriranje u klasičnom stilu: novi objekti, nova xref sekcija, /Prev lanac do prethodne sekcije i novi trejler. Ako taj trejler odbaci /XRefStm ključ, prikaz toka ostaje napušten (orphaned); ako on kopira staru vrednost unapred, prikaz toka i dalje opisuje dokument onakav kakav je bio pre uređivanja. U svakom slučaju, ta dva indeksa se sada ne slažu oko toga šta datoteka sadrži

Rezultujuća datoteka ima karakterističan potpis kvara: objekti vidljivi u jednom prikazu nedostaju ili su zastareli u drugom. Čitač koji razrešava preko prikaza toka pronalazi verziju ažuriranog objekta pre uređivanja, ili uopšte ne pronalazi unos za dodati objekat. Čitač na prikazu tabele vidi uređivanje, ali gubi trag komprimovanim objektima koje locira samo tok. U praksi, ovo se ispoljava kao polja formulara koja prežive u jednom pregledaču a nestanu u drugom, anotacije koje izgleda kao da je prolaz pečatiranja obrisao, ili pretrage koje slete na potpuno pogrešan objekat

Ono što ove datoteke čini skupim za debagovanje je to što ih Adobe Acrobat obično otvara bez prigovora: kada se indeks ne slaže sa bajtovima, on tiho obnavlja podatke o unakrsnim referencama skenirajući zaglavlja objekata, tako da ko god da je proizveo pokvarenu datoteku ne vidi ništa loše. Kvar izlazi na površinu kasnije, kada datoteka stigne do strogog potrošača, validatora pre štampe (preflight), usluge za potpisivanje ili zadatka za arhiviranje, koji veruje deklarisanoj strukturi i prijavljuje nedostajuće objekte ili neslaganje unakrsnih referenci. "Otvara se u redu u Acrobat-u" je način na koji počinje gotovo svaki tiket o hibridnoj desinhronizaciji

Detektovanje hibridne datoteke u običnom Delphiju

Klasifikacija ulaza ne zahteva PDF biblioteku. /XRefStm ključ može se pojaviti samo unutar klasičnog rečnika trejlera, a aktivni trejler se nalazi u poslednjih nekoliko kilobajta datoteke, jer specifikacija zahteva da se %%EOF pojavi blizu fizičkog kraja. Čitanje ograničenog prozora repa i pretraga po njemu dovoljni su za trijažu:

uses
  System.SysUtils, System.Classes, System.StrUtils, System.Math;

function IsHybridReferencePdf(const FileName: string): Boolean;
const
  TailWindow = 2048;
var
  Stream: TFileStream;
  Buf: TBytes;
  Tail: string;
  Len, TrailerPos, NextPos, KeyPos, StartXrefPos: Integer;
begin
  Result := False;
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    if Stream.Size < 48 then
      Exit;
    Len := Min(TailWindow, Integer(Stream.Size));
    SetLength(Buf, Len);
    Stream.Position := Stream.Size - Len;
    Stream.ReadBuffer(Buf[0], Len);
  finally
    Stream.Free;
  end;

  // Every keyword involved is 7-bit ASCII, so a byte-wise decode is safe
  Tail := TEncoding.ANSI.GetString(Buf);

  // Find the LAST 'trailer' keyword: with incremental updates,
  // the newest trailer is the one that governs the file
  TrailerPos := 0;
  NextPos := Pos('trailer', Tail);
  while NextPos > 0 do
  begin
    TrailerPos := NextPos;
    NextPos := PosEx('trailer', Tail, NextPos + 1);
  end;
  if TrailerPos = 0 then
    Exit;  // no classic trailer: a pure xref-stream file, not hybrid

  // A hybrid trailer carries /XRefStm between 'trailer' and 'startxref'
  KeyPos := PosEx('/XRefStm', Tail, TrailerPos);
  StartXrefPos := PosEx('startxref', Tail, TrailerPos);
  Result := (KeyPos > 0) and
    ((StartXrefPos = 0) or (KeyPos < StartXrefPos));
end;

Tri ishoda se poklapaju sa tri rasporeda. Datoteka koja je samo klasična ima trejler, ali nema /XRefStm: False (Netačno). Datoteka koja se u potpunosti oslanja na tokove unakrsnih referenci uopšte nema ključnu reč trailer, njeni ključevi trejlera žive u rečniku toka: takođe False (Netačno), što je ispravno, jer je takva datoteka komprimovana, a ne hibridna. Samo raspored sa dvostrukim indeksiranjem vraća True (Tačno)

Za produkcionu upotrebu, dva ojačavanja vrede dodatnih linija koda. Parsirajte ceo broj nakon /XRefStm, pređite na taj ofset i potvrdite da se objekat toka sa /Type /XRef zaista tamo nalazi; odsečena datoteka može da nosi ključ dok toka nema, što pripada drugoj kategoriji u odnosu na zdravu hibridnu datoteku. Takođe, tretirajte veličinu prozora kao parametar: 2 KB pokriva običan Office izlaz, ali neuobičajeno veliki rečnik trejlera može da gurne ključnu reč izvan dometa, a proširivanje prozora je bolje od slučajnog proglašavanja datoteke klasičnom

Rutiranje hibridnih datoteka kroz Delphi cevovod

Detekcija vam donosi odluku o rutiranju. Za datoteke koje se samo čitaju, renderuju ili validiraju, koristite učitavač koji razrešava oba prikaza, a zatim proverite ponašanje umesto bajtova. PDFium Component parsira /XRefStm lanac tokom učitavanja, tako da je tabela objekata koju vaš kod vidi spojena, a provere opisane u našem članku o validaciji objekata i tokova unakrsnih referenci primenjuju se neizmenjene. Ako je desinhronizovani hibrid oštećen dovoljno jako da odbije učitavanje, mašina ga prijavljuje kroz svoj skup grešaka, FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PASSWORD, FPDF_ERR_SECURITY i FPDF_ERR_PAGE, pri čemu FPDF_ERR_FORMAT proizvodi strukturno oštećenje. Ipak, nemojte se oslanjati na taj signal: PDFium je dizajniran da bude popustljiv i tiho obnavlja većinu nedoslednih datoteka, tako da uspešno učitavanje dokazuje da se datoteka mogla oporaviti, a ne da se njena dva prikaza slažu. Smislena provera doslednosti je poređenje onoga što potpuni prolazak kroz objekte pronalazi sa onim što trejlerov /Size deklariše

Za datoteke koje vaš cevovod menja, najbezbednija politika je da učinite da one uopšte ne budu hibridne. Učitavanje praćeno potpunim čuvanjem preko HotPDF-a prepisuje dokument sa jednom, interno doslednom unakrsnom referencom u jednom formatu: bez /XRefStm-a, bez drugog prikaza koji može ispasti iz sinhronizacije, dok je svaki objekat u vlasništvu tačno jednog unosa indeksa. Ta normalizacija je ono što želite pre arhiviranja, pre strogog daljeg RIP-a ili usluge potpisivanja, i nakon bilo kakvog uređivanja primenjenog na hibridni ulaz. To funkcioniše zato što je učitavač (loader) ispravno spojio prikaze prilikom ulaska, mehanizam kroz koji članak o HotPDF hibridnim referencama detaljno prolazi

Jedina klasa datoteka koju treba ostaviti na miru su digitalno potpisani dokumenti. Potpuno prepisivanje pomera svaki bajt, što poništava svaki potpis izračunat nad originalnim rasponima. Promena u potpisanom hibridu mora ići kao pravilno inkrementalno ažuriranje koje održava oba prikaza; datoteka kojoj je potrebno samo čitanje treba da prođe netaknuta. Normalizacija je za datoteke koje posedujete; na potpisane datoteke samo dodajete materijal

Hibridno-referentni PDF-ovi nisu loše formirani; oni predstavljaju sopstveni most kompatibilnosti tog formata, a Office aplikacije će nastaviti da ih proizvode sve dok PDF 1.4 čitači opstaju u korisničkoj bazi. Cevovod koji može da uoči /XRefStm ključ, validira spojeni dokument sa PDFium Component komponentom i regeneriše čist izlaz sa jednim indeksom uz pomoć HotPDF Component komponente tretira ih kao ono što zaista jesu: obične ulaze sa jednim dodatnim putokazom u trejleru