Technical Article

Логическият обектен модел на PDF: Типове, препратки и структура

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

Това е логическият слой на PDF, дефиниран в клауза 7.3 на ISO 32000-1, и той се намира едно ниво над физическата структура на файла (заглавна част, тяло, таблица с кръстосани препратки и trailer, което е предмет на самостоятелно разглеждане в техническия преглед на структурата на PDF файлове). Логическият модел е това, което тези байтове означават след анализ. Програмата за преглед чете файла отзад напред, за да намери trailer, следва го до корена и оттам документът се разгръща като обекти, рефериращи към обекти. Това е частта, за която разсъждавате, когато отстранявате грешки в повредена страница, пишете парсер или се доверявате на библиотека за сглобяване на документ.

Осем типа обекти и нищо друго

PDF дефинира точно осем основни типа обекти. Всяка стойност в документа е една от тях, което поддържа формата управляем въпреки големия му обхват.

Булевите стойности са ключовите думи true и false. Те включват и изключват флагове, например дали дадена анотация се отпечатва.

Числата идват в две разновидности, които спецификацията третира как един тип: цели числа като 42 и реални числа като 3.14 или -0.002. PDF няма експоненциален запис, така че никога няма да видите 1e6 в съвместим файл. Координати, размери на шрифтове и ъгли на въртене â€?всичко това са числа.

Низовете съдържат последователности от байтове, записани или в скоби, (Hello), или в ъглови скоби като шестнадесетични стойности, <48656C6C6F>. Двата записа кодират идентично съдържание; шестнадесетичният е изходът при байтове, неудобни за поставяне в скоби. Низовете пренасят текст, но те са на първо място байтове, което е от значение в момента, в който боравите с нещо извън ASCII.

Имената (Names) са атомни токени, въведени с наклонена черта: /Type, /Pages, /MediaBox. Името не е низ; то е идентификатор, използван като ключ в речник или изброена стойност, и две имена са равни само ако съвпадат байт по байт. Наклонената черта е синтаксис, а не част от името. Това обърква новодошлите, които третират /Times-Roman и низа (Times-Roman) като взаимозаменяеми â€?форматът обаче не го прави.

Масивите са подредени, разнородни списъци в квадратни скоби: [0 0 612 792] е правоъгълник на страница, а масивът може да смесва типове свободно, включително препратки към други обекти. Речниците са основната движеща сила. Записани между << и >>, речниците свързват ключове-имена със стойности от всякакъв тип, и почти всяка значима структура в PDF â€?страница, каталог, шрифт, анотация â€?е речник с ключ /Type, деклариращ какъв е той.

Потоците (Streams) са речници с опашка от необработени байтове между ключовите думи stream и endstream. Речникът описва байтовете (тяхната дължина и всички филтри, като FlateDecode, които ги компресират), а байтовете пренасят големия полезен товар: инструкции за съдържанието на страниците, вградени програми за шрифтове, изображения. Потокът е мястото, където PDF поставя всичко, което е твърде голямо или твърде двоично, за да стои вградено (inline).

Осмият тип е null обектът, ключовата дума null. Това е реална стойност, различна от липсата на ключ. Запис в речник, зададен на null, се третира като несъществуващ, а препратка, която се разрешава до несъществуващ обект, също дава null вместо грешка. Това снизходително поведение е умишлено: то позволява на повредения файл да работи с намалена функционалност, вместо изобщо да откаже да се отвори. Няма девети тип; всичко, което PDF изразява, идва от начина, по който тези осем се комбинират.

Директни стойности, непреки обекти и препратки

Всеки от тези осем типа може да се появи по два начина. Един директен обект се записава на място, като например числото 612 в масива MediaBox. На непряк (индиректен) обект се дава идентичност, така че други обекти да могат да сочат към него: две цели числа â€?номер на обекта и номер на поколението, обвиващи дефиницията в obj и endobj:

12 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj

Това е обект 12, поколение 0 â€?речник на шрифт. На всяко друго място във файла друг обект се отнася към него с непряка препратка: същите две числа, последвани от ключовата дума R, 12 0 R. Препратката е указател. Когато речникът с ресурси на дадена страница съдържа /Font << /F1 12 0 R >>, той определя обект 12 като шрифт зад ресурсното име /F1, без да копира дефиницията на шрифта в самата страница.

Номерът на поколението съществува за изтривания и повторна употреба. Когато даден обект се освободи и неговият слот се използва повторно, поколението се увеличава, така че остаряла препратка 12 0 R да не може да се разреши до новия обитател на слот 12. Наново записаните файлове са почти изцяло поколение 0, но тежко редактиран файл може да носи по-големи числа, а парсер, който игнорира поколението, в крайна сметка ще прочете грешния обект.

Непрякото рефериране (indirection) е това, което прави PDF ефективен и редактируем. Един шрифт, изображение или цветово пространство може да бъде дефинирано веднъж и реферирано от сто страници. Малка промяна може да бъде добавена като нова ревизия, която замества един-единствен обект, вместо да пренаписва целия файл. Таблицата с кръстосани препратки е индексът, който превръща номера на обекта в байтово отместване, така че четецът да скача директно до 12 0 obj без сканиране, но това е физическа оптимизация. Логически всичко, което трябва да знаете, е, че 12 0 R означава „обектъÑ? идентифициран като 12 0â€?

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

Разрешаването на препратките трябва да започне отнякъде, и това място е записът /Root на trailer, който сочи към каталога на документа (document catalog): корена на графа от обекти, речник с /Type /Catalog. Четецът достига първо до него, тъй като trailer се намира пръв, и оттам всяка друга част на документа е достъпна чрез проследяване на препратки.

