Technical Article

Изграждане на минимален PDF файл ръчно: Петте обекта, които са ви необходими

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

Това ръководство изгражда най-малкия PDF файл, който действително изобразява нещо: една страница с думите „Hello, World!â€?с вграден шрифт върху хартия с размер US Letter. Готовият файл се нуждае от точно пет обекта и няколко реда администрация около тях. Първо ще напишем обектите, след което ще сглобим заглавната част (header), таблицата с кръстосани препратки (xref table) и завършващата част (trailer), които ги свързват във файл, който четецът ще приеме.

Петте обекта, за които четецът настоява

Четецът не сканира PDF файл отгоре надолу в търсене на съдържание. Той започва от завършващата част (trailer), следва препратка към каталога на документа (Catalog) и оттам обхожда верига от обекти. Всеки обект в тази верига трябва да съществува, иначе отварянето се проваля. За документ от една страница веригата е къса и всяка връзка има една единствена задача:

  • Catalog е коренът. Това е обектът, към който сочи trailer, и единственият му задължителен запис тук е препратка към дървото на страниците.
  • Pages е възелът на дървото на страниците. Той изброява страниците в документа и съобщава колко са те.
  • Page описва една физическа страница: нейния размер, ресурсите, с които чертае, и кой поток от съдържание (content stream) я изобразява.
  • Content stream (поток от съдържание) съдържа чертожните оператори â€?постфиксните команди, които разполагат текст и графики на тази страница.
  • Font (шрифт) декларира шрифта, към който се отнася потокът от съдържание. Използвайте един от 14-те стандартни шрифта и няма да ви се налага да вграждате нищо.

Непряк обект се записва като N 0 obj ... endobj, където N е номерът на обекта, а 0 е неговият номер на генериране (винаги 0 във файл, който пишете наново). На всяко друго място във файла вие сочите към този обект с препратка: 5 0 R означава „обекÑ?5â€? Тези препратки са свързването. Каталогът съдържа 2 0 R в нашата номерация, за да стигне до дървото на страниците, дървото на страниците съдържа препратка обратно надолу към страницата и т.н. Сгрешете някой номер и четецът ще последва висящ указател към нищото.

Имена, речници и потоци

Три елемента от синтаксиса носят почти всичко. Едно име (name) започва с наклонена черта: /Type, /Page, /F0. Имената са чувствителни към регистъра на буквите идентификатори, а не низове, и PDF ги използва за ключове в речници и за обозначаване на вида на обекта. Един речник (dictionary) е набор от двойки ключ-стойност, обвити в двойни ъглови скоби, където всеки ключ е име: << /Type /Page /MediaBox [0 0 612 792] >>. Стойностите могат да бъдат числа, имена, масиви в квадратни скоби, препратки или вложени речници. Повечето PDF обекти са речници.

Един поток (stream) е речник, последван от блок от байтове между ключовите думи stream и endstream. Там живеят операторите за изчертаване на страници, а в реалните файлове â€?също и компресираните изображения и вградените шрифтове. Речникът на потока описва байтовете; в производствен файл той трябва да съдържа запис /Length, даващ точния брой байтове, и често /Filter като /FlateDecode, когато данните са компресирани. Ще се доверим на инструмент, който да попълни /Length, защото броенето на байтове на ръка е частта от това упражнение без образователна стойност и с голям шанс за грешка с единица, която да счупи файла.

Писане на обектите

Ето ги петте обекта подред. Подробност за координатите, която трябва да имате предвид, преди да прочетете потока от съдържание: PDF измерва от долния ляв ъгъл на страницата в точки, където една точка е 1/72 от инча, а Y расте нагоре. Страница с размер US Letter е 612 на 792 точки, така че 50 700 се намира близо до горния ляв ъгъл, а не до долния.

1 0 obj
<< /Type /Catalog
   /Pages 2 0 R
>>
endobj

2 0 obj
<< /Type /Pages
   /Kids [3 0 R]
   /Count 1
>>
endobj

3 0 obj
<< /Type /Page
   /Parent 2 0 R
   /MediaBox [0 0 612 792]
   /Resources << /Font << /F0 4 0 R >> >>
   /Contents 5 0 R
>>
endobj

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

5 0 obj
<< /Length 44 >>
stream
BT
/F0 36 Tf
50 700 Td
(Hello, World!) Tj
ET
endstream
endobj

Прочетете препратките и структурата ще се изясни. Обект 1, каталогът, насочва своя запис /Pages entry към обект 2. Обект 2, дървото на страниците, изброява обект 3 в /Kids и декларира /Count 1. Обект 3, страницата, сочи с /Parent обратно към обект 2 (дървото и страницата се реферират взаимно, което е задължително), оразмерява се с /MediaBox, излага шрифта под локалното име /F0 в своите /Resources и посочва обект 5 като свое съдържание. Обект 4 е шрифтът: /BaseFont /Helvetica избира един от 14-те стандартни шрифта, които всеки съвместим четец вече има, така че няма какво да се вгражда. Обект 5 е потокът от съдържание.

Какво всъщност казва потокът от съдържание

