Articol tehnic

Construiți un PDF minimal: cele cinci obiecte necesare

Un PDF este, în esență, un container în text simplu. Deschideți majoritatea fișierelor într-un editor hexazecimal și partea de sus este lizibilă: un comentariu de versiune, apoi o serie de obiecte numerotate, apoi un mic index și un indicator chiar la final, care îi spune unui cititor de unde să înceapă. Îndepărtați compresia și formatul devine destul de abordabil încât să puteți tasta un document funcțional într-un editor de text și un vizualizator să îl deschidă. Făcând asta o dată, învățați mai mult despre cum se ține PDF-ul laolaltă decât din oricâtă lectură a specificației, pentru că trebuie să cablați obiectele unul la altul manual, iar fișierul refuză să se deschidă până nimeriți cablarea corect

Acest parcurs construiește cel mai mic PDF care chiar randează ceva: o pagină, cuvintele „Hello, World!” cu un font încorporat în standard, pe hârtie US Letter. Fișierul terminat are nevoie de exact cinci obiecte și de câteva linii de evidență în jurul lor. Vom scrie mai întâi obiectele, apoi vom asambla antetul, tabelul de referințe încrucișate și trailerul care le leagă într-un fișier pe care un cititor îl va accepta

Cele cinci obiecte pe care un vizualizator le cere neapărat

Un cititor nu scanează un PDF de sus în jos în căutarea conținutului. Pornește de la trailer, urmează o referință către catalogul documentului și de acolo parcurge un lanț de obiecte. Fiecare obiect de pe acel lanț trebuie să existe, altfel deschiderea eșuează. Pentru un document de o pagină lanțul este scurt, iar fiecare verigă are o singură sarcină:

  • Catalog este rădăcina. Este obiectul spre care indică trailerul, iar singura lui intrare obligatorie aici este o referință către arborele de pagini
  • Pages este nodul arborelui de pagini. El listează paginile din document și raportează câte sunt
  • Page descrie o pagină fizică: dimensiunea ei, resursele cu care desenează și fluxul de conținut care o pictează
  • Fluxul de conținut ține operatorii de desenare, comenzile postfixate care așază text și grafică pe acea pagină
  • Font declară fontul la care se referă fluxul de conținut. Folosiți unul dintre cele 14 fonturi standard și nu trebuie să încorporați nimic
PDF: diagrama lanțului de referințe: trailerul indică spre catalog, catalogul spre arborele de pagini, arborele de pagini spre pagină, pagina spre fluxul de conținut și spre obiectul de font
Cele cinci obiecte formează un singur lanț de referințe: cititorul pornește de la trailer, urmează /Root în catalog, coboară pe arborele de pagini până la unica pagină, iar acea pagină indică atât spre fluxul ei de conținut, cât și spre fontul ei. Fiecare săgeată este o referință indirectă precum “4 0 R”

Fiecare obiect este numerotat și adresabil. Un obiect indirect se scrie ca N 0 obj ... endobj, unde N este numărul obiectului, iar 0 este numărul lui de generație (întotdeauna 0 într-un fișier scris de la zero). Oriunde altundeva în fișier indicați spre acel obiect printr-o referință: 5 0 R înseamnă „obiectul 5”. Acele referințe sunt cablarea. Catalogul ține 2 0 R în numerotarea noastră ca să ajungă la arborele de pagini, arborele de pagini ține o referință în jos, spre pagină, și așa mai departe. Greșiți un număr și cititorul urmează un indicator suspendat către nimic

Nume, dicționare și fluxuri

Trei elemente de sintaxă poartă aproape totul. Un nume începe cu o bară oblică: /Type, /Page, /F0. Numele sunt identificatori sensibili la majuscule, nu șiruri, iar PDF le folosește pentru chei de dicționar și pentru a eticheta ce este un obiect. Un dicționar este un set de perechi cheie-valoare învelite în paranteze unghiulare duble, unde fiecare cheie este un nume: << /Type /Page /MediaBox [0 0 612 792] >>. Valorile pot fi numere, nume, tablouri în paranteze drepte, referințe sau dicționare imbricate. Majoritatea obiectelor PDF sunt dicționare

