Műszaki cikk

Hibrid hivatkozású PDF-ek betöltése Wordből és Excelből Delphiben

Nyisson meg egy PDF-et, amelyet a Microsoft Word vagy Excel állított elő, lapozzon végig rajta, és semmi sem tűnik szokatlannak. Töltse be egy Delphi programba, olvassa vissza az oldalszámot, és a szám helyes. Ezután mentse el újra bekapcsolt titkosítással, és a feladat meghiúsul egy EListError hibával, vagy a kimenet egy sérült kereszthivatkozás figyelmeztetéssel nyílik meg. A fájl sosem volt sérült. Ez egy hibrid hivatkozású fájl, és pont az a szerkezet, amely lehetővé teszi, hogy egy tizenöt éves megjelenítő megnyissa, az a szerkezet, amely legyőzi azt a betöltőt, amely túl korán hagyja abba az olvasást

Ez az egyik leggyakoribb módja annak, hogy egy PDF-folyamat, amely minden belső teszten átment, olyan fájllal találkozzon, amelyet nem tud oda-vissza átvinni. A bemeneteket mind házon belül generálták, így soha nem voltak hibridek. Az első hibrid fájl azon a napon érkezik, amikor az ügyfél továbbít egy táblázatból exportált számlát

Mit is ír valójában a Word és az Excel

Az ISO 32000-1 a 7.5.8.4 szakaszban írja le a hibrid hivatkozású elrendezést. Egy alkalmazás, amely a PDF 1.5 funkcióit, például objektumfolyamokat szeretne, miközben továbbra is lehetővé teszi, hogy egy PDF 1.4 olvasó megnyissa a fájlt, kétszer írja be a kereszthivatkozási információkat. Van egy klasszikus kereszthivatkozási tábla, a fix szélességű ASCII sorok, amelyek minden PDF-et lezártak az 1.4-es verzióig, és van egy kereszthivatkozási folyam, amely indexeli a többit. A klasszikus szakasz végeleme egy /XRefStm bejegyzést hordoz, amelynek értéke a folyam bájteltolása

A munkamegosztás szándékos. Azok az objektumok, amelyeket egy régi olvasónak el kell érnie, beleértve a katalógust és az oldalfát, címezhetők a klasszikus táblából. Azok az objektumok, amelyeket tömörített objektumfolyamokba hajtottak össze, szabadként vannak megjelölve a klasszikus táblázatban, egy f típusú bejegyzéssel, így egy 1.4-es olvasó egyenesen átlépi őket, és soha nem botlik bele olyan szerkezetbe, amelyet nem tud elemezni. Valódi helyük csak a kereszthivatkozási folyamban található meg. Egy ilyen fájl aláírása a vége: egy rövid klasszikus szakasz, gyakran nem más, mint egy xref, amelyet egy 0 0 alszakasz fejléc követ, amelynek a végeleme arra a /XRefStm-re mutat, ahol a tényleges helyreállítási adatok találhatók

Miért nem bizonyít semmit a helyes oldalszám

Mivel a katalógus és az oldalfa szándékosan elérhető a klasszikus táblázatból, egy olyan betöltő, amely csak ezt a táblázatot olvassa, megtalálja a /Root-ot, végigjárja az oldalfát, és jelenti a megfelelő oldalszámot. Minden jelen van, amire egy régi olvasónak szüksége van, így a fájl egészségesnek tűnik. A hiányzó objektumok azok, amelyeket objektumfolyamokba csomagoltak: AcroForm mezőszótárak, címkézett PDF struktúraelemek, azon kis szótárak hosszú sora, amelyeknek soha nem kellett láthatónak lenniük egy régi megjelenítő számára

Ezt a hiányosságot addig nem veszi észre, amíg valami nem érinti ezeket az objektumokat, és egy teljes újramentés mindet érinti. A dokumentum végigjárása az újratitkosításhoz vagy az újraíráshoz pontosan az a művelet, amely minden objektumszámot sorban bekér, és ez az oka annak, hogy a tünet a mentéskor jelentkezik, nem pedig a betöltéskor, távol az okától

