Műszaki cikk

A PDF oldalfa alakja: elágazás, lapítás és a /Count integritása

A kapcsolódó cikkünk a PDF oldalsorrendről tárgyalja az alapszabályt: a megjelenítési sorrend a /Pages fa /Kids tömbjeinek mélységi, balról jobbra történő bejárásából adódik, sosem az objektumszámokból. Ez a cikk más szemszögből vizsgálja a fát: az alakját. Miért állítanak elő az érett PDF-írók köztes csomópontokból álló hierarchiákat, amikor egyetlen lapos tömb is teljesen szabályos lenne? Mi változik valójában, amikor egy eszköz lapítja vagy újraépíti a fát? És mi történik, amikor a /Count nyilvántartás, amely az egész szerkezetet gyorssá teszi, megszűnik igazat mondani

Az elágazás teljesítménydöntés

Semmi sem kényszeríti az írót beágyazásra. Egy 10 000 oldalas dokumentum, amelynek egyetlen gyökér /Pages csomópontja és 10 000 levélhivatkozása van egyetlen /Kids tömbben, megfelel a specifikációnak. A PDF Reference mégis kiegyensúlyozott fát javasol nagy dokumentumokhoz, és a mértékadó generátorok ezt a tanácsot mérsékelt elágazással követik, jellemzően néhány tucat gyermekkel köztes csomópontonként

Az ok abban rejlik, amit egy megjelenítőnek el kell olvasnia, mielőtt bármit is megjeleníthetne. Vegyük azt az esetet, amikor közvetlenül a 10 000 oldalas fájl 8214. oldalára ugrunk. Lapos fa esetén a megjelenítőnek először a gyökércsomópontot kell elemeznie, ez pedig egyetlen hatalmas tömb: hozzávetőleg nyolc bájt indirekt hivatkozásonként, egy 80 KB-os objektum, amelyet végig kell tokenizálni, mielőtt a 8213. bejegyzés feloldható lenne. Egy 32-es elágazású, kiegyensúlyozott fánál ugyanez az ugrás beolvassa a gyökeret, összehasonlítja a futó /Count összegeket a megfelelő gyermek kiválasztásához, majd lejjebb lép — összesen három vagy négy kis szótár, egyenként néhány száz bájt. Ez az O(log n) véletlen elérés, amelyre a fát tervezték, és pontosan emiatt létezik a /Count a köztes csomópontokon: lehetővé teszi, hogy az olvasó átugorjon egy egész részfát anélkül, hogy egyetlen benne lévő objektumot is megnyitna

A fa alakja a szerkesztés költségét is meghatározza. Egy inkrementális frissítés, amely beszúr egy oldalt, minden olyan csomópontot újra kell írjon, amelynek /Kids vagy /Count értéke megváltozott, vagyis az új levél szülőjétől a gyökérig vezető utat. Kiegyensúlyozott fában ez az út néhány kis szótár, amelyet a fájlhoz fűznek. Lapos fában az „út” maga az egyetlen óriási gyökértömb, amelyet minden revízió teljes egészében megkettőz. Egy szerződés, amely harminc felülvizsgálati és jegyzetelési ciklusán megy keresztül, végül harminc elavult másolatát cipelheti ugyanannak a 80 KB-os tömbnek a bájtfolyamában

PDF diagram: kiegyensúlyozott PDF oldalfa kis /Pages csomópontokból építve szemben a lapos fával, amelynek gyökere egy óriás /Kids tömböt tart, kiemelve a gyorsabb véletlen hozzáférést és olcsóbb inkrementális frissítéseket
Egy kiegyensúlyozott fa három-négy kis szótárból válaszolja meg az ugrást a 8214. oldalra, míg egy lapos fa minden nyitáskor és revíziónál egy hatalmas /Kids tömböt dolgoz fel és ír újra

A belső csomópontok örökölt attribútumokat hordoznak

A köztes csomópontok nem csupán az irányítást szolgálják. A négy örökölhető oldalattribútum — a /Resources, a /MediaBox, a /CropBox és a /Rotate — bármely /Pages csomópontra felhelyezhető, ahol minden alatta lévő levélre vonatkozik, hacsak egy leszármazott felül nem írja azokat. Egy fekvő tájolású függelékkel rendelkező jelentést előállító író magában a fában fejezheti ki ezt az elrendezést:

5 0 obj   % dokumentum gyökere
<< /Type /Pages /Count 6 /Kids [6 0 R  7 0 R] >>
endobj

