Technical Article

Обяснение на метаданните, отметките и анотациите в PDF

Премахнете описанията на страниците и ще останете с тънък слой структура, която никой не печата, но от която зависи всеки четец, индексатор и архивираща система. Обектът на страницата не знае нищо за главата, към която принадлежи, за автора, който я е написал, или за бележката под линия, която води другаде. Това знание живее едно ниво нагоре, в три структури, прикрепени към каталога на документа: потоците от метаданни, дървото на отметките (outline tree) и масивите от анотации за всяка страница. Те споделят една характеристика, която прави лесно допускането на грешки при тях. Нито една от тези структури не оставя видими белези на страницата, така че даден файл може да се визуализира перфектно, но все пак да му липсват отметки, да противоречи на собственото си поле за автор или да насочва хипервръзка към обект на страница, който вече не съществува.

Това е слоят, който библиотеката за PDF излага като свойства на документа, API за отметки и извиквания за връзки или анотации, и слоят, който търсачките четат, за да решат за какво се отнася вашият документ. Обектният модел под него е разгледан в прегледа на структурата на PDF документи. Тук фокусът е строго върху това, което виси от каталога.

И трите структури се прикрепват към каталога. Пълно свързване на каталога, което ги обединява, изглежда така:

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

Четири записа, четири независими подсистеми. /Pages е видимият документ; /Outlines е дървото на отметките; /Metadata сочи към XMP потока; /Names достига до речника с имена на целия документ, който наред с други неща съдържа вградени прикачени файлове. Всеки запис е незадължителен и четец, който не открие нито един от тях, все пак ще покаже страниците. Тази незадължителност е именно причината навигационният слой да бъде първото нещо, което се разваля, когато файлът се редактира от инструменти, които разбират само от страници.

Две хранилища за метаданни, които не съвпадат

PDF пренася метаданните на документа на две места едновременно, и проблемите започват, когато те казват различни неща. Оригиналният механизъм е речникът с информация за документа, рефериран чрез /Info в trailer: плосък набор от двойки ключ-стойност за /Title, /Author, /Subject, /Keywords, /Creator, /Producer и двете дати. Той е прост и всеки четец го поддържа. PDF 2.0 отхвърля по-голямата част от него в полза на втория механизъм â€?XMP потока от метаданни.

XMP е самостоятелен XML документ, записан в RDF, съхраняван като поток, който каталогът достига чрез /Metadata и е маркиран като /Type /Metadata /Subtype /XML. За разлика от речника Info, заровен в структурата на обектите на PDF, XMP пакетът е проектиран да бъде извличан и анализиран самостоятелно от инструменти, които не знаят нищо за PDF. Ето примерен пакет:

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

Три подробности в този блок определят дали метаданните ще оцелеят при контакт с истински инструменти. Инструкциите за обработка на xpacket не са декорация: те рамкират пакета, така че екстракторът да може да го намери в по-голям поток от байтове, а програма за запис, която пропуска затварящия <?xpacket end="w"?>, произвежда файл, който се отваря добре, но проваля строгите валидатори. Типовете данни на свойствата също са от значение. dc:title е езикова алтернатива, обвита в rdf:Alt, докато dc:creator е подреден списък и приема rdf:Seq; излъчването на някое от тях като обикновен текстов възел е единствената най-често срещана грешка в XMP, толерирана от повечето четеци точно до момента, в който някой спре да я приема. Префиксите на пространствата от имена са конвенционални, но URI адресите, с които се свързват, са нормативни: парсерът се ориентира по URI, а не по префикса.

Строгото правило при наличието на две хранилища е, че те трябва да съвпадат. Ако /Info казва, че авторът е един човек, а dc:creator посочва друг, вие сте доставили документ, който отговаря на един и същ въпрос по два различни начина, и кой отговор печели зависи от това кое поле чете съответният инструмент. Библиотеката обикновено пише и двете вместо вас, но в момента, в който ги редактирате на ръка или обедините файлове от различни генератори, те се разминават. Третирайте речника Info като наследствена съвместимост, а XMP като единствен източник на истина, и регенерирайте и двете от един единствен набор от стойности, вместо да ги коригирате независимо. За PDF/A това се превръща в изискване за съответствие: ISO 19005 изисква XMP и забранява всяко свойство в Info, което противоречи на неговия еквивалент в XMP.

