Műszaki cikk

Hibrid hivatkozású PDF-ek kezelése Office alkalmazásokból Delphiben

Exportáljon egy dokumentumot a Microsoft Wordből vagy Excelből "Mentés PDF-ként" funkcióval, és a lemezen lévő fájl legtöbbször egy hibrid hivatkozású fájl. Kereszthivatkozási információit kétszer is hordozza: egyszer a klasszikus, rögzített szélességű táblázatként, amellyel minden PDF végződött az 1.4-es verzióig, és egyszer tömörített kereszthivatkozási adatfolyamként, amelytől a dokumentum nagy része valójában függ. Egyetlen trailer kulcs, az /XRefStm köti össze a két nézetet, és az, hogy egy eszköz a teljes dokumentumot látja-e, attól függ, hogy követi-e azt a kulcsot

Ez a cikk a hibrid fájlokat a feldolgozó oldaláról vizsgálja meg: hogyan néznek ki a fájl végén lévő bájtok, hogyan sodródik szét a két nézet a szerkesztés során, és hogyan észlelheti és irányíthatja a Delphi-folyamat a hibrid bemeneteket. Az, hogy egy betöltő hogyan egyesíti a nézeteket, és miért nem alkuképes a sorrend, a témája a hibrid hivatkozású fájlok betöltéséről szóló HotPDF cikkünknek; ez a cikk elsősorban az elrendezés felismeréséről szól

Miért írja az Office exportálás kétszer az indexet?

A PDF 1.5 két olyan funkciót vezetett be, amelyek megváltoztatták a fájl alakját: a kereszthivatkozási adatfolyamokat, amelyek az objektumindexet tömörített bináris adatként tárolják, nem pedig egyszerű szöveges táblázatként, és az objektum-adatfolyamokat, amelyek sok apró objektumot egyetlen Flate-tömörített tárolóba csomagolnak. Egy ezeket használó író kisebb fájlokat hoz létre, de egy PDF 1.4 olvasó nem tudja megnyitni az eredményt, mert a struktúrák, amelyekre támaszkodik, az xref kulcsszó és a trailer szótár eltűntek

Az ISO 32000-1 §7.5.8.4 határozza meg a kompromisszumot. Egy hibrid hivatkozású fájl mindkettőt tartalmazza: egy klasszikus kereszthivatkozási táblát, amely azokat az objektumokat címezi, amelyeket egy régi olvasónak el kell érnie, beleértve a katalógust és az oldalfát is, valamint egy kereszthivatkozási adatfolyamot, amely minden mást indexel. Az objektum-adatfolyamokba hajtogatott objektumokat szabadnak jelöli a klasszikus tábla, így egy 1.4-es olvasó panasz nélkül átugorja őket; valódi helyeik csak az adatfolyam nézetben léteznek. A klasszikus trailer ezután hordoz egy /XRefStm kulcsot, amely az adott adatfolyam bájteltolását (byte offset) tartalmazza. Egy régi megjelenítő soha nem olvassa a kulcsot, és a táblázat nézetből jeleníti meg a fájlt. Egy modern megjelenítő követi, és a teljes dokumentumot látja. A Word és az Excel évek óta pontosan ezt az elrendezést bocsátja ki, ezért a hibrid fájlok nem egzotikus sarokesetek, hanem a vállalati folyamatok által fogadott adatok nagy részét teszik ki

Hogyan néz ki egy hibrid fájl vége

Az elrendezést a bájtok alapján a legkönnyebb megérteni. Itt van egy kis hibrid fájl vége, rövidített eltolásokkal; egy valódi Office exportban az /XRefStm érték jellemzően egy nagy eltolás a fájl végéhez közel. Az olvasási sorrend a hátulról-előre haladó bejárás, amelyet a PDF fájlszerkezetről szóló áttekintésünkben ismertetünk: keresd meg az %%EOF-ot, olvasd el a startxref-et, ugorj a táblázathoz

% ... törzs objektumok, beleértve az objektum stream-eket és a 116. bájtnál,
% a kereszthivatkozás stream-et (egy stream objektum /Type /XRef-fel) ...

xref                    % klasszikus rész: amire a startxref mutat
0 4
0000000000 65535 f      % 0. hely: a szabad lista feje, mindig jelen van
0000000017 00000 n      % 1. objektum: a katalógus, minden olvasó számára látható
0000000000 65535 f      % 2. objektum: szabadként jelölve -- egy objektum streamben él
0000000000 65535 f      % 3. objektum: ugyanez; csak a stream nézet találja meg
trailer
<<
  /Size 4
  /Root 1 0 R
  /XRefStm 116          % a kereszthivatkozás stream bájteltolása
>>
startxref
7164                    % a fenti 'xref' kulcsszó bájteltolása
%%EOF