A csapda egy detektor, amely látja az xref-et és megáll

A legolcsóbb módja annak eldöntésére, hogy egy fájl hogyan van indexelve, az a startxref követése, és az első bájtok megvizsgálása, amelyekre mutat. A xref kulcsszó egy klasszikus táblát jelent; egy folyamobjektum egy kereszthivatkozási folyamot. Ez a teszt minden olyan fájl esetében helyes, amely elkötelezi magát az egyik séma mellett. Ez azonban helytelen egy hibrid fájl esetében, amelynek a startxref-je egy klasszikus szakaszra irányul azzal az egyetlen céllal, hogy kielégítse a régi olvasókat, míg az ebben a szakaszban lévő végelem szótárban található /XRefStm az, ahol a dokumentum nagy része valójában indexelve van. Egy detektor, amely "klasszikus"-t ad vissza az első xref-nél, amellyel találkozik, soha nem olvassa a /XRefStm-et, és minden olyan objektum, amely csak a folyamban él, láthatatlanná válik

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;

A korai kilépés érzékelőjével a betöltés rendben lévőnek tűnik, és az újramentés az, ahol a hiányzó objektumok bejelentik magukat. A javítás nem az, hogy több bájtot olvasunk az elején; a javítás az, hogy felismerjük a hibrid végelemet, és követjük az /XRefStm-et, mielőtt úgy döntenénk, hogy a fájl kész

Az összevonási sorrend nem alku tárgya

Miután mindkét indexet beolvasták, csak egy irányba kombinálhatók. A kereszthivatkozási folyamot először össze kell vonni, a klasszikus bejegyzésekkel kitöltve azt. Az ok az a kis megtévesztés, amely a formátum szívében található. Egy hibrid fájl tömörített objektumait szabadként jelöli meg a klasszikus táblázatban, hogy a régi olvasók figyelmen kívül hagyják őket. Egy betöltő, amely tiszteletben tartja az "elsőként látott nyer" irányelvet, és először a klasszikus táblázatot olvassa el, ezeket az objektumszámokat szabadként rögzíti, majd eldobja a folyambejegyzéseket, amelyek valójában meghatározzák a helyüket, mert a helyek már foglaltak. Fordítsa meg a sorrendet, és a folyamból származó 2. típusú bejegyzések, mindegyik egy objektumfolyamszám és egy index, megnyerik azokat a helyeket, amelyeket birtokolniuk kell, és a klasszikus bejegyzések körülöttük helyezkednek el

Ugyanez a fegyelem véd meg egy régebbi revíziótól, amely feltámasztana egy törölt objektumot. A növekményes frissítések a /Prev-en keresztül visszafelé láncolódnak, és egy 0-s típusú szabad bejegyzés az az őrszem, amely jelzi, hogy egy újabb szakasz visszavont egy objektumszámot. A láncban lévő későbbi, régebbi szakasznak nem szabad megengedni, hogy felülírja ezt az őrszemet egy elavult hellyel. Kezelje az "elsőként látott"-at mérvadóként a szabad markerek esetében, és a törölt objektum törölt marad; kezelje óvatlanul, és a fájl saját története újraéleszti azt a tartalmat, amelyet a legutóbbi revízió eltávolított

Mit jelent ez a HotPDF-ben

A motor feloldja Önnek a hibrid hivatkozású fájlokat, és megteszi ezt minden olyan útvonalon, amelynek elemeznie kell a kereszthivatkozási adatokat. Töltsön be egy dokumentumot a LoadFromFile vagy a LoadFromStream segítségével, hajtsa végre a módosításokat, és hívja meg a SaveLoadedDocument-et; vagy futtasson egy egyszeri műveletet, például az EncryptFile-t, amely egy bemenetet olvas és egy kimenetet ír. A helyreállítás mindkét esetben beolvassa az /XRefStm-et, összevonja a folyamszakaszt a klasszikus bejegyzések előtt, és feloldja a folyamokban élő objektumokat, mielőtt az írás felsorolná őket. Az AES-256 titkosítási útvonal az, ahol a probléma először megmutatta magát, mert egy dokumentum titkosítása újraírja az összes objektumot, és így megköveteli, hogy minden objektum már megtalálható legyen