Дървото на отметките зад панела с отметки

Това, което програмата за преглед показва като панел с отметки (bookmarks panel), във файла е двойно свързано дърво от речници, наречено структура на документа (document outline). Каталогът сочи към коренния речник на структурата чрез /Outlines; коренът сочи към първия и последния си елементи на най-високо ниво; и всеки елемент е свързан със своите съседи и родител. Никъде няма обикновен масив от отметки. Цялата структура се възстановява чрез проследяване на препратките, което е именно причината една-единствена прекъсната връзка да направи така, че цял клон да изчезне от панела без никаква съобщена грешка.

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

Прочетете връзките и инвариантите ще станат очевидни. Всеки елемент сочи обратно към своя родител /Parent. Съседните елементи образуват верига чрез /Prev и /Next, като първият елемент пропуска /Prev, а последният пропуска /Next. Родителят именува своите първо и последно дете чрез /First and /Last, а децата между тях са достъпни само чрез преминаване по веригата на съседите. Сгрешете някоя връзка и провалът ще бъде тих: остарял /Next съкращава цяла глава, родител, чийто /Last не прекратява веригата правилно, оставя елементи сираци, а четецът изобразява само това, което може да достигне.

Полето /Count носи състояние, което често изненадва хората. В корена и при всеки разширен елемент то съдържа броя на наследниците, които в момента са видими; при свит елемент то е отрицателно число, чиято абсолютна стойност е броят на наследниците, които биха се появили при разширяване. Така /Count не е фиксиран структурен факт за дървото, а запазеното състояние (отворено или затворено) на панела, и генератор, който го кодира твърдо като положително число, отваря отново всеки клон, който авторът е възнамерявал да остави затворен.

Всеки елемент заслужава мястото си, като сочи нанякъде. /Title е това, което панелът показва; /Dest е мястото, където се приземява кликването. Дестинацията може да бъде вградена в елемента, както по-горе, или име, което се разрешава чрез речника с имена на документа, което е по-добрият избор, когато много отметки и връзки се насочват към същите места, тъй като коригирате преместената цел само на едно място. Библиотеката обикновено крие това дърво зад манипулатор на корена на структурата и методи, които добавят дочерни записи; в HotPDF документът излага OutlineRoot от тип THPDFDocOutlineObject и свързва /Prev, /Next, /Parent и /Count препратките вместо вас, докато добавяте елементи. Струва си да се възползвате от това, тъй като ръчната поддръжка на тези инварианти при редакции е мястото, където отметките най-често се чупят.

Дестинации: граматиката на това накъде води кликването

Както отметките, така и анотациите на връзки сочат към дестинации, а дестинацията е нещо повече от номер на страница. Това е масив, който именува обекта на страницата и след това указва чрез глагол във втория слот как програмата за преглед трябва да я рамкира. Най-често срещаният и най-злоупотребяваният е /XYZ, с форма [page /XYZ left top zoom]. Неговите три операнда са независими и всеки може да бъде null, което означава „оставÐ?това така, както го е настроил четецътâ€? Така [page /XYZ null null null] скача на страницата, без да докосва позицията на превъртане или мащабиране â€?обикновено това, което искате от връзка „къÐ?страницаâ€? Числата са в пространството на потребителя по подразбиране, измерено от долния ляв ъгъл с нарастване на y нагоре â€?същата координатна система, която използва съдържанието на страницата. Разработчиците, идващи от уеб дизайн, рефлекторно измерват отгоре и изпращат читателя в грешния край на страницата.

Фамилията /Fit заменя прецизното позициониране с устойчивост. [page /Fit] мащабира цялата страница в прозореца, [page /FitH top] напасва ширината на страницата спрямо даден горен ръб, а [page /FitR l b r t] мащабира правоъгълник, за да запълни изгледа. Тъй като те изчисляват мащаба от геометрията на страницата, а не от фиксирани координати, /Fit дестинацията продължава да се държи правилно дори след оразмеряване на страницата, докато дестинацията /XYZ с фиксирано мащабиране може да остави читателя да гледа празното поле. За съдържание на документ, /FitH с горната координата на раздела остарява много по-добре от /XYZ с налучкано мащабиране.