Két részlet ebben a kiíratásban hordozza az egész mechanizmust. Először is, a startxref a klasszikus részre mutat, szándékosan: ez az a cím, ahová egy régi olvasónak landolnia kell. A kereszthivatkozási adatfolyam csak a trailer szótáron belüli /XRefStm kulcson keresztül érhető el, így egy értelmező, amely soha nem keresi ezt a kulcsot, soha nem tudja meg, hogy az adatfolyam létezik. Másodszor, a 2. és a 3. objektum jóindulatú hazugságok. A klasszikus tábla szabadnak nyilvánítja őket, de valójában egy tömörített tárolóban ülő valódi objektumok; a szabad jelölés az, ami megakadályozza, hogy egy 1.4-es olvasó belebotoljon olyan bejegyzésekbe, amelyeket nem tud használni. Egy fogyasztó, aki csak a klasszikus nézetben bízik, arra a következtetésre jut, hogy a dokumentum nagy része nem létezik

Hogyan sodródik szét a két nézet

A Wordből frissen kikerülő hibrid fájl belsőleg konzisztens: mindkét nézet ugyanazt a dokumentumot írja le, mindkettő a bejelentett hatókörén belül. A probléma akkor kezdődik, amikor a fájlt egy olyan eszközzel szerkesztik, amely csak az egyik nézetet érti meg. Tekintsünk egy bélyegző segédprogramot, amely hozzáfűz egy klasszikus stílusú növekményes frissítést: új objektumokat, egy új xref szakaszt, egy /Prev láncot az előző szakaszhoz és egy új trailert. Ha ez a trailer eldobja az /XRefStm kulcsot, a stream nézet elárvul; ha előremásolja a régi értéket, a stream nézet még mindig úgy írja le a dokumentumot, ahogyan az a szerkesztés előtt volt. Akárhogy is, a két index mostantól nem ért egyet abban, hogy mit tartalmaz a fájl

Az eredményül kapott fájlnak egy jellegzetes hibaaláírása van: az egyik nézetben látható objektumok hiányoznak vagy elavultak a másikban. Egy olvasó, amely a stream nézeten keresztül old fel, egy frissített objektum szerkesztés előtti verzióját találja, vagy egyáltalán nincs bejegyzés egy hozzáfűzött objektumhoz. A táblázat nézeten lévő olvasó látja a szerkesztést, de elveszíti a tömörített objektumokat, amelyeket csak a stream tud lokalizálni. A gyakorlatban ez úgy jelentkezik, mint olyan űrlapmezők, amelyek az egyik megjelenítőben túlélik, a másikban pedig eltűnnek, olyan annotációk, amelyeket a bélyegzés látszólag törölt, vagy olyan keresések, amelyek teljesen rossz objektumon landolnak

Ezeket a fájlokat azért költséges hibakeresésnek alávetni, mert az Adobe Acrobat általában panasz nélkül megnyitja őket: amikor az index nem egyezik a bájtokkal, csendben újraépíti a kereszthivatkozási adatokat az objektumfejlécek beolvasásával, így aki a hibás fájlt készítette, nem lát semmi rosszat. A hiba később felszínre kerül, amikor a fájl elér egy szigorú fogyasztót, egy előellenőrző (preflight) validátort, egy aláíró szolgáltatást, egy archiválási feldolgozási feladatot, amely bízik a bejelentett struktúrában, és hiányzó objektumokat vagy kereszthivatkozási eltérést jelent. Szinte minden hibrid deszinkronizációs hibajegy úgy kezdődik: "Acrobatban hibátlanul megnyílik"

Hibrid fájl észlelése tiszta Delphiben

A bemenetek osztályozásához nem szükséges PDF könyvtár. Az /XRefStm kulcs csak a klasszikus trailer szótáron belül fordulhat elő, és az aktív trailer a fájl utolsó pár kilobájtján belül helyezkedik el, mert a specifikáció megköveteli, hogy az %%EOF a fizikai végéhez közel jelenjen meg. Egy korlátozott végső ablak elolvasása és keresése elegendő az osztályozáshoz:

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;

  // Minden érintett kulcsszó 7 bites ASCII, így a bájtonkénti dekódolás biztonságos
  Tail := TEncoding.ANSI.GetString(Buf);

  // Keresd meg az UTOLSÓ 'trailer' kulcsszót: növekményes frissítéseknél,
  // a legújabb trailer az, amelyik irányítja a fájlt
  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;  // nincs klasszikus trailer: egy tiszta xref-stream fájl, nem hibrid

  // Egy hibrid trailer az /XRefStm-et a 'trailer' és a 'startxref' között hordozza
  KeyPos := PosEx('/XRefStm', Tail, TrailerPos);
  StartXrefPos := PosEx('startxref', Tail, TrailerPos);
  Result := (KeyPos > 0) and
    ((StartXrefPos = 0) or (KeyPos < StartXrefPos));
end;