6 0 obj   % jelentés törzse: álló A4, törzsbetűtípus
<< /Type /Pages /Parent 5 0 R /Count 3
   /Kids [30 0 R  31 0 R  32 0 R]
   /MediaBox [0 0 595 842]
   /Resources << /Font << /F1 8 0 R >> >> >>
endobj

7 0 obj   % függelék: fekvő A4, elforgatva, saját betűtípus
<< /Type /Pages /Parent 5 0 R /Count 3
   /Kids [40 0 R  41 0 R  42 0 R]
   /MediaBox [0 0 842 595] /Rotate 90
   /Resources << /Font << /F2 9 0 R >> >> >>
endobj

40 0 obj  % függelék oldala: örökli a méretet, forgatást, betűtípusokat
<< /Type /Page /Parent 7 0 R /Contents 43 0 R >>
endobj

A 40–42. objektumok szinte üresek. Az oldalméretük, forgatásuk és betűtípus-erőforrásaik mind öröklés útján érkeznek a 7. csomóponttól, ami tömörré és önfenntartóvá teszi a fájlt: adjunk hozzá egy negyedik oldalt a függelék csomópontja alá, és az automatikusan fekvő tájolású lesz

Ugyanez a mechanizmus okozza a klasszikus oldalmozgatási veszélyt. Tegyük fel, hogy egy eszköz a 40. objektumot a jelentés törzsébe mozgatja a két /Kids tömb szerkesztésével és a /Parent átirányításával a 6. csomópontra. A mozgatás szerkezetileg érvényes, mégis a 40. objektum most az álló /MediaBox-ot, forgatás nélküli állapotot és az /F1 betűtípust örökli — miközben a tartalomfolyama továbbra is az /F2-t választja, amely már nem oldható fel. Az oldal egyetlen szerkesztéssel összezsugorodik, visszaforog, és elveszíti a szövegét. A robusztus átrendező kód ezért az összes négy örökölhető attribútum feloldott értékét rögzíti az oldal szótárában, mielőtt újra szülőt rendelne hozzá. Ha valaha is húzott már egy oldalt egy szerkesztőben, és látta, hogy megváltozik a mérete vagy a tájolása, akkor ezt a mechanizmust tapasztalta

Lapítás: szabályos, gyakori, néha költséges

Sok eszköz az ellenkező irányba megy. A minimalista írók egyszintű fát állítanak elő, mert ez egyszerű, és számos egyesítő és szétválasztó segédprogram bármilyen beolvasott fát egyetlen lapos /Kids tömbbé épít újra, mivel a kiegyensúlyozott szerkezet előállítása plusz munka, a lapos kimenet pedig mindig szabályos. A helyes újraépítésnek egyúttal fel kell oldania az öröklődést is: minden attribútumot, amelyet a levél örökölt, át kell másolni a levélre, vagy fel kell emelni az új gyökérbe, ha az egységes az egész dokumentumban — különben a kimenet pontosan úgy változtatja meg a geometriát, mint az oldalmozgatás esete

Tipikus dokumentumoknál a lapítás ártalmatlan. Nagy méretnél viszont árt, a már ismertetett két módon: a gyökértömb egyetlen nagy objektummá válik, amelyet minden megnyitásnak és minden oldalugrásnak teljesen elemeznie kell, és minden szerkezeti szerkesztés teljesen újraírja azt. Amit a lapítás nem tesz tönkre, az az indirekt hivatkozások általi megosztás — egy lapos fa, amelyben mind a 10 000 oldal ugyanarra a /Resources szótárobjektumra mutat, továbbra is deduplikált marad. Ami elvész, az csupán a lehetőség, hogy a bejegyzést kihagyjuk az oldalról, és hagyjuk, hogy egy ős szolgáltassa azt

Amikor a /Count hazudik

A /Count tiszta nyilvántartás: meg kell egyeznie a csomópont részfájában lévő levéloldalak számával, és a fájlformátumban semmi sem kényszeríti ki ezt. Két sérülési minta felel a valóságban látott hazug számlálók többségéért

PDF diagram örökölhető PDF oldalattribútumokról (/Resources, /MediaBox, /CropBox, /Rotate), amelyek belső /Pages csomópontokból a levéloldalakra folynak, figyelmeztetéssel: egy oldal áttelepítése csendben kicseréli mindent, amit örököl
Az öröklődő attribútumok belső csomópontokon élnek, és minden alattuk lévő levélre áramlanak, így egy oldal szülőcseréje csendben kicseréli a méretét, forgatását és fontjait