Анотации: всичко интерактивно, което не е съдържание на страницата

Анотацията е обект, който наслагва страницата, без да е част от нейния поток от съдържание. Връзки, лепкави бележки, маркери, уиджети за формуляри, икони за прикачени файлове, печати â€?всичко това са анотации, изброени в масива /Annots на страницата, върху която седят. Премахването на анотация от този масив я изтрива от страницата, въпреки че базовото съдържание е непокътнато. Това е целият замисъл: анотациите са слой за редактиране, отделен от чертежите под тях.

Всяка анотация споделя малък скелет. /Subtype именува вида, /Rect дава нейния обхващащ правоъгълник (bounding box) в координатите на страницата, а /Contents съдържа текст, който служи и за достъпно описание. Анотацията за връзка (Link) си струва да се проучи, тъй като идва в две форми: чиста дестинация и действие (action).

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

Правоъгълникът /Rect е активната зона; щракването в нея изпраща читателя до дестинацията, използвайки същата граматика, която използва и структурата на документа. Стойността /Border [0 0 0] върши важна работа, потискайки грозния правоъгълник по подразбиране, който програмите за преглед чертаят около връзките. Втората форма заменя чистата дестинация /Dest с действие /A (action), чийто подтип /S избира поведението: /GoTo в рамките на този файл, /GoToR за друг файл, /URI за уеб адрес, /Launch за стартиране на външна програма. Това последното заслужава подозрение. Действието /Launch, което стартира изпълним файл, е поведението, което прави PDF файловете вектор за зловреден софтуер, така че съвместимите програми за преглед го блокират или предупреждават шумно, и връзката се проваля при повечето четеци. Използвайте /URI и /GoTo и оставете /Launch настрана.

Анотациите за маркиране (като маркери и лепкави бележки) и анотациите за форми (като /Square) носят една особеност: техният вид на екрана не се подразбира автоматично от техния тип. Програмата за преглед изобразява своя собствена версия, освен ако не фиксирате външния вид с поток за външен вид â€?записа /AP, който реферира XObject форма, съдържаща операторите за чертане. Пропуснете го и едно и също маркиране може да изглежда различно в два четеца или преди и след преминаване през редактор. За всичко, чийто точен вид е важна част от документа, осигурете /AP. Прикачените файлове, между другото, използват същата машина: вграден поток от файлове и речник за спецификация на файлове, изведени на повърхността или като анотация /FileAttachment, или чрез дървото с имена /EmbeddedFiles под каталожното /Names.

Къде се чупи този слой и как да го уловим

Повтарящият се провал в целия този слой е висящата препратка (dangling reference). Отметките спират да се показват, когато каталогът няма запис /Outlines или веригата на съседите се скъса по средата на дървото; метаданните се игнорират, когато XMP потокът няма своята маркировка /Type /Metadata /Subtype /XML или xpacket обвивката е дефектна. Във всеки случай съдържанието на страницата е наред, така че обикновеното отваряне изглежда правилно и дефектът излиза наяве само в панела, който никой не е проверил.

Два лесни навика улавят по-голямата част от тези проблеми. Отворете готовия файл в реална програма за преглед и преминете през панела с отметки и извадка от връзки, което тества графа от препратки по начина, по който ще го направи четецът. След това прочетете метаданните обратно с отделен инструмент и потвърдете, че речникът Info и XMP съвпадат â€?несъответствие, което никакво кликане няма да разкрие. Генерирайте този слой чрез библиотека, която притежава счетоводството на връзките, и повечето от тези капани никога няма да се отворят. HotPDF Component за Delphi и C++Builder излага структурите за отметки, анотации и метаданни чрез програмни интерфейси (APIs) на ниво документ, така че вие описвате йерархията на отметките и връзките, а библиотеката свързва препратките. За обектния модел, към който се прикрепват тези структури, техническият преглед на структурата на PDF файлове обхваща каталога и таблицата с кръстосани препратки, от които зависят те.