// 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]);

A részlet, amelyet érdemes magunkkal vinni, az API-tól felfelé helyezkedik el. A Word, Excel, PowerPoint és a "Mentés PDF-ként" folyamok hosszú sorából érkező fájlok rutinszerűen hibridek, így egy betöltő, amelyet csak a saját generátorának kimenetével gyakorol, soha nem biztos, hogy találkozik eggyel a tesztelés során. Töltse fel a tesztkészleteit valódi Office alkalmazásokból exportált dokumentumokkal, ne csak azokkal a fájlokkal, amelyeket a saját kódja állított elő

Gyanús fájl ellenőrzése

Két ellenőrzés gyorsan eldönti a kérdést. Nyissa meg a fájlt egy hexadecimális nézetben, és olvassa el a végső startxref utáni bájtokat; egy hibrid fájl egy rövid klasszikus szakaszt mutat, amelynek végelem szótára tartalmazza az /XRefStm-et. Vagy hasonlítsa össze a teljes elemzés által jelentett objektumszámot a legmagasabb objektumszámmal, amelyet a /Size deklarál a végelemben. A nagy rés azt jelenti, hogy az objektumok olyan folyamokban bújnak meg, amelyeket a betöltő nem nyitott meg, ami ugyanaz a hiány, amely később mentéskori hibává válik

A tipikus Excel-export farka konkréttá teszi az első ellenőrzést. A végső xref kulcsszó után minden egyszerű ASCII, így az aláírás egyenesen egy hexadecimális nézetből olvasható (az eltolások illusztrációk, a magyarázatok hozzáadva)

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

A 0 0 alszakasz az árulkodó jel: egy nulla bejegyzést tartalmazó klasszikus tábla csak azért létezik, hogy hordozza a végelemet, és a végelem főként azért létezik, hogy az /XRefStm 87325-öt mondja. Egy detektor, amely az xref kulcsszónál megáll, ezen a ponton a semmi indexét látta. Amikor inkább szkriptelni szeretné az ellenőrzést, mintsem szemrevételezni, a marker mindig a fájl utolsó pár kilobájtján belül található, így elegendő egy korlátozott visszafelé történő olvasás

// 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');

Kezelje a szondát triázsként, ne elemzőként: megmondja, hogy a kötegből mely fájlok érdemelnek figyelmet egy újramentési feladat futtatása előtt, és semmi mást. Hogy mit kell tennie egy betöltőnek az általa talált eltolással, követve a szakaszláncot, összevonva a folyambejegyzéseket a klasszikusok előtt, tiszteletben tartva a szabad bejegyzésű őrszemeket, azt lépésről lépésre végigjárjuk a társcikkünkben, amely a hibrid hivatkozású PDF-ek Office-alkalmazásokból történő kezeléséről szól

Ennek a történetnek az írói oldala, hogyan jönnek létre elsősorban az objektumfolyamok és a tömörített kereszthivatkozások, az objektumfolyamokról és növekményes frissítésekről szóló cikkünkben olvasható. Ha a szóban forgó hibrid fájl is nagyon nagy, a Direct File API útmutató a nagy PDF-munkafolyamatokhoz című részben található betöltési technikák lehetővé teszik, hogy ellenőrizze anélkül, hogy az egészet beolvasná a memóriába. Mindkettő természetesen párosul az itt leírt helyreállítással, amely a Delphi és a C++Builder számára készült HotPDF Component részeként kerül forgalomba az ezen a blogon máshol bemutatott betöltő, szerkesztő, titkosító és aláíró API-k mellett