Exportați un document din Microsoft Word sau Excel cu Salvare ca PDF și fișierul de pe disc este, de cele mai multe ori, un fișier cu referințe hibride. Acesta poartă informațiile de referință încrucișată de două ori: o dată ca tabelul clasic cu lățime fixă care se găsea la sfârșitul oricărui PDF până la versiunea 1.4, și o dată ca un flux de referințe încrucișate comprimat de care depinde de fapt majoritatea documentului. O singură cheie de trailer, /XRefStm, unește cele două vizualizări, iar dacă un instrument vede întregul document se reduce la a urmări sau nu acea cheie
Acest articol analizează fișierele hibride din perspectiva consumatorului: cum arată octeții de la sfârșitul fișierului, cum cele două vizualizări se distanțează sub editare, și cum un flux de lucru Delphi poate detecta și direcționa intrările hibride. Modul în care un încărcător îmbină vizualizările și de ce ordinea nu este negociabilă, este subiectul articolului nostru HotPDF despre încărcarea fișierelor cu referințe hibride; acesta se referă în primul rând la recunoașterea aspectului
De ce exporturile Office scriu indexul de două ori
PDF 1.5 a introdus două caracteristici care au schimbat forma fișierului: fluxuri de referințe încrucișate, care stochează indexul obiectului ca date binare comprimate în loc de un tabel text clar, și fluxuri de obiecte, care ambalează multe obiecte mici într-un singur container comprimat prin Flate. Un program care le folosește la scriere produce fișiere mai mici, dar un cititor PDF 1.4 nu poate deschide rezultatul, deoarece structurile pe care se bazează, cuvântul cheie xref și dicționarul trailer, au dispărut
ISO 32000-1 §7.5.8.4 definește compromisul. Un fișier cu referințe hibride le scrie pe ambele: un tabel clasic de referințe încrucișate adresând obiectele la care trebuie să ajungă un cititor vechi, catalogul și arborele paginii printre ele, și un flux de referințe încrucișate care indexează orice altceva. Obiectele împăturite în fluxuri de obiecte sunt marcate ca libere în tabelul clasic, astfel încât un cititor 1.4 le sare fără plângeri; locațiile lor reale există doar în flux. Trailer-ul clasic poartă apoi o cheie /XRefStm care conține decalajul de octeți (byte offset) al acelui flux. Un vizualizator vechi nu citește niciodată cheia și randează fișierul din vizualizarea tabelului. Un vizualizator modern o urmărește și vede documentul complet. Word și Excel emit exact acest aspect de ani de zile, motiv pentru care fișierele hibride nu sunt un caz exotic de margine, ci o mare parte din ceea ce primesc fluxurile de lucru de afaceri
Cum arată sfârșitul unui fișier hibrid
Aspectul este cel mai ușor de înțeles din octeți. Iată sfârșitul unui mic fișier hibrid, decalajele scurtate; într-un export real de la Office valoarea /XRefStm este de obicei un mare decalaj aproape de capătul fișierului. Ordinea de citire este plimbarea coadă-prima descrisă în prezentarea noastră generală a structurii fișierelor PDF: găsiți %%EOF, citiți startxref, săriți la tabel
% ... 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
Două detalii în acest dump poartă întregul mecanism. În primul rând, startxref indică secțiunea clasică, intenționat: aceasta este adresa la care trebuie să aterizeze un cititor vechi. Fluxul de referințe încrucișate poate fi accesat doar prin cheia /XRefStm din dicționarul trailer-ului, astfel încât un parser care nu caută niciodată acea cheie nu află niciodată că fluxul există. În al doilea rând, obiectele 2 și 3 sunt minciuni de un tip benign. Tabelul clasic le declară libere, dar sunt obiecte reale aflate într-un container comprimat; marcajul liber este ceea ce împiedică un cititor 1.4 să se poticnească de intrări pe care nu le poate folosi. Un consumator care are încredere doar în vizualizarea clasică concluzionează că majoritatea acestui document nu există
Cum se distanțează cele două vizualizări
Un fișier hibrid recent ieșit din Word este consecvent din punct de vedere intern: ambele vizualizări descriu același document, fiecare în scopul declarat. Problema începe când fișierul este editat de un instrument care înțelege doar una dintre vizualizări. Luați în considerare un utilitar de ștampilare care adaugă o actualizare incrementală în stil clasic: obiecte noi, o nouă secțiune xref, un lanț /Prev către secțiunea anterioară și un nou trailer. Dacă acel trailer abandonează cheia /XRefStm, vizualizarea fluxului este orfană; dacă copiază valoarea veche mai departe, vizualizarea fluxului descrie în continuare documentul așa cum era înainte de editare. Oricum ar fi, cei doi indecși nu mai sunt de acord cu privire la ceea ce conține fișierul
Fișierul rezultat are o semnătură distinctivă de eșec: obiectele vizibile într-o vizualizare lipsesc sau sunt învechite în cealaltă. Un cititor care rezolvă prin vizualizarea fluxului găsește versiunea pre-editată a unui obiect actualizat, sau nicio intrare pentru unul adăugat. Un cititor pe vizualizarea tabelului vede editarea, dar pierde evidența obiectelor comprimate pe care doar fluxul le localizează. În practică, aceasta iese la suprafață ca câmpuri de formular care supraviețuiesc într-un vizualizator și dispar în altul, adnotări pe care o trecere de ștampilare pare să le fi șters, sau căutări care aterizează pe cu totul alt obiect
Ceea ce face ca depanarea acestor fișiere să fie costisitoare este că Adobe Acrobat le deschide de obicei fără plângeri: atunci când indexul nu este de acord cu octeții, reconstruiește în liniște datele de referințe încrucișate scanând pentru antetele obiectelor, astfel încât oricine a produs fișierul rupt nu vede nimic în neregulă. Eșecul apare mai târziu, când fișierul ajunge la un consumator strict, un validator preflight, un serviciu de semnare, o lucrare de stocare de arhivă, care are încredere în structura declarată și raportează obiecte lipsă sau o nepotrivire a referințelor încrucișate. "Se deschide bine în Acrobat" este modul în care începe aproape fiecare tichet de desincronizare hibridă
Detectarea unui fișier hibrid în Delphi pur
Clasificarea intrărilor nu necesită o bibliotecă PDF. Cheia /XRefStm poate apărea doar în interiorul unui dicționar clasic de trailer, iar trailer-ul activ se află în ultimii câțiva kiloocteți ai fișierului, deoarece specificația cere ca %%EOF să apară aproape de capătul fizic. Citirea unei ferestre limitate la final și căutarea în ea este suficientă pentru triaj:
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;
Cele trei rezultate se aliniază cu cele trei aspecte. Un fișier pur clasic are un trailer, dar nu are /XRefStm: False. Un fișier care se bazează în întregime pe fluxurile de referințe încrucișate nu are deloc cuvântul cheie trailer, cheile sale de trailer trăiesc în dicționarul fluxului: de asemenea False, corect, deoarece un astfel de fișier este comprimat, nu hibrid. Numai aspectul dublu-indexat returnează True
Pentru utilizare în producție, două întăriri merită liniile suplimentare. Analizați întregul după /XRefStm, săriți la acel decalaj și confirmați că un obiect de flux cu /Type /XRef se află efectiv acolo; un fișier trunchiat poate purta cheia în timp ce fluxul a dispărut, ceea ce aparține unei alte categorii decât un hibrid sănătos. Și tratați dimensiunea ferestrei ca un parametru: 2 KB acoperă ieșirea obișnuită Office, dar un dicționar de trailer neobișnuit de mare poate împinge cuvântul cheie în afara intervalului, iar lărgirea ferestrei este mai bună decât a declara accidental fișierul ca fiind clasic
Direcționarea fișierelor hibride printr-un flux de lucru Delphi
Detectarea vă cumpără o decizie de rutare. Pentru fișierele care sunt doar citite, randate sau validate, utilizați un încărcător care rezolvă ambele vizualizări, apoi verificați comportamentul mai degrabă decât octeții. Componenta PDFium analizează lanțul /XRefStm în timpul încărcării, astfel încât tabelul de obiecte pe care îl vede codul dvs. este cel îmbinat, iar verificările descrise în articolul nostru despre validarea fluxurilor de obiecte și referințe încrucișate se aplică neschimbate. Dacă un hibrid desincronizat este grav deteriorat încât refuză încărcarea, motorul o raportează prin setul său de erori, FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PASSWORD, FPDF_ERR_SECURITY și FPDF_ERR_PAGE, având FPDF_ERR_FORMAT ca cel pe care îl produce deteriorarea structurală. Nu vă bazați pe acel semnal, totuși: PDFium este indulgent prin design și reconstruiește în liniște majoritatea fișierelor inconsistente, astfel încât o încărcare cu succes dovedește că fișierul a putut fi recuperat, nu că cele două vizualizări ale sale sunt de acord. Verificarea consecvenței semnificative este compararea a ceea ce găsește o parcurgere completă a obiectelor cu ceea ce declară atributul /Size din trailer
Pentru fișierele pe care fluxul dvs. le modifică, cea mai sigură politică este să opriți să mai fie deloc hibride. O încărcare urmată de o salvare completă prin HotPDF rescrie documentul cu o singură referință încrucișată, consecventă cu sine, într-o singură formă: fără /XRefStm, fără a doua vizualizare care să piardă sincronizarea, fiecare obiect deținut de exact o singură intrare în index. Această normalizare este ceea ce doriți înainte de introducerea în arhivă, înaintea unui RIP strict sau a unui serviciu de semnare în aval și după orice editare aplicată unei intrări hibride. Funcționează deoarece încărcătorul a îmbinat corect vizualizările la intrare, mecanismul pe care articolul cu referințe hibride HotPDF îl detaliază pas cu pas
Singura clasă de fișiere care trebuie lăsată în pace sunt documentele semnate digital. O rescriere completă mută fiecare octet, ceea ce invalidează orice semnătură calculată pe intervalele originale. O modificare a unui hibrid semnat trebuie să intre ca o actualizare incrementală adecvată care menține ambele vizualizări; un fișier care necesită doar citire ar trebui să treacă neatins. Normalizarea este pentru fișierele pe care le dețineți; la fișierele semnate adăugați întotdeauna doar la capăt
PDF-urile cu referințe hibride nu sunt malformate; ele sunt puntea de compatibilitate proprie a formatului, iar aplicațiile Office vor continua să le producă atâta timp cât cititoarele PDF 1.4 supraviețuiesc în baza de instalări. Un flux care poate observa cheia /XRefStm, valida documentul îmbinat cu Componenta PDFium, și regenera o ieșire cu un singur index curat folosind Componenta HotPDF le tratează ca ceea ce sunt: intrări obișnuite cu un indicator suplimentar în trailer