Un fișier PDF este, în esență, o colecție de obiecte care indică unele spre altele. Îndepărtați compresia, evidența referințelor încrucișate și pozițiile în octeți, iar ce rămâne este un graf: un set mic de valori tipizate, cablate între ele prin referințe, înrădăcinate într-un singur obiect pe care cititorul știe să îl găsească. Tot ce poate exprima un PDF, de la un paragraf de text la un font încorporat sau la o semnătură digitală, este construit din opt tipuri primitive de obiecte și din regula care lasă un obiect să se refere la altul. Învățați-le pe acelea, iar restul formatului se citește ca o compoziție, nu ca un mister
Acesta este stratul logic al PDF, definit în ISO 32000-1 clauza 7.3, și stă cu un nivel deasupra aranjării fizice a fișierului (antetul, corpul, tabelul de referințe încrucișate și trailerul, care are propriul subiect în privirea de ansamblu tehnică asupra structurii fișierului PDF). Modelul logic este ce înseamnă acei octeți după analiză. Un vizualizator citește fișierul de la coadă ca să găsească trailerul, îl urmează până la rădăcină, iar de acolo documentul se desfășoară ca obiecte care referă obiecte. Aceasta este partea despre care raționați când depanați o pagină malformată, scrieți un analizor sau vă încredeți într-o bibliotecă să asambleze un document
Opt tipuri de obiecte și nimic altceva
PDF definește exact opt tipuri de obiecte de bază. Fiecare valoare dintr-un document este unul dintre ele, ceea ce ține formatul abordabil în ciuda anvergurii lui
Booleenele sunt cuvintele-cheie true și false. Ele aprind și sting indicatori, cum ar fi dacă o adnotare se tipărește
Numerele vin în două forme pe care specificația le tratează ca pe un singur tip: întregi precum 42 și reale precum 3.14 sau -0.002. PDF nu are notație cu exponent, deci nu veți vedea niciodată 1e6 într-un fișier conform. Coordonatele, dimensiunile fonturilor și unghiurile de rotație sunt toate numere
Șirurile țin secvențe de octeți, scrise fie între paranteze, (Hello), fie între paranteze unghiulare, în hexazecimal, <48656C6C6F>. Ambele notații codifică un conținut identic; hexazecimalul este supapa pentru octeții incomozi între paranteze. Șirurile poartă text, dar sunt octeți în primul rând, ceea ce contează din clipa în care tratați ceva dincolo de ASCII
Numele sunt tokenuri atomice introduse printr-o bară oblică: /Type, /Pages, /MediaBox. Un nume nu este un șir; este un identificator, folosit ca cheie de dicționar sau ca valoare enumerată, iar două nume sunt egale doar dacă se potrivesc octet cu octet. Bara oblică este sintaxă, nu parte din nume. Asta îi încurcă pe începătorii care tratează /Times-Roman și șirul (Times-Roman) ca interschimbabile; formatul nu o face
Tablourile sunt liste ordonate și eterogene între paranteze drepte: [0 0 612 792] este un dreptunghi de pagină, iar un tablou poate amesteca liber tipuri, inclusiv referințe către alte obiecte. Dicționarele sunt calul de povară. Scrise între << și >>, un dicționar leagă chei de tip nume de valori de orice tip, iar aproape orice structură cu sens din PDF, pagină, catalog, font, adnotare, este un dicționar cu o cheie /Type care declară ce este
Fluxurile sunt dicționare cu o coadă de octeți bruți între cuvintele-cheie stream și endstream. Dicționarul descrie octeții (lungimea lor și eventualele filtre precum FlateDecode care îi comprimă), iar octeții poartă încărcătura voluminoasă: instrucțiunile de conținut al paginii, programele de fonturi încorporate, imaginile. Un flux este locul în care PDF pune orice este prea mare sau prea binar ca să stea în linie
Al optulea tip este obiectul null, cuvântul-cheie null. Este o valoare reală, distinctă de absența unei chei. O intrare de dicționar setată pe null este tratată ca și cum nu ar fi prezentă, iar o referință care se rezolvă la un obiect inexistent dă tot null, nu o eroare. Acel comportament îngăduitor este deliberat: lasă un fișier deteriorat să se degradeze în loc să refuze deschiderea. Nu există un al nouălea tip; tot ce exprimă PDF vine din felul în care se combină aceste opt
Valori directe, obiecte indirecte și referințe
Oricare dintre acele opt tipuri poate apărea în două feluri. Un obiect direct este scris la fața locului, precum 612 din interiorul unui tablou MediaBox. Un obiect indirect primește o identitate, ca alte obiecte să poată indica spre el: două numere întregi, un număr de obiect și un număr de generație, care învelesc definiția între obj și endobj:
12 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj
Acesta este obiectul 12, generația 0, un dicționar de font. Oriunde altundeva în fișier, alt obiect se referă la el printr-o referință indirectă: aceleași două numere urmate de cuvântul-cheie R, adică 12 0 R. Referința este un indicator. Când dicționarul de resurse al unei pagini spune /Font << /F1 12 0 R >>, el numește obiectul 12 drept fontul din spatele numelui de resursă /F1, fără să copieze definiția fontului în pagină
Numărul de generație există pentru ștergeri și reutilizare. Când un obiect este eliberat, iar slotul lui refolosit, generația crește, ca un 12 0 R învechit să nu se poată rezolva la noul ocupant al slotului 12. Fișierele proaspăt scrise sunt aproape toate de generație 0, dar un fișier editat masiv poate purta numere mai mari, iar un analizor care ignoră generația va citi până la urmă obiectul greșit
Indirecția este ce face PDF-ul eficient și editabil. Un font, o imagine sau un spațiu de culoare pot fi definite o dată și referite din o sută de pagini. O mică schimbare poate fi adăugată ca revizie nouă care înlocuiește un singur obiect, în loc să rescrie fișierul. Tabelul de referințe încrucișate este indexul care transformă un număr de obiect într-o poziție în octeți, ca cititorul să sară direct la 12 0 obj fără să scaneze, dar aceasta este o optimizare fizică. Logic, tot ce trebuie să știți este că 12 0 R înseamnă „obiectul identificat drept 12 0.”
Catalogul: locul de unde începe orice document
Rezolvarea referințelor trebuie să înceapă de undeva, iar acel undeva este intrarea /Root din trailer, care indică spre catalogul documentului: rădăcina grafului de obiecte, un dicționar cu /Type /Catalog. Cititorul ajunge la el primul pentru că trailerul este găsit primul, iar de acolo orice altă parte a documentului este accesibilă urmând referințe
Catalogul poartă doar două intrări strict obligatorii: /Type al lui și /Pages, o referință indirectă către rădăcina arborelui de pagini. Restul sunt opționale și descriu comportamentul la nivel de document, nu conținutul: /Outlines indică spre arborele de marcaje, /Names ține arbori de nume indexați după șir, /Metadata referă un flux de metadate XMP, iar /PageMode și /PageLayout sugerează cum ar trebui să deschidă documentul un vizualizator. Niciuna dintre acestea nu este necesară pentru randarea unei pagini; ele configurează experiența din jurul paginilor. Structurile de marcaje, metadate și adnotări care atârnă de catalog sunt tratate în articolul despre metadate, marcaje și adnotări PDF
Diagrama de mai jos arată unde stă corpul de obiecte în fișierul din jur. Catalogul și arborele de pagini trăiesc în interiorul acelui corp ca obiecte indirecte obișnuite; antetul, tabelul de referințe încrucișate și trailerul din jurul lor sunt schela fizică ce îi permite unui cititor să le localizeze

