Atmetus puslapių aprašymus, lieka plonas struktūros sluoksnis, kurio niekas nespausdina, tačiau nuo kurio priklauso kiekvienas skaitytuvas, paieškos indeksuotojas ir archyvavimo sistema. Puslapio objektas nieko nežino apie skyrių, kuriam jis priklauso, autorių, kuris jį parašė, ar išnašą, kuri nurodo į kitą vietą. Šios žinios gyvena vienu lygiu aukščiau, trijose struktūrose, pririštose prie dokumento katalogo: metaduomenų srautuose, žymių (bookmarks) medyje ir kiekvieno puslapio anotacijų masyvuose. Juos sieja viena savybė, dėl kurios lengva padaryti klaidų: nė vienas iš bendrų elementų nepalieka matomų žymių puslapyje, todėl failas gali būti atvaizduojamas puikiai, tačiau jame gali trūkti žymių, jis gali prieštarauti savo paties autoriaus laukui arba nukreipti nuorodą į puslapio objektą, kuris faile nebėra.
Tai yra sluoksnis, kurio reikšmes PDF biblioteka atskleidžia kaip dokumento savybes, žymių API bei nuorodų ar anotacijų iškvietimus, ir sluoksnis, kurį nuskaito paieškos robotai, kad nuspręstų, apie ką yra jūsų dokumentas. Po juo slypintis objektų modelis aprašytas PDF dokumento struktūros nagrinėjime. Čia dėmesys sutelkiamas tik į tai, kas kabo nuo katalogo.
Visos trys struktūros prisijungia prie katalogo. Pilnas katalogas, sujungiantis jas visas, atrodo taip:
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
/Outlines 3 0 R
/Names << /EmbeddedFiles 4 0 R >>
/Metadata 5 0 R
>>
endobj
Keturi įrašai, keturios nepriklausomos posistemės. /Pages yra matomas dokumentas; /Outlines – žymių medis; /Metadata nurodo į XMP srautą; /Names pasiekia viso dokumento pavadinimų žodyną, kuris, be kitų dalykų, laiko įterptus failų priedus. Kiekvienas iš jų yra neprivalomas, o skaitytuvas, neradęs nė vieno iš jų, vis tiek rodys puslapius. Šis neprivalomumas yra būtent ta priežastis, kodėl navigacijos sluoksnis sugenda pirmiausia, kai failas redaguojamas įrankiais, kurie supranta tik puslapius.
Dvi metaduomenų saugyklos, kurios prieštarauja viena kitai
PDF dokumento metaduomenis laiko dviejose vietose vienu metu, ir problemos prasideda tada, kai jie sako skirtingus dalykus. Pradinis mechanizmas yra dokumento informacijos žodynas (Info dictionary), į kurį nurodo pabaigos bloko (trailer) įrašas /Info: paprastas raktų ir reikšmių porų rinkinys, skirtas /Title, /Author, /Subject, /Keywords, /Creator, /Producer ir dviem datoms. Jis yra paprastas ir jį nuskaito kiekviena skaityklė. PDF 2.0 atsisako (deprecates) didžiosios jo dalies, pirmenybę teikiant antrajam mechanizmui – XMP metaduomenų srautui.
XMP yra savarankiškas XML dokumentas, parašytas RDF formatu, saugomas kaip srautas, kurį katalogas pasiekia per /Metadata nuorodą, ir pažymėtas /Type /Metadata /Subtype /XML. Skirtingai nuo Info žodyno, paslėpto PDF objektų struktūroje, XMP paketas sukurtas taip, kad jį būtų galima ištraukti ir išanalizuoti atskirai įrankiais, kurie visiškai nežino apie PDF. Štai tipiškas paketas:
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
Trys šio bloko detalės nusprendžia, ar metaduomenys išliks apdorojant juos realiais įrankiais. Apdorojimo instrukcijos xpacket nėra tik dekoracija: jos įrėmina paketą, kad ištraukimo įrankis galėtų jį rasti didesniame baitų sraute, a generatorius, kuris praleidžia pabaigos instrukciją <?xpacket end="w"?>, sukuria failą, kuris atsidaro sėkmingai, tačiau pažeidžia griežtų tikrintuvų taisykles. Savybių duomenų tipai taip pat svarbūs. dc:title yra kalbos alternatyva, apgaubta rdf:Alt, o dc:creator yra tvarkingas sąrašas, naudojantis rdf:Seq; bet kurio iš jų pateikimas kaip paprasto teksto mazgo yra pati dažniausia XMP klaida, kurią toleruoja dauguma skaityklių tol, kol nesusiduriama su tokia, kuri to netoleruoja. Vardų sričių (namespaces) priešdėliai yra sutartiniai, tačiau URI, prie kurių jie pririšti, yra normatyviniai: analizatorius remiasi URI, o ne priešdėliu.
Griežta taisyklė su dviem saugyklomis yra ta, kad jos privalo sutapti. Jei /Info nurodo vieną autorių, o dc:creator – kitą, jūs išsiuntėte dokumentą, kuris į tą patį klausimą atsako dviem būdais, ir kuris atsakymas laimės, prikalauso nuo to, kurį lauką nuskaito naudojamas įrankis. Biblioteka paprastai įrašo abu už jus, tačiau tą akimirką, kai redaguojate kurį nors iš jų rankiniu būdu arba sujungiate failus iš skirtingų generatorių, jie ima skirtis. Traktuokite Info žodyną kaip pasenusį suderinamumo įrankį, o XMP – kaip tiesos šaltinį, ir atnaujinkite abu iš vieno reikšmių rinkinio, užuot keitę juos atskirai. PDF/A standartui tai tampa privalomu reikalavimu: ISO 19005 reikalauja naudoti XMP ir draudžia bet kokią Info savybę, kuri prieštarauja jos XMP atitikmeniui.
Žymių medžio struktūra už žymių skydelio
Tai, ką skaitytuvas rodo kaip žymių skydelį, faile yra dvigubai susietas žodynų medis, vadinamas dokumento kontūru (document outline). Katalogas nurodo į šakninį kontūro žodyną per /Outlines; šaknis nurodo į pirmąjį ir paskutinį aukščiausio lygio elementus; o kiekvienas elementas yra susietas su kaimynais ir savo tėvu. Niekuo faile nėra jokio žymių masyvo. Visa struktūra atkuriama sekant nuorodomis, ir būtent todėl viena pažeista nuoroda gali priversti visą žymių šaką pradingti be jokio pranešimo apie klaidą.
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
Perskaitykite nuorodas ir ryšiai taps akivaizdūs. Kiekvienas elementas nurodo atgal į savo tėvą /Parent. Kaimyniniai elementai (siblings) sudaro grandinę per /Prev ir /Next, kur pirmasis elementas neturi /Prev, o paskutinis – /Next. Tėvas įvardija savo pirmąjį ir paskutinį vaikus per /First ir /Last, o vaikai tarp jų yra pasiekiami tik einant kaimyninių elementų grandine. Suklyskite nors vienoje vietoje, ir gedimas bus tylus: pasenusi /Next nuoroda nukirs skyrių, tėvas, kurio /Last neužbaigia grandinės, paliks elementus našlaičiais, o skaitytuvas atvaizduos tik tai, ką sugeba pasiekti.
Laukas /Count neša būseną, kuri dažnai stebina. Šaknyje ir bet kuriame išskleistame elemente jis rodo šiuo metu matomų palikuonių skaičių; suskleistame elemente tai yra neigiamas skaičius, kurio modulis nurodo, kiek palikuonių atsirastų jį išskleidus. Taigi /Count nėra fiksuotas struktūrinis faktas apie medį – tai tiesiog išsaugota skydelio atidarymo ar uždarymo būsena, o generatorius, kuris kietai įrašo teigiamą skaičių, iš naujo atidarys kiekvieną šaką, kurią autorius norėjo palikti uždarytą.
Kiekvienas elementas turi nukreipti vartotoją į tam tikrą vietą. /Title yra tai, ką rodo skydelis; /Dest – kur nukeliaujama paspaudus. Tikslas (destination) gali būti įrašytas tiesiogiai elemente, kaip parodyta aukščiau, arba būti pavadinimu, kurį išsprendžia dokumento pavadinimų žodynas. Pastarasis variantas yra pranašesnis, kai daug žymių ir nuorodų rodo į tas pačias vietas, nes pakeistą tikslą pataisysite tik vienoje vietoje. Biblioteka paprastai paslepia šį medį už šakninio žymių objekto ir metodų, pridedančių vaikų įrašus; programoje HotPDF dokumentas atskleidžia OutlineRoot savybę, kurios tipas yra THPDFDocOutlineObject, ir atlieka /Prev, /Next, /Parent bei /Count susiejimus už jus, kai pridedate naujus elementus. Tuo verta naudotis, nes rankinis šių ryšių palaikymas atliekant pakeitimus dažniausiai ir sugadina žymes.
Tikslo nuorodos: gramatika, kur nuveda paspaudimas
Tiek žymės, tiek nuorodų anotacijos rodo į tikslo nuorodos (destinations), o tikslo nuoroda yra daugiau nei puslapio numeris. Tai masyvas, kuris įvardija puslapio objektą, o tada per antrame lizde esantį raktinį žodį nurodo, kaip skaitytuvas turėtų jį pateikti. Dažniausiai naudojamas ir labiausiai neteisingai suprantamas yra /XYZ, turintis formą [page /XYZ left top zoom]. Jo trys operandai yra nepriklausomi, ir bet kuris gali būti null, kas reiškia „palikti taip, kaip buvo skaityklėje“. Taigi [page /XYZ null null null] tiesiog peršoka į puslapį, nekeisdamas slinkties pozicijos ar mastelio – būtent to paprastai norima iš nuorodos į puslapį. Skaičiai nurodomi numatytojoje vartotojo erdvėje, matuojamoje nuo apatinio kairiojo kampo su kylančia Y ašimi – toje pačioje koordinačių sistemoje, kurią naudoja puslapio turinys. Autoriai, pripratę prie ekranų maketavimo, instinktyviai matuoja nuo viršaus ir nusiunčia skaitytoją į neteisingą puslapio dalį.
Šeima /Fit keičia tikslų pozicionavimą į lankstumą. [page /Fit] pritaiko visą puslapį lange, [page /FitH top] pritaiko puslapio plotį pagal nurodytą viršutinį kraštą, o [page /FitR l b r t] priartina stačiakampį, kad jis užpildytų vaizdą. Kadangi šie parametrai mastelį skaičiuoja pagal puslapio geometriją, o ne pagal fiksuotas koordinates, /Fit tikslas veikia teisingai net pakeitus puslapio dydį, tuo tarpu /XYZ su kietai įrašytu masteliu gali palikti skaitytoją žiūrintį į paraštę. Turinys su /FitH ir viršutine sekcijos koordinate tarnauja geriau nei /XYZ su spėjamu masteliu.
Anotacijos: viskas, kas interaktyvu ir nėra puslapio turinys
Anotacija yra objektas, kuris užkloja puslapį, nebūdamas jo turinio srauto dalimi. Nuorodos, lipnūs lapeliai, paryškinimai, formų laukai, failų priedų piktogramos, antspaudai – visa tai yra anotacijos, išvardytos puslapio, ant kurio jos stovi, /Annots masyve. Pašalinus anotaciją iš šio masyvo, ji pašalinama iš puslapio, net jei pats turinys lieka nepaliestas. Tai ir yra esmė: anotacijos yra redagavimo sluoksnis, atskiras nuo po jomis esančių ženklų.
Kiekviena anotacija dalijasi bendra nedidele ašimi. /Subtype nurodo rūšį, /Rect pateikia jos ribojamąjį rėmelį puslapio koordinatėse, o /Contents laiko tekstą, kuris taip pat atlieka prieinamumo aprašymo funkciją. Nuorodos anotacija (Link annotation) yra atvejis, kurį verta panagrinėti, nes ji būna dviejų formų: paprasto tikslo nuorodos ir veiksmo.
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
Savybė /Rect yra karštasis taškas; paspaudus jo viduje skaitytojas siunčiamas į tikslo nuorodą pagal tą pačią gramatiką, kurią naudoja kontūras. /Border [0 0 0] atlieka realų darbą – pašalina negražų numatytąjį rėmelį, kurį skaitytuvai brėžia aplink nuorodas. Antroji forma pakeičia paprastą /Dest į /A veiksmą, kurio /S potipis pasirenka elgseną: /GoTo šiame faile, /GoToR kitame faile, /URI interneto adresu arba /Launch išorinei programai paleisti. Pastaroji funkcija kelia įtarimą: /Launch veiksmas, paleidžiantis vykdomąjį failą, yra elgsena, paverčianti PDF virusų nešiotoju, todėl standartus atitinkantys skaitytuvai jį blokuoja arba garsiai įspėja vartotoją, ir daugumai skaitytojų nuoroda tiesiog neveikia. Rinkitės /URI bei /GoTo ir nenaudokite /Launch.
Žmėjimo anotacijos (pavyzdžiui, paryškinimai ar lipnūs lapeliai) bei formų anotacijos (pavyzdžiui, /Square) prideda papildomą niuansą: jų vaizdas ekrane nėra tiesiogiai nusakomas jų tipu. Skaitytuvas atvaizduoja savo versiją, nebent užfiksuosite vaizdą naudodami vaizdo srautą (appearance stream) – /AP įrašą, kuris nurodo į formos XObject, turintį piešimo operatorius. Praleiskite jį, ir tas pats paryškinimas dviejuose skaitytuvuose arba po redagavimo gali atrodyti skirtingai. Viskam, kurio tiksli išvaizda yra dokumento dalis, pateikite /AP. Failų priedai, beje, naudoja tą patį mechanizmą: įterptą failo srautą ir failo specifikacijos žodyną, kuris pateikiamas arba kaip /FileAttachment anotacija, arba per /EmbeddedFiles pavadinimų medį po katalogo /Names įrašu.
Kur šis sluoksnis sugenda ir kaip tai pastebėti
Nuolatinis gedimas visuose šiuose elementuose yra pažeista nuoroda. Žymės nustoja rodytis, kai kataloge nėra /Outlines įrašo arba kaimyninių elementų grandinė nutrūksta medžio viduryje; metaduomenys ignoruojami, kai XMP sraute trūksta /Type /Metadata /Subtype /XML žymos arba xpacket apvalkalas yra suformuotas klaidingai. Kiekvienu atveju puslapio turinys išlieka tvarkingas, todėl paprastas failo atidarymas atrodo teisingas, o defektas išryškėja tik skydelyje, kurio niekas nepatikrino.
Du paprasti įpročiai padeda aptikti didžiąją dalį šių klaidų. Atidarykite sukurtą failą realioje skaityklėje ir pereikite per žymių skydelį bei keletą nuorodų – taip išbandysite nuorodų grafą taip, kaip tai darys skaitytuvas. Tda perskaitykite metaduomenis atskiru įrankiu ir įsitikinkite, kad Info žodynas bei XMP sutampa, nes šio neatitikimo joks spaudinėjimas neatskleis. Generuokite šį sluoksnį naudodami biblioteką, kuri pati rūpinasi nuorodų apskaita, ir dauguma šių spąstų bus išvengti. HotPDF Component Delphi ir C++Builder aplinkoms atskleidžia kontūro, anotacijų bei metaduomenų struktūras per dokumento lygio API, todėl jūs tiesiog aprašote žymių hierarchiją bei nuorodas, o biblioteka pati susieja nuorodas. Norintiems sužinoti apie objektų modelį, prie kurio tvirtinamos šios struktūros, techninė PDF failo struktūros apžvalga aprašo katalogą bei kryžminių nuorodų lentelę, nuo kurių jie priklauso.