Un flux este un dicționar urmat de un bloc de octeți între cuvintele-cheie stream și endstream. Acolo trăiesc operatorii de desenare a paginii, iar în fișierele reale trăiesc și imaginile comprimate și fonturile încorporate. Dicționarul fluxului descrie octeții; într-un fișier de producție el trebuie să poarte o intrare /Length care dă numărul exact de octeți și adesea un /Filter precum /FlateDecode atunci când datele sunt comprimate. Ne vom sprijini pe o unealtă ca să completeze /Length, pentru că numărarea octeților de mână este partea din acest exercițiu fără niciun câștig educativ și cu șanse mari de a greși cu unu, ceea ce strică fișierul

Scrierea obiectelor

Iată cele cinci obiecte în ordine. Detaliul de coordonate de reținut înainte de a citi fluxul de conținut: PDF măsoară din colțul din stânga jos al paginii, în puncte, unde un punct este 1/72 de țol, iar Y crește în sus. O pagină US Letter are 612 pe 792 de puncte, deci 50 700 stă aproape de stânga sus, nu de jos

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

Citiți referințele și structura iese la iveală. Obiectul 1, catalogul, își îndreaptă intrarea /Pages spre obiectul 2. Obiectul 2, arborele de pagini, listează obiectul 3 în /Kids și declară /Count 1. Obiectul 3, pagina, îndreaptă /Parent înapoi în sus, spre obiectul 2 (arborele și pagina se referă reciproc, ceea ce este obligatoriu), se dimensionează cu /MediaBox, expune fontul sub numele local /F0 în /Resources și numește obiectul 5 drept conținutul său. Obiectul 4 este fontul: /BaseFont /Helvetica alege unul dintre cele 14 fonturi standard pe care orice cititor conform le are deja, deci nu e nimic de încorporat. Obiectul 5 este fluxul de conținut

Ce spune de fapt fluxul de conținut

Corpul fluxului este un program minuscul în limbajul de descriere a paginii din PDF, care este postfixat: operanzii vin primii, apoi operatorul care îi consumă. Cinci linii fac toată treaba. BT și ET deschid și închid un obiect de text; tot ce poziționează sau afișează text trebuie să stea între ele. /F0 36 Tf setează fontul curent la resursa numită /F0, la 36 de puncte (Tf înseamnă „setează fontul și dimensiunea textului”). 50 700 Td mută poziția textului la (50, 700) în coordonatele paginii. (Hello, World!) Tj afișează șirul, pe care PDF îl scrie ca text literal între paranteze, folosind Tj ca să îl picteze la poziția curentă. Lăsați deoparte BT/ET și un cititor strict respinge operatorii de text; uitați să setați un font înainte de Tj și nu există niciun font curent cu care să deseneze

/Length 44 din dicționarul fluxului este numărul de octeți dintre stream și endstream și trebuie să fie exact. Aceasta este valoarea pe care merită să o predați unei unelte, în loc să numărați sfârșiturile de linie manual, mai ales că faptul că editorul dumneavoastră scrie terminatoarele de linie ca LF sau CRLF schimbă totalul

Antet, xref și trailer

Obiectele sunt conținutul. Trei piese structurale le transformă într-un fișier. Prima este antetul, chiar prima linie, care numește formatul și versiunea:

%PDF-1.7

% începe un comentariu în sintaxa PDF, dar un cititor tratează acest comentariu anume drept semnătura formatului și citește versiunea din el. Un scriitor real îl urmează imediat cu o a doua linie de comentariu, cu octeți cu bitul înalt setat, un indiciu pentru uneltele de transfer de fișiere că fișierul este binar și nu trebuie mutilat ca text

La finalul fișierului vine tabelul de referințe încrucișate, indexul care face posibil accesul aleatoriu. El înregistrează poziția în octeți a fiecărui obiect de la începutul fișierului, ca un cititor să poată sări direct la obiectul 3, fără să analizeze mai întâi obiectele 1 și 2. Tabelul este rigid: intrările au lățime fixă, câte 20 de octeți fiecare, inclusiv terminatorul de linie, formatate ca o poziție de 10 cifre, o generație de 5 cifre, un cuvânt-cheie (n pentru în uz, f pentru liber) și un terminator de doi octeți. Un tabel corect pentru cele șase intrări ale noastre (obiectul 0 este mereu capul listei de liberi) arată astfel:

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

