Technical Article

Štruktúra PDF: Metadáta XML, záložky a anotácie

Ak odstránime popisy stránok, zostane nám tenká vrstva štruktúry, ktorú síce nikto netlačí, no závisí od nej každá čítačka, indexátor aj archivačný systém. Samotný objekt stránky nevie nič o kapitole, do ktorej patrí, o autorovi, ktorý ju napísal, ani o poznámke pod čiarou, ktorá odkazuje inam. Tiete informácie žijú o úroveň vyššie, v troch štruktúrach pripojených ku katalógu dokumentu: v prúdoch metadát, v strome osnovy (outline tree) a v poliach anotácií jednotlivých stránok. Spája ich vlastnosť, kvôli ktorej je ľahké urobiť chybu. Žiadna z nich nezanecháva na stránke viditeľné stopy, takže súbor sa môže vykresliť dokonale, no napriek tomu mu môžu chýbať záložky, jeho autor v metadátach môže protirečiť inému poľu alebo odkaz môže smerovať na objekt stránky, ktorý už neexistuje.

Toto je vrstva, ktorú knižnice pre prácu s PDF sprístupňujú ako vlastnosti dokumentu, rozhrania API pre záložky a volania pre odkazy či anotácie. A tiež vrstva, ktorú čítajú vyhľadávacie roboty, aby zistili, o čom váš dokument je. Objektový model pod ňou je popísaný v sprievodcovi štruktúrou PDF dokumentu. Tu sa zameriame výhradne na to, čo visí z katalógu.

Všetky tri štruktúry sa pripájajú ku katalógu. Kompletné prepojenie katalógu vyzerá takto:

1 0 obj
<< /Type /Catalog
   /Pages 2 0 R
   /Outlines 3 0 R
   /Names << /EmbeddedFiles 4 0 R >>
   /Metadata 5 0 R
>>
endobj

Štyri položky, štyri nezávislé subystémy. /Pages je viditeľný dokument, /Outlines predstavuje strom záložiek, /Metadata ukazuje na prúd XMP a /Names sprístupňuje celodokumentový slovník názvov, ktorý okrem iného obsahuje prílohy vložených súborov. Každá z týchto položiek je voliteľná a čítačka zobrazí stránky aj vtedy, ak nenájde žiadnu z nich. Tá voliteľnosť je presne tým dôvodom, prečo navigačná vrstva degraduje ako prvá, keď súbor upravujú nástroje, ktoré rozumejú iba stránkam.

Dve úložiská metadát, ktoré si protirečia

PDF nesie metadáta dokumentu na dvoch miestach naraz, a problémy začínajú vtedy, keď sa tieto údaje nezhodujú. Pôvodným mechanizmom je slovník informácií o dokumente (document information dictionary), na ktorý odkazuje kľúč /Info v časti trailer: plochá sada dvojíc kľúč-hodnota pre /Title, /Author, /Subject, /Keywords, /Creator, /Producer a dva dátumy. Je to jednoduché a čítá to každý prehliadač. PDF 2.0 však väčšinu z toho zavrhuje (deprecates) v prospech druhého mechanizmu: prúdu metadát XMP.

XMP je samostatný XML dokument napísaný v RDF, uložený ako prúd, ku ktorému katalóg pristupuje cez kľúč /Metadata, a je označený ako /Type /Metadata /Subtype /XML. Na rozdiel od slovníka Info skrytého v štruktúre objektov PDF je balík XMP navrhnutý tak, aby ho mohli samostatne extrahovať a analyzovať nástroje, ktoré o PDF nič nevedia. Tu je ukážka takého balíka:

5 0 obj
<< /Type /Metadata /Subtype /XML /Length 1235 >>
stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
        xmlns:dc="http://purl.org/dc/elements/1.1/"
        xmlns:xmp="http://ns.adobe.com/xap/1.0/"
        xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
      <dc:title><rdf:Alt><rdf:li xml:lang="x-default">Quarterly Report</rdf:li></rdf:Alt></dc:title>
      <dc:creator><rdf:Seq><rdf:li>A. Author</rdf:li></rdf:Seq></dc:creator>
      <xmp:CreateDate>2026-06-16T10:46:27+08:00</xmp:CreateDate>
      <xmp:CreatorTool>Reporting Service 4.2</xmp:CreatorTool>
      <pdf:Producer>losLab PDF Library</pdf:Producer>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj

Tri detaily v tomto bloku rozhodujú o tom, či metadáta prežijú kontakt so skutočnými nástrojmi. Inštrukcie na spracovanie xpacket nie sú len na ozdobu: ohraničujú balík tak, aby ho extraktor našiel vo vnútri väčšieho bajtového prúdu. Zapisovač, ktorý vynechá uzatvárací <?xpacket end="w"?>, vytvorí súbor, ktorý sa síce bez problémov otvorí, ale neprejde prísnymi validátormi. Dôležité sú aj dátové typy vlastností. dc:title je jazyková alternatíva obalená v rdf:Alt, zatiaľ čo dc:creator je usporiadaný zoznam a vyžaduje rdf:Seq. Ich zápis ako obyčajného textového uzla je najčastejšou chybou v XMP, ktorú väčšina prehliadačov toleruje, až kým nenarazíte na ten, ktorý ju netoleruje. Predpony menných priestorov (namespaces) sú konvenčné, no URI, na ktoré sa viažu, sú normatívne: parser sa riadi podľa URI, nie podľa predpony.

Zásadným pravidlom pri dvoch úložiskách je ich vzájomná zhoda. Ak /Info uvádza jedného autora a dc:creator iného, odoslali ste dokument, ktorý na rovnakú otázku dáva dve rôzne odpovede, a to, ktorá z nich vyhrá, závisí od toho, ktoré pole čítajúci nástroj načíta. Knižnica zvyčajne zapisuje obe verzie za vás. Akonáhle však jednu z nich upravíte ručne alebo zlúčite súbory z rôznych generátorov, začnú sa rozchádzať. Považujte slovník Info za dedičstvo pre kompatibilitu a XMP za zdroj pravdy a obe regenerujte z jednej sady hodnôt, namiesto ich samostatného opravovania. Pri formáte PDF/A sa to stáva požiadavkou na zhodu: norma ISO 19005 nariaďuje používanie XMP a zakazuje akúkoľvek vlastnosť Info, ktorá by bola v rozpore s jej náprotivkom v XMP.

Strom osnovy za panelom záložiek

To, čo prehliadač zobrazuje ako panel záložiek, je v súbore obojstranne prepojený strom slovníkov nazývaný osnova dokumentu (document outline). Katalóg ukazuje na koreňový slovník osnovy cez kľúč /Outlines, koreň ukazuje na svoju prvú a poslednú položku najvyššej úrovne a každá položka je prepojená so svojimi susedmi a rodičom. Nikde v súbore neexistuje pole záložiek. Celá štruktúra sa rekonštruuje sledovaním referencií, a presne preto môže jediný poškodený odkaz spôsobiť, že z panelu bez akejkoľvek chyby zmizne celá vetva.

8 0 obj                                    % the outline root
<< /Type /Outlines /Count 4 /First 9 0 R /Last 9 0 R >>
endobj
9 0 obj                                    % top-level: a chapter
<< /Title (Chapter 1: Results)
   /Parent 8 0 R /Count 2
   /First 12 0 R /Last 15 0 R >>
endobj
12 0 obj                                   % first child
<< /Title (Introduction)
   /Parent 9 0 R /Next 15 0 R
   /Dest [3 0 R /XYZ 72 720 0] >>
endobj
15 0 obj                                   % second child, last sibling
<< /Title (Methodology)
   /Parent 9 0 R /Prev 12 0 R
   /Dest [3 0 R /Fit] >>
endobj

Keď si prečítate odkazy, invarianty sú zrejmé. Každá položka ukazuje späť na svojho rodiča /Parent. Súrodenci tvoria reťazec cez /Prev a /Next, pričom prvá položka vynecháva /Prev a posledná vynecháva /Next. Rodič pomenúva svoje prvé a posledné dieťa cez /First a /Last a deti medzi nimi sú prístupné iba prechádzaním súrodeneckého reťazca. Ak urobíte chybu, zlyhanie bude tiché: neaktuálny kľúč /Next odstrihne kapitolu, rodič, ktorého /Last neukončuje reťazec, zanechá položky osirotené a prehliadač zobrazí len to, čo dokáže nájsť.

