Eksporter et dokument fra Microsoft Word eller Excel med Lagre som PDF, og filen på disk er oftere enn ikke en hybrid-referansefil (hybrid-reference file). Den bærer sin kryssreferanseinformasjon to ganger: en gang som den klassiske tabellen med fast bredde (fixed-width) som avsluttet hver PDF opp til versjon 1.4, og en gang som en komprimert kryssreferansestrøm (cross-reference stream) som mesteparten av dokumentet faktisk avhenger av. En enkelt trailer-nøkkel, /XRefStm, syr de to visningene (views) sammen, og hvorvidt et verktøy ser hele dokumentet koker ned til om det følger (follows) den nøkkelen
Denne artikkelen ser på hybridfiler fra forbrukssiden (consuming side): hvordan bytene på slutten av filen ser ut, hvordan de to visningene (views) driver fra hverandre under redigering, og hvordan en Delphi-rørledning (pipeline) kan oppdage (detect) og rute hybridinndata (hybrid inputs). Hvordan en laster (loader) slår sammen (merges) visningene, og hvorfor rekkefølgen ikke er forhandlingsbar, er gjenstand for vår HotPDF-artikkel om innlasting av hybrid-referansefiler (hybrid-reference files); denne handler om å gjenkjenne oppsettet (layout) i første omgang
Hvorfor Office-eksporter skriver indeksen to ganger
PDF 1.5 introduserte to funksjoner (features) som endret formen (shape) på filen: kryssreferansestrømmer (cross-reference streams), som lagrer objektindeksen som komprimerte binære data i stedet for en rentekst-tabell (plaintext table), og objektstrømmer (object streams), som pakker (pack) mange små objekter inn i én Flate-komprimert beholder (container). En skribent (writer) som bruker dem produserer mindre filer, men en PDF 1.4-leser kan ikke åpne resultatet, fordi strukturene (structures) den støtter seg på (keys on), xref-nøkkelordet (keyword) og trailer-ordboken (dictionary), er borte
ISO 32000-1 §7.5.8.4 definerer kompromisset. En hybrid-referansefil (hybrid-reference file) skriver begge: en klassisk kryssreferansetabell (cross-reference table) som adresserer objektene en gammel leser (reader) må nå, katalogen (catalog) og sidetreet (page tree) blant dem, og en kryssreferansestrøm (cross-reference stream) som indekserer (indexes) alt annet. Objekter som er brettet (folded) inn i objektstrømmer (object streams) er markert (marked) som ledige (free) i den klassiske tabellen, slik at en 1.4-leser (reader) hopper over dem uten klage; deres sanne plasseringer finnes bare i strømmen. Den klassiske traileren (trailer) bærer deretter (carries) en /XRefStm-nøkkel som inneholder byte-forskyvningen (byte offset) til den strømmen. En gammel visningsprogram (viewer) leser aldri nøkkelen og gjengir (renders) filen fra tabellvisningen. En moderne visningsprogram følger den og ser hele dokumentet. Word og Excel har sendt ut (emitted) nøyaktig dette oppsettet i årevis, og det er derfor hybridfiler ikke er et eksotisk hjørnetilfelle, men en stor andel av det forretningsrørledninger (business pipelines) mottar
Slik ser halen (tail) av en hybridfil ut
Oppsettet (layout) er lettest å forstå fra bytene. Her er halen (tail) av en liten hybridfil, forskyvninger (offsets) forkortet; i en ekte Office-eksport er /XRefStm-verdien typisk en stor forskyvning nær slutten av filen. Leserekkefølgen (reading order) er hale-først-gjennomgangen (tail-first walk) beskrevet i vår oversikt over PDF-filstruktur (file structure): finn %%EOF, les startxref, hopp til tabellen
% ... kroppsobjekter (body objects), inkludert objektstrømmer (object streams) og, ved byte 116,
% kryssreferansestrømmen (cross-reference stream) (et strøm-objekt med /Type /XRef) ...
xref % klassisk seksjon (classic section): hva startxref peker på (points at)
0 4
0000000000 65535 f % spor (slot) 0: hode av ledigliste (free list), alltid til stede
0000000017 00000 n % objekt 1: katalogen (catalog), synlig for enhver leser
0000000000 65535 f % objekt 2: markert ledig (free) -- lever i en objektstrøm
0000000000 65535 f % objekt 3: det samme; bare strøm-visningen lokaliserer det
trailer
<<
/Size 4
/Root 1 0 R
/XRefStm 116 % byte-forskyvning (offset) av kryssreferansestrømmen
>>
startxref
7164 % byte-forskyvning av 'xref'-nøkkelordet over
%%EOF
To detaljer i dette dumpet (dump) bærer hele mekanismen. For det første peker (points) startxref på den klassiske seksjonen (classic section), med vilje: det er adressen en gammel leser (reader) må lande på. Kryssreferansestrømmen kan bare nås (reachable) gjennom /XRefStm-nøkkelen inne i trailer-ordboken (trailer dictionary), så en tolker (parser) som aldri ser etter den nøkkelen, lærer aldri at strømmen (stream) eksisterer. For det andre er objektene 2 og 3 løgner (lies) av en godartet (benign) art. Den klassiske tabellen erklærer dem ledige (free), men de er virkelige objekter som sitter inne i en komprimert (compressed) beholder (container); frimarkeringen (free marking) er det som hindrer en 1.4-leser fra å snuble over (tripping over) oppføringer (entries) den ikke kan bruke. En forbruker (consumer) som stoler på den klassiske visningen (classic view) alene, konkluderer med at mesteparten av dette dokumentet ikke eksisterer
Hvordan de to visningene (views) driver (drift) fra hverandre
En hybridfil (hybrid file) rett ut av Word er internt konsistent (internally consistent): begge visningene (views) beskriver det samme dokumentet, hver innenfor sitt erklærte omfang (scope). Trøbbelet (trouble) starter når filen redigeres (edited) av et verktøy (tool) som bare forstår én av visningene. Vurder et stemplingsverktøy (stamping utility) som legger til en klassisk-stil trinnvis (incremental) oppdatering (update): nye objekter, en ny xref-seksjon, en /Prev-kjede til forrige seksjon, og en ny trailer. Hvis den traileren fjerner (drops) /XRefStm-nøkkelen, blir strømvisningen foreldreløs (orphaned); hvis den kopierer den gamle verdien fremover (forward), beskriver strømvisningen fremdeles dokumentet slik det var før redigeringen (edit). Uansett er (are) de to indeksene nå uenige om hva filen (file) inneholder
Den resulterende (resulting) filen har en karakteristisk (distinctive) feilsignatur (failure signature): objekter synlige i én visning mangler eller er utdaterte (stale) i den andre. En leser (reader) som løser opp (resolves) gjennom strømvisningen finner før-redigeringsversjonen (pre-edit version) av et oppdatert objekt, eller ingen oppføring i det hele tatt for et tilføyd objekt (appended one). En leser på tabellvisningen ser redigeringen (edit), men mister oversikten over (loses track of) de komprimerte objektene som bare strømmen lokaliserer. I praksis overflater (surfaces) dette som skjemafelt (form fields) som overlever i ett visningsprogram (viewer) og forsvinner i et annet, kommentarer (annotations) som en stemplingspassering (stamping pass) ser ut til å ha slettet, eller oppslag (lookups) som lander på helt feil (wrong) objekt
Det som gjør disse filene dyre å feilsøke (debug) er at Adobe Acrobat vanligvis (usually) åpner dem uten klager (complaint): når indeksen er uenig med bytene, gjenoppbygger (rebuilds) den stille kryssreferansedataene (cross-reference data) ved å skanne etter (scanning for) overskrifter (headers), så den som produserte (produced) den ødelagte (broken) filen ser ingenting galt (wrong). Feilen (failure) kommer til overflaten (surfaces) senere, når filen når en streng (strict) forbruker (consumer), en preflight-validator (preflight validator), en signeringstjeneste (signing service), en arkiveringsjobb (archival ingest job), som stoler på den erklærte strukturen (declared structure) og rapporterer (reports) manglende objekter (missing objects) eller et avvik i kryssreferanser (cross-reference mismatch). "Den åpnes fint i Acrobat" er hvordan nesten hver billett (ticket) om desynkronisering (desynchronization) av hybrid (hybrid) begynner
Å oppdage (Detecting) en hybridfil (hybrid file) i ren (plain) Delphi
Klassifisering (Classifying) av inndata (inputs) krever ikke et PDF-bibliotek (PDF library). Nøkkelen /XRefStm kan bare forekomme (occur) inne i en klassisk trailer-ordbok (classic trailer dictionary), og den aktive traileren sitter innenfor de siste par kilobytene av filen, fordi (because) spesifikasjonen krever at %%EOF skal vises (appear) nær den fysiske (physical) slutten (end). Å lese et avgrenset (bounded) halevindu (tail window) og søke i det er (is) nok for triagering (triage):
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;
// Hvert involvert nøkkelord er 7-bits ASCII, så en bytewise-dekoding er trygg
Tail := TEncoding.ANSI.GetString(Buf);
// Finn det SISTE 'trailer'-nøkkelordet: med inkrementelle (incremental) oppdateringer (updates),
// er den nyeste (newest) traileren (trailer) den som styrer (governs) filen (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; // ingen klassisk trailer (classic trailer): en ren (pure) xref-stream (strøm) fil (file), ikke hybrid
// En hybrid (hybrid) trailer bærer (carries) /XRefStm mellom 'trailer' og 'startxref'
KeyPos := PosEx('/XRefStm', Tail, TrailerPos);
StartXrefPos := PosEx('startxref', Tail, TrailerPos);
Result := (KeyPos > 0) and
((StartXrefPos = 0) or (KeyPos < StartXrefPos));
end;
De tre utfallene (outcomes) stemmer (line up) med de tre layoutene (layouts). En kun-klassisk-fil (classic-only file) har en trailer (trailer), men ingen /XRefStm: False. En fil som satser fullt ut (commits fully) på kryssreferansestrømmer (cross-reference streams) har ikke trailer-nøkkelordet i det hele tatt, dens trailernøkler (trailer keys) lever i strømordboken (stream dictionary): også False, noe som er riktig, fordi en slik fil er komprimert, ikke hybrid. Bare oppsettet med to indekser (double-indexed layout) returnerer True
For produksjonsbruk er to herdinger (hardenings) verdt de ekstra linjene (extra lines). Tolk (Parse) heltallet (integer) etter /XRefStm, søk (seek) til den offsetten (offset), og bekreft (confirm) at et stream-objekt (strøm-objekt) med /Type /XRef faktisk sitter der; en avkortet fil (truncated file) kan bære nøkkelen mens strømmen (stream) er borte (gone), noe som (which) hører hjemme (belongs) i en annen bøtte (different bucket) enn en sunn hybrid (healthy hybrid). Og (And) behandle vindustørrelsen som en parameter (parameter): 2 KB dekker (covers) vanlig (ordinary) Office-utdata (output), men en uvanlig (unusually) stor trailer-ordbok (trailer dictionary) kan presse (push) nøkkelordet ut av rekkevidde (out of range), og (and) å utvide vinduet slår (beats) å erklære filen klassisk (classic) ved et uhell (accident)
Ruting (Routing) av hybridfiler gjennom en Delphi-pipeline (rørledning)
Gjenkjenning (Detection) kjøper (buys) deg en ruting-beslutning (routing decision). For filer som (that) bare (only) skal leses (read), rendres (rendered), eller valideres (validated), bruk (use) en loader (laster) som løser opp (resolves) begge (both) visningene (views), og bekreft (verify) deretter oppførsel (behavior) fremfor (rather than) bytes (byter). PDFium-komponenten parser (tolker) /XRefStm-kjeden (chain) under innlasting (load), så (so) tabellen (table) koden din ser (sees) er den sammenslåtte (merged), og sjekkene beskrevet (described) i vår artikkel om validering av objekt- og kryssreferansestrømmer (validating object and cross-reference streams) gjelder uendret (unchanged). Hvis en desynkronisert (desynchronized) hybrid (hybrid) er skadet (damaged) så ille (badly) at den nekter (refuse) lasting (loading), rapporterer (reports) motoren (engine) det gjennom sitt feilsett (error set), FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PASSWORD, FPDF_ERR_SECURITY og (and) FPDF_ERR_PAGE, med FPDF_ERR_FORMAT den ene (the one) strukturelle (structural) skaden (damage) produserer (produces). Ikke len deg på (lean on) det signalet, skjønt: PDFium er mildt (lenient) av (by) design og gjenoppbygger (rebuilds) de fleste inkonsekvente (inconsistent) filer (files) stille (silently), så (so) en vellykket innlasting (successful load) beviser (proves) at filen (file) var gjenopprettelig (recoverable), ikke at de to visningene (views) stemmer overens (agree). Den meningsfulle (meaningful) konsistenssjekken (consistency check) er (is) å sammenligne hva en full (full) objektgjennomgang (object walk) finner mot (against) hva (what) trailerens (trailer's) /Size erklærer (declares)
For filer (files) som (that) rørledningen (pipeline) din endrer (modifies), er den tryggeste policyen (policy) å (to) forhindre at de (them) forblir (being) hybride (hybrid) i (at) det hele tatt (all). En innlasting (load) etterfulgt (followed) av (by) en full lagring (full save) gjennom HotPDF skriver (rewrites) dokumentet på nytt med en enkelt (single), selvkonsistent (self-consistent) kryssreferanse (cross-reference) i (in) én (one) form: ingen (no) /XRefStm, ingen andre visning (second view) som (to) kan falle (fall) ut av (out of) synkronisering (sync), hvert (every) objekt eid av nøyaktig (exactly) én indeks-oppføring (index entry). Den (That) normaliseringen (normalization) er det (what) du vil ha (want) før (before) arkivering (archival ingest), før en streng (strict) nedstrøms (downstream) RIP eller signeringstjeneste (signing service), og (and) etter enhver redigering (edit) brukt (applied) på en hybrid input (inndata). Det (It) fungerer (works) fordi (because) lasteren (loader) slo sammen (merged) visningene riktig på (on) veien (the way) inn (in), mekanismen (the mechanism) som HotPDF hybrid-referanseartikkelen går gjennom (walks through) i (in) detalj (detail)
Den ene klassen (class) av filer man skal (to) la (leave) være (alone) er (is) digitalt (digitally) signerte (signed) dokumenter (documents). En full omskriving (rewrite) flytter (moves) hver byte (byte), noe (which) ugyldiggjør (invalidates) enhver (any) signatur (signature) beregnet (computed) over (over) de opprinnelige områdene (original ranges). En (A) endring (change) på en signert (signed) hybrid (hybrid) må gå (go) inn (in) som (as) en riktig (proper) inkrementell oppdatering (incremental update) som (that) opprettholder (maintains) begge (both) visningene (views); en fil som (that) bare (only) trenger å leses (reading) bør (should) passere (pass) gjennom uberørt (untouched). Normalisering (Normalization) er (is) for filer (files) du (you) eier (own); signerte filer legger du (you) bare (only ever) til i (append to)
Hybrid-referanse (Hybrid-reference) PDF-er er ikke feilformaterte (malformed); de er (they are) formatets (format's) egne (own) kompatibilitetsbro (compatibility bridge), og (and) Office-applikasjoner (Office applications) vil (will) fortsette (keep) å produsere dem (producing them) så (as) lenge (long) som (as) PDF 1.4-lesere (readers) overlever (survive) i (in) installasjonsbasen (install base). En rørledning (pipeline) som (that) kan (can) oppdage (spot) /XRefStm-nøkkelen (key), validere (validate) det sammenslåtte dokumentet (merged document) med PDFium Component, og generere (regenerate) rene (clean) enkeltindeks-utdata (single-index output) med (with) HotPDF Component behandler (treats) dem (them) som (as) det (what) de (they) er (are): ordinære (ordinary) inndata (inputs) med (with) ett (one) ekstra (extra) veiskilt (signpost) i (in) traileren (trailer)