A három kimenetel egybeesik a három elrendezéssel. Egy csak klasszikus fájlban van trailer, de nincs /XRefStm: Hamis. Egy olyan fájl, amely teljes mértékben elkötelezi magát a kereszthivatkozási streamek mellett, egyáltalán nem tartalmaz trailer kulcsszót, a trailer kulcsai a stream szótárban élnek: szintén Hamis, helyesen, mert egy ilyen fájl tömörített, nem hibrid. Csak a dupla indexes elrendezés tér vissza Igaz értékkel

A termelési felhasználáshoz két megerősítés megéri az extra sorokat. Értelmezze az /XRefStm utáni egész számot, keressen arra az eltolásra, és ellenőrizze, hogy valóban ott ül-e egy stream objektum /Type /XRef attribútummal; egy csonkított fájl hordozhatja a kulcsot, miközben az adatfolyam eltűnt, ami egy másik vödörbe tartozik, mint egy egészséges hibrid. És kezelje az ablak méretét paraméterként: a 2 KB lefedi a szokásos Office kimenetet, de egy szokatlanul nagy trailer szótár kitolhatja a kulcsszót a hatótávolságból, és az ablak kiszélesítése jobb, mint véletlenül klasszikusnak nyilvánítani a fájlt

Hibrid fájlok irányítása egy Delphi folyamaton keresztül

Az észlelés egy útválasztási döntést ad. Azokhoz a fájlokhoz, amelyeket csak olvasnak, megjelenítenek vagy érvényesítenek, használjon olyan betöltőt, amely mindkét nézetet feloldja, majd ellenőrizze a viselkedést a bájtok helyett. A PDFium Komponens beolvasás közben elemzi az /XRefStm láncot, így az objektumtábla, amelyet a kódja lát, az összevont, és az objektum és kereszthivatkozási streamek validálásáról szóló cikkünkben leírt ellenőrzések változatlanul érvényesek. Ha egy deszinkronizált hibrid annyira megsérül, hogy megtagadja a betöltést, a motor ezt a hibahalmazán keresztül jelzi, FPDF_ERR_SUCCESS, FPDF_ERR_UNKNOWN, FPDF_ERR_FILE, FPDF_ERR_FORMAT, FPDF_ERR_PASSWORD, FPDF_ERR_SECURITY és FPDF_ERR_PAGE, ahol a szerkezeti sérülés az FPDF_ERR_FORMAT hibát eredményezi. Ne támaszkodjon azonban erre a jelre: a PDFium kialakításánál fogva engedékeny, és a legtöbb inkonzisztens fájlt csendben újraépíti, tehát egy sikeres betöltés azt bizonyítja, hogy a fájl helyreállítható volt, nem pedig azt, hogy a két nézete megegyezik. Az értelmes konzisztencia-ellenőrzés az, ha összehasonlítjuk, hogy mit talál egy teljes objektumbejárás azzal, amit a trailer /Size deklarál

Azoknál a fájloknál, amelyeket a folyamata módosít, a legbiztonságosabb irányelv megakadályozni, hogy egyáltalán hibridek legyenek. Egy betöltés, amelyet a HotPDF-en keresztül történő teljes mentés követ, újraírja a dokumentumot egyetlen, önkonzisztens kereszthivatkozással, egy formában: nincs /XRefStm, nincs második nézet, amely kiesne a szinkronból, minden objektumot pontosan egy indexbejegyzés birtokol. Erre a normalizálásra van szükség az archiválási feldolgozás előtt, egy szigorú alsóbb szintű RIP vagy aláíró szolgáltatás előtt, és a hibrid bemenetre alkalmazott bármilyen szerkesztés után. Ez azért működik, mert a betöltő a befelé vezető úton helyesen egyesítette a nézeteket, azt a mechanizmust, amelyet a HotPDF hibrid hivatkozás cikk részletesen végigvesz

Az egyetlen fájlosztály, amelyet békén kell hagyni, a digitálisan aláírt dokumentumok. Egy teljes újraírás minden bájtot elmozdít, ami érvénytelenít minden, az eredeti tartományokra kiszámított aláírást. Az aláírt hibrid módosításának megfelelő növekményes frissítésként kell bekerülnie, amely mindkét nézetet fenntartja; annak a fájlnak, amelyet csak olvasni kell, érintetlenül kell áthaladnia. A normalizálás az Ön tulajdonában lévő fájlokra vonatkozik; aláírt fájlokat, amelyekhez csak hozzáfűz

A hibrid hivatkozású PDF-ek nem rosszul formázottak; ők a formátum saját kompatibilitási hídja, és az Office alkalmazások addig fogják gyártani őket, amíg a PDF 1.4 olvasók túlélik a telepítési bázisban. Egy folyamat, amely képes észrevenni az /XRefStm kulcsot, érvényesíteni az összevont dokumentumot a PDFium Komponenssel, és tiszta egyindexes kimenetet regenerálni a HotPDF Komponenssel, úgy kezeli őket, amik: közönséges bemenetek, egy extra útbaigazító táblával a trailerben