Acele poziții sunt partea fragilă a scrierii unui PDF manual. Fiecare este poziția exactă, în octeți, la care începe N 0 obj corespunzător, iar fiecare poziție se deplasează în clipa în care adăugați un caracter oriunde deasupra ei. Trailerul este punctul de intrare pe care un cititor îl folosește ultimul și primul: /Root 1 0 R numește catalogul, /Size 6 declară numărul de obiecte, iar startxref 408 dă poziția în octeți a cuvântului xref însuși. Un cititor deschide fișierul, sare la final, citește startxref, se poziționează la tabelul de referințe încrucișate și de acolo ajunge la catalog și la tot ce este dedesubt. %%EOF marchează ultimul octet

Lăsați o unealtă să repare numărătorile de octeți

Pozițiile de mai sus sunt ilustrative; în practică vor fi greșite până terminați de tastat, pentru că depind de aranjarea exactă în octeți a fișierului dumneavoastră. În loc să le recalculați, scrieți structura cu valori de substituție și lăsați un utilitar să reconstruiască tabelul de referințe încrucișate și lungimile fluxurilor. Utilitarul gratuit și multiplatformă pdftk face asta într-o singură trecere:

pdftk hello-draft.pdf output hello.pdf

El vă analizează obiectele, recalculează fiecare poziție în octeți, completează valorile /Length corecte, scrie un tabel xref și un trailer valide și emite hello.pdf. Deschideți-l în orice vizualizator și obțineți o pagină cu „Hello, World!” în Helvetica de 36 de puncte, aproape de partea de sus. Qpdf face aceeași treabă, iar multe vizualizatoare vor repara din mers și un fișier ușor malformat. Rostul sprijinirii pe o unealtă aici nu este lenea; este că aritmetica pozițiilor este singura parte a formatului cu zero conținut conceptual și cu cea mai mare rată de eroare, deci automatizarea ei lasă structura să rămână lucrul pe care îl învățați

De ce se scalează asta la documente reale

Nimic dintr-un raport de o sută de pagini nu schimbă forma pe care tocmai ați construit-o. Catalogul stă tot la rădăcină, arborele de pagini adună tot paginile, iar fiecare pagină indică tot spre resursele ei și spre un flux de conținut. Ce crește este lățimea, nu coloana vertebrală: arborele de pagini se ramifică pentru ca un cititor să poată sări peste subarbori întregi, fluxurile de conținut poartă sute de operatori în loc de cinci, fonturile ajung încorporate ca obiecte-flux proprii, cu tabele de lățimi și codificări, iar imaginile sosesc ca fluxuri cu filtre specifice imaginilor. Fișierele moderne tind de asemenea să împacheteze multe obiecte în fluxuri de obiecte comprimate și să înlocuiască tabelul xref simplu cu un flux de referințe încrucișate, motiv pentru care deschiderea unui PDF real într-un editor de text arată de obicei un zid de binar. Modelul de dedesubt este identic cu cel din fișierul dumneavoastră făcut manual. Pentru graful de obiecte mai larg și pentru felul în care se raportează catalogul, arborele de pagini și dicționarele de resurse într-un document mai mare, turul aprofundat al structurii documentului PDF preia de unde se oprește acesta, iar privirea de ansamblu asupra structurii fișierului acoperă actualizările incrementale și modul în care trailerul se înlănțuie peste revizii

De la scrisul de mână la o bibliotecă

Tastarea obiectelor manual este un exercițiu de învățare, nu o tehnică de producție. În clipa în care aveți nevoie de fonturi reale, text împăturit, imagini sau de mai mult decât o pagină banală, evidența octeților pe care pdftk a peticit-o pentru dumneavoastră devine toată munca, și vreți o bibliotecă care să și-o asume. Aceleași cinci obiecte tot sunt scrise, dar o bibliotecă calculează fiecare poziție, gestionează dicționarele de fonturi și de resurse și comprimă fluxurile de conținut fără să urmăriți dumneavoastră vreun octet. În Delphi și C++Builder, HotPDF Delphi Component reduce tot acest fișier la o mână de apeluri: pregătiți documentul, apelați BeginDoc, SetFont și TextOut ca să plasați același salut, apoi EndDoc ca să scrie un catalog, un arbore de pagini, un xref și un trailer corecte. Înțelegerea obiectelor de dedesubt este ce vă lasă să raționați despre rezultat atunci când un document nu se randează cum vă așteptați