Pole /Count nesie stav, ktorý mnohých prekvapí. V koreni a v akejkoľvek rozbalenej položke udáva počet aktuálne viditeľných potomkov. V zbalenej položke ide o záporné číslo, ktorého absolútna hodnota vyjadruje, koľko potomkov by sa objavilo po rozbalení. Pole /Count teda nie je pevný štrukturálny údaj o strome, ale uložený stav otvorenia či zatvorenia panelu. Generátor, ktorý ho pevne zapíše ako kladnú hodnotu, otvorí každú vetvu, ktorú chcel autor pôvodne nechať zatvorenú.

Každá položka má svoj zmysel v tom, že niekam ukazuje. Kľúč /Title definuje text zobrazený v paneli, /Dest určuje cieľ, kam prejde kliknutie. Cieľ môže byť zapísaný priamo v položke (ako v ukážke vyššie) alebo ako názov, ktorý sa vyhodnocuje cez slovník názvov dokumentu. To je lepšia voľba v prípade, ak veľa záložiek a odkazov mieri na rovnaké miesta, pretože presunutý cieľ opravíte na jedinom mieste. Knižnica zvyčajne skrýva tento strom za objektom koreňa osnovy a metódami na pridávanie detí. V HotPDF dokument sprístupňuje OutlineRoot typu THPDFDocOutlineObject a odkazy /Prev, /Next, /Parent a /Count spája za vás pri pridávaní položiek. To sa rozhodne oplatí využiť, pretože ručné udržiavanie týchto invariantov pri úpravách je miestom, kde osnovy najčastejšie zlyhávajú.

Ciele: gramatika toho, kam kliknutie vedie

Záložky aj odkazové anotácie ukazujú na ciele (destinations) a cieľ je viac než len číslo strany. Je to pole, ktoré pomenúva objekt stránky a v druhom slote pomocou kľúčového slova špecifikuje, ako ju má prehliadač zobraziť. Najbežnejším a tiež najčastejšie nesprávne používaným je /XYZ v tvare [page /XYZ left top zoom]. Jeho tri operandy sú nezávislé a ktorýkoľvek z nich môže byť null, čo znamená „ponechať hodnotu, ktorú mal čitateľ nastavenú“. Zápis [page /XYZ null null null] skočí na stránku bez zmeny pozície rolovania alebo priblíženia, čo je zvyčajne to, čo od odkazu „prejsť na stránku“ očakávate. Čísla sú v predvolenom používateľskom priestore merané od ľavého dolného rohu, pričom súradnica y rastie nahor: ide o rovnaký súradnicový systém, aký používa obsah stránky. Autori prichádzajúci z oblasti tvorby webov reflexívne merajú súradnice zhora a posielajú čitateľa na opačný koniec stránky.

Rodina /Fit vymieňa presné umiestnenie za prispôsobivosť. Zápis [page /Fit] prispôsobí celú stránku oknu, [page /FitH top] prispôsobí šírku stránky s danou hornou hranou a [page /FitR l b r t] priblíži obdĺžnik tak, aby vyplnil zobrazenie. Keďže tieto príkazy počítajú mierku z geometrie stránky a nie z pevných súradníc, cieľ typu /Fit sa po zmene veľkosti stránky zachová správne, zatiaľ čo cieľ /XYZ s pevne zadefinovaným zoomom môže čitateľa zanechať otočeného na okraj. Pre obsah dokumentu je /FitH s hornou súradnicou danej sekcie oveľa trvácnejšou voľbou než /XYZ s odhadnutým priblížením.

Anotácie: všetko interaktívne, čo nie je obsahom stránky

Anotácia je objekt, ktorý prekrýva stránku bez toho, aby bol súčasťou jej obsahového prúdu. Odkazy, lepiace poznámky, zvýraznenia, prvky formulárov, ikony príloh súborov, pečiatky: to všetko sú anotácie uvedené v poli /Annots stránky, na ktorej sa nachádzajú. Odstránením anotácie z tohto poľa ju odstránite zo stránky, hoci samotný obsah pod ňou zostane nedotknutý. O to tu ide: anotácie sú samostatnou vrstvou úprav oddelenou od grafických prvkov, nad ktorými ležia.