Az első az inkrementális frissítés által hátrahagyott elavult számláló. Egy szerkesztő beszúr egy oldalt, új /Kids tömbbel és frissített /Count értékkel újraírja a közvetlen szülőt, mindkettőt hozzáfűzi a fájlhoz — és sosem nyúl az ősökhöz:

% Eredeti revízió
12 0 obj
<< /Type /Pages /Count 9 /Kids [13 0 R  14 0 R  15 0 R] >>
endobj

14 0 obj
<< /Type /Pages /Parent 12 0 R /Count 3
   /Kids [50 0 R  51 0 R  52 0 R] >>
endobj

% Hozzáfűzött revízió: egy oldal beszúrva a középső ágba.
% A 14. objektum lecserélődött; a 12. objektumot sosem írják újra
14 0 obj
<< /Type /Pages /Parent 12 0 R /Count 4
   /Kids [50 0 R  51 0 R  90 0 R  52 0 R] >>
endobj

A fa most tíz levelet tartalmaz, de a gyökér még mindig kilencet mond. Az a megjelenítő, amely megbízik a gyökérben, kilenc oldalt jelent az oldalszámlálójában. Az, amelyik a belső számlálókat bináris kereséshez használja egy oldalugráshoz, minden, a beszúrási pont utáni oldalnál hibás indexet számít ki. Egy teljes bejárás tízet talál. Három különböző válasz, egyetlen fájl

PDF diagram egy elavult /Count értékről egy PDF oldalfában: egy beszúrt oldal, egy gyökér, amely még kilencet jelent, és három fogyasztó, amelyek eltérő összegeket kapnak, amíg egy teljes bejárás tizet nem talál
Egy beszúrt oldal a tárolt gyökér /Count-ját kilencen hagyja, miközben a bejárás tízet talál, és a szigorú elemzők elutasítják azt, amit az elnéző nézők csendben átugranak

A második minta az a számláló, amely sosem lehetne helyes: negatív, nulla egy feltöltött csomóponton, vagy abszurd módon hatalmas. Ezek fuzzingból, átviteli sérülésből, és időnként szerkesztők aritmetikai hibáiból erednek. Kifejezetten veszélyesek az olyan kódra, amely megbízik a /Count értékében az allokációhoz — egy tömb méretezése egy -3-as /Count alapján legjobb esetben tartományhibát vált ki, egy kétmilliárdos /Count alapján történő allokáció pedig szolgáltatásmegtagadási támadást okoz. Az érték megbízhatatlan bemenet, mint a fájl minden más száma

Az elemzők ebben a kérdésben két táborra oszlanak. A szigorú fogyasztók — preflight eszközök, PDF/A validátorok, archiválási folyamatok — összehasonlítják a /Count értéket a bejárás eredményével, és elutasítják vagy megjelölik a fájlt. Az interaktív megjelenítők szinte kivétel nélkül megengedők: bejárják a fát, levezetik a valódi számlálót, és csendben figyelmen kívül hagyják a tárolt értéket, ami pontosan az oka annak, hogy egy elavult számlálójú fájl évekig keringhet panasz nélkül, amíg egy szigorúbb elemzővel nem találkozik valamelyik automatizált munkafolyamatban. A könyvtári kód számára a védekező középút az, hogy a /Count értéket tippként kezeljük — hasznos az előzetes allokációhoz és a részfa átugrásához, miután ellenőrizték —, miközben a bejárás marad az igazság forrása

Magához a bejárási algoritmushoz, az öröklődés-keresési szabályokhoz és a katalógustól a levélig tartó bejáráshoz kezdje az oldalsorrend-magyarázóval. Hogy ezek a hibamódok hogyan néznek ki, amikor egy valódi ügyféldokumentum eléri a termelési kódot, olvassa el az oldalsorrend-hibakeresési esettanulmányt, amely egy összekevert oldalú incidenst követ végig a tünettől a kiváltó okig

A HotPDF Delphi Component mindezt belsőleg kezeli: bármilyen mélységű beágyazott fákat bejár, feloldja az örökölt attribútumokat oldalak másolásakor vagy mozgatásakor, és a tényleges levélszámok alapján ellenőrzi a /Count értéket ahelyett, hogy megbízna benne, így az API-jában szereplő oldalindexek mindig logikai oldalakat jelentenek