Arborele de pagini: o ierarhie echilibrată de pagini
De la /Pages, documentul se ramifică în arborele de pagini, unde alegerea PDF-ului de a folosi un graf în locul unei liste plate dă roade. Paginile nu sunt stocate ca o secvență simplă; ele atârnă dintr-un arbore ale cărui noduri interioare sunt noduri de arbore de pagini (/Type /Pages) și ale cărui frunze sunt obiecte de pagină (/Type /Page). Un nod interior își listează copiii într-un tablou /Kids și înregistrează, în /Count, câte pagini-frunză trăiesc sub el. Fiecare nod în afară de rădăcină poartă o referință /Parent înapoi în sus, deci arborele se parcurge în ambele direcții
/Count și poate transmite mai departe atribute precum /MediaBox către fiecare descendent2 0 obj % rădăcina arborelui de pagini
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 3 >>
endobj
3 0 obj % o pagină-frunză
<< /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 % un nod interior care grupează încă două pagini
<< /Type /Pages /Parent 2 0 R /Kids [6 0 R 7 0 R] /Count 2 >>
endobj
Aici obiectul 2 este rădăcina, cu trei pagini sub el: pagina-frunză 3, plus alte două accesibile prin nodul interior 4. /Count al rădăcinii, egal cu 3, trebuie să fie egal cu totalul frunzelor de sub el, iar un contor care nu se potrivește cu structura reală este un mod frecvent în care un fișier editat manual o ia razna. Rostul arborelui este localitatea accesului. Un cititor care deschide pagina 900 dintr-un document de o mie de pagini nu parcurge 900 de obiecte; coboară prin câteva noduri, pentru că un arbore bine format rămâne puțin adânc și echilibrat. Construirea unui asemenea arbore manual este destul de migăloasă încât merită văzută de la un capăt la altul, ceea ce face parcursul despre construirea unui document PDF de la zero
Arborele își câștigă a doua pâine prin moștenire. O mână de atribute de pagină, /Resources, /MediaBox, /CropBox și /Rotate, pot fi setate pe un nod interior și omise de pe paginile individuale, care moștenesc atunci valoarea celui mai apropiat strămoș. Setați /MediaBox o dată pe rădăcină și fiecare frunză primește aceeași dimensiune de pagină fără repetare; o pagină care trebuie să difere își declară propria valoare. Acesta este singurul loc din modelul de obiecte în care sensul unei valori depinde de poziția obiectului în arbore, nu doar de propriul lui conținut
Ce ține de fapt o pagină-frunză
Un obiect de pagină este punctul de îmbinare dintre modelul structural și conținutul vizibil. Intrarea lui /Contents referă unul sau mai multe fluxuri de conținut, operatorii de desenare care pictează text și grafică pe pagină. Dicționarul lui /Resources numește fonturile, imaginile și spațiile de culoare pe care se bazează acei operatori, fiecare intrare fiind o referință indirectă către un obiect partajat între pagini. /MediaBox dă dreptunghiul paginii în puncte (1/72 de țol), iar intrări precum /Rotate și /CropBox ajustează felul în care este prezentată
Acea împărțire a sarcinilor este întregul model în miniatură. Dicționarul de pagină este structură: intrări tipizate și referințe care spun ce este pagina și cu ce desenează. Fluxul de conținut este instrucțiuni: un bloc separat și comprimabil care spune cum se desenează. Fontul din spatele lui /F1 este o resursă partajată, definită o dată și indicată oriunde este folosită. Dicționarul, fluxul și referința cooperează ca să randeze o pagină, iar aceleași tipare se scalează la întregul document. Operatorii din fluxul de conținut de dinăuntrul acelui bloc sunt tratați separat pentru text și fonturi și pentru grafică și elemente vizuale
De ce merită cunoscut acest model
Majoritatea dezvoltatorilor întâlnesc modelul de obiecte doar când se strică ceva: o pagină se randează goală pentru că referința ei /Contents atârnă în gol, textul iese ca niște casete pentru că o resursă de font nu a fost niciodată încorporată, o unealtă raportează un /Count care nu se potrivește cu paginile pe care le găsește. Fiecare dintre acestea este o afirmație despre graf, iar citirea directă a grafului bate ghicitul. Cele opt tipuri și regula referințelor sunt un vocabular destul de mic încât să îl țineți în minte, iar odată ce vedeți un PDF ca obiecte care indică spre obiecte, fișierele malformate încetează să mai fie opace
Acestea fiind spuse, scrierea modelului de mână este rareori alegerea potrivită dincolo de învățare. Menținerea coerentă a pozițiilor din referințele încrucișate, a numerelor de generație, a contoarelor din arborele de pagini și a lungimilor de fluxuri de-a lungul editărilor este exact genul de evidență pentru care există o bibliotecă. În producție, o bibliotecă matură de dezvoltare PDF gestionează graful de obiecte și vă lasă să gândiți în termeni de pagini și conținut. Cunoașterea modelului tot dă roade: înțelegeți ce construiește biblioteca dedesubt și de ce