12 0 obj                                    % link to a destination
<< /Type /Annot /Subtype /Link
   /Rect [100 200 300 250]
   /Border [0 0 0]
   /Dest [5 0 R /XYZ null null null] >>
endobj
13 0 obj                                    % link that runs an action
<< /Type /Annot /Subtype /Link
   /Rect [50 50 200 100]
   /Border [0 0 0]
   /A << /Type /Action /S /URI /URI (https://www.example.com) >> >>
endobj

Pole /Rect definuje klikateľnú oblasť. Kliknutie do nej odošle čitateľa na cieľ, pričom sa používa rovnaká gramatika ako pri osnovách. Položka /Border [0 0 0] vykonáva dôležitú prácu: potláča nevzhľadný predvolený obdĺžnik, ktorý prehliadače okolo odkazov kreslia. Druhý tvar vymieňa samotný kľúč /Dest za akciu /A, ktorej podtyp /S určuje správanie: /GoTo v rámci tohto súboru, /GoToR pre iný súbor, /URI pre webovú adresu a /Launch na spustenie externého programu. Posledná menovaná možnosť si zaslúži veľkú opatrnosť. Akcia /Launch spúšťajúca spustiteľný súbor robí z PDF potenciálny vektor šírenia malvéru, preto ju kompatibilné prehliadače blokujú alebo zobrazujú dôrazné varovania, čím odkaz pre väčšinu čitateľov znefunkčnia. Siahnite po /URI a /GoTo a akciu /Launch radšej vynechajte.

Zvýrazňovacie anotácie (markup annotations), ako sú lepiace poznámky, a tvarové anotácie, ako napríklad /Square, prinášajú drobný detail: ich vzhľad na obrazovke nevyplýva priamo z ich typu. Prehliadač vykreslí svoju vlastnú verziu, pokiaľ vzhľad pevne nedefinujete pomocou prúdu vzhľadu (appearance stream) v položke /AP, ktorá odkazuje na XObject formulára s operátormi kreslenia. Ak to vynecháte, rovnaké zvýraznenie môže v dvoch rôznych čítačkách vyzerať inak, prípadne sa zmeniť po opätovnom uložení v editore. Pre čokoľvek, čoho presný vzhľad je pre dokument dôležitý, zadefinujte položku /AP. Mimochodom, prílohy súborov používajú rovnaké mechanizmy: prúd vloženého súboru a slovník špecifikácie súboru, ktoré sa zobrazujú buď ako anotácia /FileAttachment, alebo cez strom názvov /EmbeddedFiles pod položkou katalógu /Names.

Kde táto vrstva zlyháva a ako to odhaliť

Najčastejšou chybou v celej tejto vrstve je poškodený odkaz (dangling reference). Záložky sa prestanú zobrazovať, keď katalóg neobsahuje položku /Outlines alebo sa súrodenecký reťazec pretrhne uprostred stromu. Metadáta sa ignorujú, ak prúdu XMP chýba označenie /Type /Metadata /Subtype /XML, prípadne ak je poškodené ohraničenie xpacket. Vo všetkých prípadoch je obsah stránky v poriadku, takže pri bežnom otvorení vyzerá súbor správne a chyba sa prejaví až v paneli, ktorý nikto neskontroloval.

Väčšinu chýb odhalia dva jednoduché návyky. Otvorte výsledný súbor v reálnom prehliadači a preklikajte panel záložiek a niekoľko odkazov, čím preveríte graf referencií rovnakým spôsobom, akým to robí čítačka. Potom načítajte metadáta pomocou samostatného nástroja a overte, či slovník Info a XMP súhlasia (tento nesúlad totiž žiadnym klikaním neodhalíte). Ak generujete túto vrstvu cez knižnicu, ktorá spravuje odkazy za vás, väčšine týchto chýb sa úplne vyhnete. Komponent HotPDF pre Delphi a C++Builder sprístupňuje štruktúry osnovy, anotácií a metadát cez rozhrania API na úrovni dokumentu, takže vy len zadefinujete hierarchiu záložiek a odkazov a prepojenie referencií necháte naňho. Pre bližšie pochopenie objektového modelu, ku ktorému sa tieto štruktúry pripájajú, sa technický prehľad štruktúry súborov PDF zaoberá katalógom a tabuľkou krížových odkazov, na ktorých tieto prvky závisia.