Тялото на потока е малка програма на езика за описание на страници на PDF, който е постфиксен: операндите идват първи, последвани от оператора, който ги консумира. Пет реда вършат работата. BT и ET отварят и затварят текстов обект; всичко, което позиционира или показва текст, трябва да стои между тях. /F0 36 Tf задава текущия шрифт на ресурса с име /F0 на 36 точки (Tf означава „задаванÐ?на текстов шрифт и размерâ€?. 50 700 Td премества текстовата позиция на (50, 700) в координатите на страницата. (Hello, World!) Tj показва низа, който PDF записва като буквален текст в скоби, използвайки Tj, за да го изрисува на текущата позиция. Пропуснете BT/ET и строгият четец ще отхвърли текстовите оператори; забравете да зададете шрифт преди Tj и няма да има текущ шрифт, с който да се рисува.

Стойността /Length 44 в речника на потока е броят на байтовете между stream и endstream и трябва да бъде точен. Това е стойността, която си струва да се остави на инструмент, вместо да се броят новите редове на ръка, особено след като това дали вашият редактор записва краищата на редовете като LF или CRLF променя общия брой.

Заглавна част, xref и trailer

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

%PDF-1.7

Символът % започва коментар в синтаксиса на PDF, но четецът третира този конкретен коментар като сигнатура на формата и чете версията от него. Истинската програма за запис го следва веднага с втори коментарен ред от байтове с висок бит â€?намек за инструментите за трансфер на файлове, че файлът е двоичен и не трябва да бъде променян като обикновен текст.

В края на файла се намира таблицата с кръстосани препратки (cross-reference table), индексът, който прави възможен произволния достъп. Тя записва байтовото отместване (offset) на всеки обект от началото на файла, така че четецът може да отиде директно до обект 3, без да анализира първо обекти 1 и 2. Таблицата е твърда: записите са с фиксирана ширина, по 20 байта всеки, включително края на реда, форматирани като 10-цифрено отместване, 5-цифрено поколение, ключова дума (n за използвани, f за свободни) и двубайтов терминатор. Правилна таблица за нашите шест записа (обект 0 винаги е началото на списъка със свободни обекти) изглежда така:

xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000235 00000 n
0000000308 00000 n
trailer
<< /Size 6
   /Root 1 0 R
>>
startxref
408
%%EOF

Тези отмествания са крехката част от писането на PDF на ръка. Всяко едно от тях е точната байтова позиция, от която започва съответният N 0 obj, и всяко отместване се променя в момента, в който добавите символ някъде по-нагоре. Завършващата част (trailer) е входната точка, която четецът използва последна и първа: /Root 1 0 R указва каталога, /Size 6 посочва броя на обектите, а startxref 408 дава байтовото отместване на самата дума xref. Четецът отваря файла, скача до края, прочита startxref, търси таблицата с кръстосани препратки и оттам достига до каталога и всичко под него. %%EOF маркира последния байт.

Оставете инструмент да коригира броя на байтовете

Отместванията по-горе са илюстративни; на практика те ще бъдат грешни до момента, в който приключите с писането, тъй като зависят от точната байтова подредба на вашия файл. Вместо да ги преизчислявате, напишете структурата с временни стойности и оставете инструмент да изгради отново таблицата с кръстосани препратки и дължините на потоците. Безплатният, мултиплатформен pdftk прави това с едно преминаване:

pdftk hello-draft.pdf output hello.pdf

Той анализира вашите обекти, преизчислява всяко байтово отместване, попълва правилните /Length стойности, записва валидна xref таблица и trailer и излъчва hello.pdf. Отворете го в която и да е програма за преглед и ще получите една страница с „Hello, World!â€?с 36-точкова Helvetica близо до върха. Qpdf върши същата работа, а много програми за преглед също така ще поправят леко малформиран файл в движение. Точката на подкрепа от инструмент тук не е мързел; работата е в това, че аритметиката на отместванията е единствената част от формата с нулева концептуална стойност и най-висок процент грешки, така че автоматизирането й позволява на структурата да остане това, което учите.

Защо това се мащабира до реални документи

Нищо в доклад от сто страници не променя формата, който току-що изградихте. Каталогът все още се намира в корена, дървото на страниците все още събира страниците и всяка страница все още сочи към своите ресурси и поток от съдържание. Това, което расте, е ширината, а не гръбнакът: дървото на страниците се разклонява, така че четецът да може да пропусне цели поддървета, потоците от съдържание съдържат стотици оператори вместо пет, шрифтовете се вграждат като техни собствени поточни обекти с таблици за ширини и кодирания, а изображенията пристигат като потоци със специфични за изображенията филтри. Съвременните файлове също така са склонни да пакетират много обекти в компресирани потоци от обекти и да заменят обикновената xref таблица с поток от кръстосани препратки, поради което отварянето на истински PDF в текстов редактор обикновено показва стена от двоични данни. Моделът отдолу е идентичен с този във вашия ръчно изработен файл. За по-широката графика на обекти и това как каталогът, дървото на страниците и речниците с ресурси се отнасят в по-голям документ, задълбочената обиколка на структурата на PDF документи продължава оттам, където тази спира, а прегледът на файловата структура обхваща инкременталните актуализации и как trailer се свързва през ревизиите.

От писане на ръка към библиотека

Писането на обекти на ръка е учебно упражнение, а не производствена техника. В момента, в който се нуждаете от реални шрифтове, обвит текст, изображения или нещо повече от тривиална страница, счетоводството на байтовете, което pdftk поправи за вас, става цялата задача, и вие искате библиотека, която я притежава. Същите пет обекта все още се пишат, но библиотека изчислява всяко отместване, управлява речниците на шрифтовете и ресурсите и компресира потоците от съдържание, без да следите нито един байт. В Delphi и C++Builder, HotPDF Component свежда целия този файл до шепа извиквания: настройка на документа, извикване на BeginDoc, SetFont и TextOut за поставяне на същия поздрав, и след това EndDoc за записване на правилния каталог, дърво на страниците, xref и trailer. Разбирането на обектите отдолу е това, което ви позволява да разсъждавате върху резултата, когато даден документ не се визуализира по начина, по който сте очаквали.