Каталогът носи само два строго задължителни записа: своя /Type и /Pages â€?непряка препратка към корена на дървото на страниците. Останалите са незадължителни и описват поведението на целия документ, а не съдържанието: /Outlines сочи към дървото с отметки, /Names съдържа дървета с имена, индексирани по низове, /Metadata реферира XMP поток от метаданни, а /PageMode и /PageLayout подсказват как програмата за преглед трябва да отвори документа. Никое от тях не е необходимо за изобразяването на дадена страница; те просто конфигурират изживяването около страниците. Структурите с отметки, метаданни и анотации, закрепени за каталога, са разгледани в статията за PDF метаданни, отметки и анотации.

Диаграмата по-долу показва къде се намира тялото на обектите в околния файл. Каталогът и дървото на страниците живеят в това тяло като обикновени непреки обекти; заглавната част, таблицата с кръстосани препратки и trailer около тях са физическото скеле, което позволява на четеца да ги локализира.

Diagram of a PDF file's four physical sections: a version header, a body holding the document objects including the catalog and page tree, a cross-reference table of object offsets, and a trailer pointing at the root

Дървото на страниците: балансирана йерархия от страници

От /Pages документът се разклонява в дървото на страниците (page tree), където изборът на PDF за граф пред плосък списък се отплаща. Страниците не се съхраняват като проста последователност; те висят от дърво, чиито вътрешни възли са възли на дървото на страниците (/Type /Pages), а листата са обекти на страниците (/Type /Page). Всеки вътрешен възел изброява своите наследници в масив /Kids и записва в /Count колко крайни страници (листа) живеят под него. Всеки възел, с изключение на корена, носи препратка /Parent обратно нагоре, така че дървото се обхожда и в двете посоки.

2 0 obj                                  % root of the page tree
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 3 >>
endobj

3 0 obj                                  % a leaf page
<< /Type /Page /Parent 2 0 R
   /MediaBox [0 0 612 792]
   /Resources << /Font << /F1 12 0 R >> >>
   /Contents 5 0 R >>
endobj

4 0 obj                                  % an interior node grouping two more pages
<< /Type /Pages /Parent 2 0 R /Kids [6 0 R 7 0 R] /Count 2 >>
endobj

Тук обект 2 е коренът с три страници под него: крайната страница 3, плюс още две, достъпни през междинния възел 4. Броят /Count от 3 на корена трябва да бъде равен на общия брой листа под него, а бройка, която се разминава с реалната структура, е често срещан начин ръчно редактиран файл да се счупи. Смисълът на дървото е локалността на достъпа. Четецът, отварящ страница 900 от документ с хиляди страници, не обхожда 900 обекта; той се спуска през шепа възли, тъй като добре оформеното дърво остава плитко и балансирано. Изграждането на такова дърво на ръка е достатъчно сложно, за да си струва да се види в детайли, което правим в ръководството за изграждане на PDF документ от нулата.

Дървото се отплаща повторно чрез наследяване. Атрибути на страниците â€?/Resources, /MediaBox, /CropBox и /Rotate â€?могат да бъдат зададени на междинен възел и пропуснати в отделните страници, които след това наследяват стойността на най-близкия предшественик. Задайте /MediaBox веднъж на корена и всяко листо получава същия размер на страницата без повторение; страница, която трябва да се различава, декларира своя собствена стойност. Това е единственото място в модела на обектите, където значението на дадена стойност зависи от позицията на обекта в дървото, а не само от неговото собствено съдържание.

Какво всъщност съдържа крайната страница

Обектът на страницата е точката на свързване между структурния модел и видимото съдържание. Неговият запис /Contents реферира един или повече потоци от съдържание (content streams) â€?чертожните оператори, които изписват текст и рисуват графики върху страницата. Неговият речник /Resources именува шрифтовете, изображенията и цветовите пространства, на които разчитат тези оператори, като всеки запис е непряка препратка към обект, споделен между страниците. Записът /MediaBox дава правоъгълника на страницата в точки (1/72 от инча), а записи като /Rotate и /CropBox регулират как се представя тя.

Това разделение на труда е целият модел в миниатюра. Речникът на страницата е структурата: типизирани записи и препратки, които казват каква е страницата и с какво рисува. Потокът от съдържание съдържа инструкциите: отделен, компресируем блок, който казва как да се рисува. Шрифтът зад /F1 е споделен ресурс, дефиниран веднъж и посочван навсякъде, където се използва. Речник, поток и препратка си сътрудничат за изобразяването на една страница, и същите модели се мащабират до целия документ. Операторите на потока от съдържание в този блок са разгледани отделно за текст и шрифтове и за графики и визуални елементи.

Защо си струва да познавате този модел

Повечето разработчици се сблъскват с обектния модел само когато нещо се счупи: страницата се визуализира празна, защото препратката /Contents виси, текстът излиза като квадратчета, защото ресурсът на шрифта никога не е бил вграден, или даден инструмент съобщава за /Count, който не съвпада със страниците, които може да намери. Всяко от тези неща е твърдение за графа и четенето на графа директно е по-добро от гадаенето. Осемте типа и правилото за рефериране са достатъчно малък речников запас, който да държите в главата си, и след като видите PDF като обекти, сочещи към обекти, повредените файлове спират да бъдат непроницаеми.

Въпреки това, писането на модела на ръка рядко е правилното решение извън процеса на обучение. Поддържането на отместванията на кръстосаните препратки, номерата на поколенията, броя на дърветата на страниците и дължините на потоците съгласувани при редакции е вид администрация, която библиотеката съществува да управлява. В производствена среда, една зряла библиотека за разработка на PDF управлява графа от обекти, оставяйки ви да мислите за страници и съдържание. Познаването на модела все пак се отплаща: вие разбирате какво строи библиотеката отдолу и защо.