Otvorte PDF vytvorené v programoch Microsoft Word alebo Excel, prelistujte ho a nič nevyzerá neobvykle. Načítajte ho do programu v Delphi, prečítajte si späť počet strán a číslo bude správne. Následne ho znova uložte so zapnutým šifrovaním a úloha zlyhá s chybou EListError, alebo sa na výstupe otvorí upozornenie na poškodený krížový odkaz. Súbor pritom nikdy nebol poškodený. Ide o súbor s hybridným odkazom a presne tá istá štruktúra, ktorá umožňuje jeho otvorenie v pätnásťročnom prehliadači, je štruktúrou, ktorá znefunkční zavádzač, ktorý prestane čítať príliš skoro
Toto je jeden z najčastejších spôsobov, ako sa PDF proces, ktorý prešiel každým interným testom, stretne so súborom, ktorý nedokáže znova spracovať. Všetky vstupy boli generované interne, takže nikdy neboli hybridné. Prvý hybridný súbor sa objaví v deň, keď zákazník prepošle faktúru exportovanú z tabuľkového procesora
Čo Word a Excel skutočne zapisujú
Norma ISO 32000-1 popisuje rozloženie hybridného odkazu v časti §7.5.8.4. Aplikácia, ktorá požaduje funkcie formátu PDF 1.5, ako sú napríklad toky objektov, a zároveň chce umožniť čítačke formátu PDF 1.4 otvoriť daný súbor, zapíše informácie o krížových odkazoch dvakrát. Existuje klasická tabuľka krížových odkazov – ASCII riadky s pevnou šírkou, ktoré uzatvárali každé PDF až do verzie 1.4 – a existuje tok krížových odkazov, ktorý indexuje zvyšok. Ukončovač klasickej časti obsahuje položku /XRefStm, ktorej hodnota je bajtový ofset tohto toku
Toto rozdelenie úloh je zámerné. Objekty, ku ktorým musí mať stará čítačka prístup, vrátane katalógu a stromu stránok, sú adresovateľné z klasickej tabuľky. Objekty, ktoré boli zbalené do komprimovaných tokov objektov, sú v klasickej tabuľke označené ako voľné, s typom položky f, takže čítačka verzie 1.4 ich rovno preskočí a nikdy nenarazí na štruktúru, ktorú nevie analyzovať. Ich skutočné umiestnenie sa nachádza iba v toku krížových odkazov. Charakteristickým znakom takého súboru je jeho koniec: krátka klasická časť, často pozostávajúca len z xref s nasledujúcou hlavičkou podsekcie 0 0, ktorej trailer ukazuje na /XRefStm, kde sa nachádzajú skutočné údaje na obnovu
Prečo správny počet strán nič nedokazuje
Keďže katalóg a strom stránok sú zámerne dostupné z klasickej tabuľky, zavádzač, ktorý načíta len túto tabuľku, nájde /Root, prejde stromom stránok a nahlási správny počet strán. Všetko, čo stará čítačka potrebuje, je prítomné, takže súbor sa javí ako v poriadku. Objekty, ktoré chýbajú, sú tie zbalené do tokov objektov: slovníky polí AcroForm, prvky štruktúry tagovaného PDF a dlhý zoznam malých slovníkov, ktoré nikdy nemuseli byť viditeľné pre starší prehliadač
Túto medzeru si nevšimnete, kým sa niečo nedotkne týchto objektov a pri úplnom opätovnom uložení sa načíta úplne každý. Prechádzanie dokumentu za účelom jeho opätovného šifrovania alebo prepísania je presne tou operáciou, ktorá si postupne vyžiada každé číslo objektu, čo je dôvodom, prečo sa symptóm prejaví až pri ukladaní a nie pri načítavaní, teda ďaleko od jeho skutočnej príčiny
Pascou je detektor, ktorý vidí xref a zastaví sa
Lacný spôsob, ako určiť, akým spôsobom je súbor indexovaný, je sledovať startxref a preskúmať prvé bajty, na ktoré ukazuje. Kľúčové slovo xref znamená klasickú tabuľku; objekt toku znamená tok krížových odkazov. Tento test je správny pre akýkoľvek súbor, ktorý používa iba jednu schému. Je však nesprávny pre hybridný súbor, ktorého startxref smeruje na klasickú časť výlučne za účelom uspokojenia starých čítačiek, zatiaľ čo /XRefStm v ukončovači tejto časti je miesto, kde je indexovaná väčšina dokumentu. Detektor, ktorý vráti „klasický“ pri prvom zistení xref, nikdy nenačíta /XRefStm a každý objekt, ktorý existuje iba v toku, zostane neviditeľný
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;
S detektorom predčasného ukončenia vyzerá načítanie v poriadku a až pri opätovnom ukladaní sa chýbajúce objekty ohlásia. Riešením nie je prečítať na začiatku viac bajtov; je ním rozpoznať hybridný ukončovač a sledovať /XRefStm predtým, než usúdite, že je súbor dokončený
Poradie zlučovania nie je možné meniť
Akonáhle sú načítané oba indexy, dajú sa zlúčiť len v jednom smere. Tok krížových odkazov sa musí zlúčiť ako prvý a klasické položky sa doplnia okolo neho. Dôvodom je drobný klam v srdci tohto formátu. Hybridný súbor označuje svoje komprimované objekty v klasickej tabuľke ako voľné, takže ich staré čítačky ignorujú. Zavádzač, ktorý uplatňuje pravidlo „prvý videný vyhráva“ a najprv načíta klasickú tabuľku, zaznamená tieto čísla objektov ako voľné, a potom zahodí položky toku, ktoré ich skutočne lokalizujú, pretože tieto pozície sú už obsadené. Opačné poradie zaručuje, že položky typu 2 z toku, každá predstavuje číslo toku objektov plus index, získajú miesta, ktoré im majú patriť, a klasické položky sa usadia okolo nich
Rovnaká disciplína chráni pred tým, aby staršia revízia oživila vymazaný objekt. Inkrementálne aktualizácie sa reťazia dozadu prostredníctvom /Prev a voľná položka typu 0 slúži ako strážca, ktorý signalizuje, že novšia časť stiahla číslo objektu. Neskoršej, staršej časti v reťazci nesmie byť povolené prepísať tohto strážcu zastaranou lokalitou. Ak za smerodajné považujeme prvé videné záznamy pre voľné značky, zmazaný objekt zostane zmazaný; ak sa s ním zaobchádza neopatrne, vlastná história súboru znovu oživí obsah, ktorý najnovšia revízia odstránila
Čo to znamená v HotPDF
Samotný engine vyrieši súbory s hybridným odkazom za vás a urobí to pri každej ceste, ktorá musí analyzovať údaje krížových odkazov. Načítajte dokument pomocou LoadFromFile alebo LoadFromStream, vykonajte zmeny a zavolajte SaveLoadedDocument; alebo spustite jednorazovú operáciu, ako je EncryptFile, ktorá načíta vstup a zapíše výstup. V každom prípade obnova načíta /XRefStm, zlúči sekciu toku pred klasickými položkami a vyrieši objekty uložené v toku predtým, než ich zápis vylistuje. Cesta 256-bitového šifrovania AES je miestom, kde sa problém prejavil po prvýkrát, pretože zašifrovanie dokumentu prepisuje každý objekt, a preto si vyžaduje, aby už bol každý objekt lokalizovaný
// 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]);
Detail, ktorý stojí za zapamätanie, sa nachádza nad úrovňou API. Súbory z programov Word, Excel, PowerPoint a z dlhého zoznamu procesov „Uložiť ako PDF“ sú bežne hybridné, takže zavádzač, ktorý testujete iba voči výstupu vlastného generátora, sa s nimi pri testovaní nemusí nikdy stretnúť. Zaraďte do svojich testovacích dát aj dokumenty exportované zo skutočných aplikácií Office, nielen súbory vyprodukované vaším vlastným kódom
Kontrola súboru, pri ktorom máte podozrenie
Dve prehliadky otázku rýchlo vyriešia. Otvorte súbor v hexadecimálnom prehliadači a prečítajte si bajty po poslednom startxref; hybridný súbor má krátku klasickú časť, ktorej adresár ukončovača obsahuje /XRefStm. Alebo porovnajte počet objektov, ktorý nahlási úplná analýza, s najvyšším číslom objektu, ktoré deklaruje /Size v ukončovači. Veľká medzera znamená, že objekty sa skrývajú v tokoch, ktoré zavádzač neotvoril, čo je ten istý nedostatok, ktorý sa neskôr zmení na zlyhanie pri ukladaní
Koniec typického exportu z Excelu robí prvú kontrolu konkrétnou. Všetko po poslednom kľúčovom slove xref je obyčajné ASCII, takže signatúra sa dá čítať priamo z hexadecimálneho zobrazenia (ilustračné ofsety, pridané poznámky)
xref
0 0 % empty classic subsection: no rows at all
trailer
<< /Size 216 % one past the highest object number in use
/Root 1 0 R
/Info 15 0 R
/ID [<5C9A...> <5C9A...>]
/XRefStm 87325 % byte offset of the cross-reference stream
>>
startxref
88710 % points at the classic section above
%%EOF
Podsekcia 0 0 je poznávacím znamením: klasická tabuľka s nula položkami existuje iba na to, aby niesla ukončovač a ukončovač existuje hlavne preto, aby uviedol /XRefStm 87325. Detektor, ktorý sa zastaví pri kľúčovom slove xref, doteraz videl index, ktorý neobsahuje vôbec nič. Keď by ste chceli túto kontrolu radšej skriptovať namiesto pozerania voľným okom, značka sa vždy nachádza v rámci posledných pár kilobajtov súboru, takže obmedzené spätné čítanie úplne postačuje
// Returns the /XRefStm offset from the file's tail, or -1 if the
// marker is absent (the file is not hybrid, or not a PDF at all)
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; // the trailer lives in the tail
if FS.Size < Len then
Len := Integer(FS.Size);
FS.Position := FS.Size - Len; // bounded backward read: 2 KB max
SetLength(Tail, Len);
FS.ReadBuffer(Tail[1], Len);
finally
FS.Free;
end;
P := Pos(AnsiString('/XRefStm'), Tail);
if P = 0 then
Exit; // no hybrid marker in the tail
Inc(P, Length('/XRefStm'));
while (P <= Len) and (Tail[P] in [' ', #9, #13, #10]) do
Inc(P); // skip whitespace after the key
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;
// Usage: a non-negative result names the byte where the stream starts
if FindXRefStm('Invoice_XLS.pdf') >= 0 then
Writeln('hybrid-reference file: resave will need the /XRefStm section');
Považujte túto sondu za triedenie, nie za analyzátor: povie vám len to, ktoré súbory v dávke si vyžadujú pozornosť pred spustením úlohy ukladania a nič viac. To, čo musí zavádzač s nájdeným ofsetom urobiť – sledovanie reťazca sekcií, zlučovanie položiek toku pred klasickými záznamami a rešpektovanie strážcov pre voľné záznamy – je krok za krokom popísané v našom sprievodnom článku o spracovaní PDF súborov s hybridným odkazom z aplikácií Office
Pohľad tvorcu na to, ako vôbec vznikajú toky objektov a komprimované krížové odkazy, je obsiahnutý v našom článku o tokoch objektov a inkrementálnych aktualizáciách. Keď je daný hybridný súbor zároveň veľmi veľký, techniky načítavania v návode Direct File API pre prácu s veľkými PDF vám ho umožnia preskúmať bez toho, aby ste ho celý čítali do pamäte. Obe techniky sa prirodzene dopĺňajú s obnovou, ktorá je tu popísaná, a sú dodávané ako súčasť nástroja HotPDF Component pre Delphi a C++Builder spolu s API pre načítanie, úpravu, šifrovanie a podpisovanie, ktoré sú preberané inde na tomto blogu