Техническа статия

Обяснение на 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 в трейлъра: плосък набор от двойки ключ-стойност за /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

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

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

8 0 obj                                    % коренът на контура
<< /Type /Outlines /Count 4 /First 9 0 R /Last 9 0 R >>
endobj
9 0 obj                                    % най-високо ниво: глава
<< /Title (Chapter 1: Results)
   /Parent 8 0 R /Count 2
   /First 12 0 R /Last 15 0 R >>
endobj
12 0 obj                                   % първо дете
<< /Title (Introduction)
   /Parent 9 0 R /Next 15 0 R
   /Dest [3 0 R /XYZ 72 720 0] >>
endobj
15 0 obj                                   % второ дете, последен брат
<< /Title (Methodology)
   /Parent 9 0 R /Prev 12 0 R
   /Dest [3 0 R /Fit] >>
endobj

Прочетете връзките и инвариантите стават очевидни. Всеки елемент сочи обратно към своя /Parent. Братята и сестрите образуват верига чрез /Prev и /Next, като първият елемент пропуска /Prev, а последният пропуска /Next. Родителят назовава своето първо и последно дете чрез /First и /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 дава неговата ограничаваща кутия в координати на страницата, а /Contents съдържа текст, който служи и като достъпно описание. Анотацията за връзка е случаят, който си струва да се проучи, защото се предлага в две форми: гола дестинация и действие

12 0 obj                                    % връзка към дестинация
<< /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                                    % връзка, която изпълнява действие
<< /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, чийто подтип /S избира поведението: /GoTo в рамките на този файл, /GoToR за друг файл, /URI за уеб адрес, /Launch за стартиране на външна програма. Това последното заслужава подозрение. /Launch, който стартира изпълним файл, е поведението, което прави PDF файловете вектор за зловреден софтуер, така че съвместимите програми за преглед го блокират или подканват високо и връзката се проваля за повечето читатели. Посегнете към /URI и /GoTo и оставете /Launch намира

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

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

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

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