Tekninen artikkeli

PDF-tiedostorakenne: otsake, runko, xref ja trailer

PDF-lukija ei aloita tiedoston alusta. Se aloittaa lopusta. Viimeiset tavut sisältävät kaiken muun osoitteen, ja jäsennin, joka ei ymmärrä tätä järjestystä, lukee muodon väärin ensimmäiseltä riviltä lähtien. Joten hyödyllisin tapa oppia PDF-tiedostoista levyllä on oppia se kuten lukija tekee: häntä ensin, sitten hypätä taaksepäin karttaan, ja sitten ratkaista objektit, joihin kartta osoittaa

Tavut itsessään ovat riittävän yksinkertaisia luettavaksi tekstieditorissa, kun mitään ei ole pakattu. Minimaalinen yksisivuinen asiakirja, joka piirtää tekstin "Hello, World!", mahtuu alle viiteensataan tavuun, ja jokainen muodon rakenteellinen elementti näkyy siinä. Tässä on koko tiedosto neljällä osalla merkittynä:

%PDF-1.0                          % Header
%âãÏÓ

1 0 obj                           % Body: the object sequence
<<
/Kids [2 0 R]
/Count 1
/Type /Pages
>>
endobj

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

3 0 obj
<< /Font << /F0 << /BaseFont /Times-Italic /Subtype /Type1 /Type /Font >> >> >>
endobj

4 0 obj
<< /Length 65 >>
stream
1. 0. 0. 1. 50. 700. cm BT
  /F0 36. Tf
  (Hello, World!) Tj
ET
endstream
endobj

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

xref                              % Cross-reference table
0 6
0000000000 65535 f
0000000015 00000 n
0000000074 00000 n
0000000192 00000 n
0000000291 00000 n
0000000409 00000 n

trailer                           % Trailer
<<
/Root 5 0 R
/Size 6
>>
startxref
459
%%EOF

Neljä osaa, aina tässä järjestyksessä tiedostossa alaspäin: otsake, objektirunko, ristiinviittaustaulukko ja trailer. Kuju piilee siinä, että luet niitä melkein käänteisessä järjestyksessä. ISO 32000-2 §7.5.1 esittää saman neliosaisen anatomian, ja syy taaksepäin suuntautuvaan pääsyyn on puhtaasti käytännöllinen: lukija, joka hyppää suoraan tarvitsemaansa objektiin, on paljon nopeampi kuin sellainen, joka skannaa jokaisen tavun ylhäältä, ja tämä satunnaiskäyttö on juuri se syy, miksi trailer ja ristiinviittaustaulukko ovat olemassa

Otsake on kaksi riviä, ja toisella on merkitystä

Ensimmäinen rivi on %PDF-1.0. Prosenttimerkki tekee siitä syntaksin kannalta kommentin, mutta lukijat käsittelevät sitä tiedoston allekirjoituksena ja poimivat siitä versionumeron. Version käsittely on käytännössä löyhää. PDF 2.0:lle rakennettu lukija avaa mielellään tiedoston, joka väittää olevansa 1.0, ja useimmat lukijat yrittävät avata tiedostoa, jonka ilmoitettu versio on väärä tai jonka versiorivi on haudattu hieman syvemmälle tiedostoon nollatavun sijaan. Numero on vihje siitä, mitä ominaisuuksia odottaa, ei portti

Toinen rivi on se, jonka ihmiset vahingossa poistavat ja viettävät sitten iltapäivän virheenkorjauksen parissa. Sekin on kommentti, mutta sen hyötykuorma on neljä tavua ASCII 127:n yläpuolella. Ne ovat olemassa, jotta kaikki mikä siirtää tiedostoa "tekstitilassa" tunnistaa sen binääriseksi ja lopettaa rivinvaihtojen uudelleenkirjoittamisen. PDF sisältää pakattuja virtoja, joiden tavut voivat vahingossa vastata rivinvaihtoa tai syötön palautusta; jos siirtotyökalu kirjoittaa ne uudelleen, sanakirjaan tallennettu virran pituus ei enää vastaa levyllä olevia tavuja ja tiedosto vioittuu. Korkean tavun kommentti on neljäkymmentä vuotta vanha suoja ASCII-tilassa olevaa FTP:tä vastaan, ja se on edelleen jokaisessa vakavasti otettavan työkalun kirjoittamassa tiedostossa, koska sen estämä vika on hiljainen ja täydellinen

Runko sisältää objektit, joista jokainen on numeroitu

Kaikki, mikä muodostaa asiakirjan, on rungossa tasaisena sarjana epäsuoria objekteja. Jokainen niistä alkaa kahdella kokonaisluvulla ja obj-avainsanalla, sisältää sen sisällön ja päättyy endobj-avainsanaan. Yllä olevassa näytteessä objekti 1 on sivupuun solmu: 1 0 obj, sitten sanakirja, sitten endobj. Ensimmäinen kokonaisluku on objektin numero, toinen on sukupolven numero. Sukupolvi on lähes aina nolla juuri kirjoitetussa tiedostossa; se kasvaa vain, kun objektinumeroa käytetään uudelleen muokkauksien yhteydessä, mikä on tarpeeksi harvinaista, jotta voit pitää nollasta poikkeavaa sukupolvea merkkinä siitä, että tiedosto on käynyt läpi inkrementaalisia päivityksiä. Avainsanojen välinen sisältö on tässä sanakirja, kirjoitettuna << ja >> väliin, mutta se voisi yhtä hyvin olla numero, merkkijono, taulukko tai virta

Mikä tekee tästä kaavion pikemminkin kuin luettelon, on viittausmerkintä 2 0 R. Se tarkoittaa "objekti 2, sukupolvi 0, missä ikinä se tiedostossa sattuu sijaitsemaan". Sivupuun solmu yllä ei sisällä sen sivua; se osoittaa objektiin 2, joka osoittaa sen resursseihin ja sisältövirtaan samalla mekanismilla. Runko on aseteltu siihen järjestykseen, jonka kirjoittaja katsoi sopivaksi, ja viittaukset nivotuvat puuksi, joka juurtuu luetteloon. Sijainnilla tiedostossa ei ole merkitystä. Identiteetti tulee objektin numerosta, ja sijainti tulee ristiinviittaustaulukosta

Ristiinviittaustaulukko on tavusiirtymien hakemisto

Xref-taulukko on se, joka muuttaa objektinumerot tiedoston sijainneiksi. Se on syy, miksi lukija voi avata tuhatsivuisen asiakirjan ja renderöidä sivun 850 jäsentämättä 849 edeltävää sivua. Jokainen merkintä kirjaa tarkalleen, mistä sen objekti alkaa, laskettuna tavuina tiedoston alusta alkaen:

xref
0 6                  % 6 entries, starting at object 0
0000000000 65535 f   % entry 0: head of the free list
0000000015 00000 n   % object 1 begins at byte 15
0000000074 00000 n   % object 2 begins at byte 74
0000000192 00000 n   % object 3 begins at byte 192
0000000291 00000 n   % object 4 begins at byte 291
0000000409 00000 n   % object 5 begins at byte 409

Kiinteä leveys on tarkoituksellinen. Jokainen merkintä on tarkalleen kaksikymmentä tavua: kymmennumeroinen siirtymä, välilyönti, viisinumeroinen sukupolvi, välilyönti, yksimerkkinen tyyppi ja kaksitavuinen rivinloppu. Koska rivit ovat yhtenäisiä, lukija voi indeksoida suoraan objektin n merkintään aritmetiikalla skannaamisen sijaan, joten taulukko, joka antaa satunnaiskäytön runkoon, on itsessään satunnaisesti käytettävissä. 0 6 -rivi on alaosaston otsake: se sanoo, että seuraavat merkinnät kuvaavat kuutta objektia alkaen numerosta 0

Objekti 0 on erikoinen ja aina läsnä. Sen tyyppi on f vapaalle (free), sen sukupolvi on 65535, ja se johtaa vapaiden objektinumeroiden linkitettyä luetteloa. Tiedostossa, jota ei ole koskaan muokattu, vapaa luettelo on vain tämä yksi merkintä, muodollisuus. Se lunastaa paikkansa inkrementaalisten päivitysten aikana, kun objektin poistaminen lisää sen numeron tuohon luetteloon, jotta myöhempi muokkaus voi periä sen. Muut merkinnät ovat tyyppiä n käytössä olevalle (in-use), ja niiden kymmennumeroinen luku on siirtymä, johon siirtyisit lukeaksesi kyseisen objektin määritelmän

Trailer on aloituksen sisääntulokohta, ja se sijaitsee lopussa

Trailer on ensimmäinen asia, jonka lukija todella kuluttaa, vaikka se on kirjoitettu viimeisenä. Jäsennin avaa tiedoston, siirtyy loppuun ja kävelee taaksepäin etsien merkkiä %%EOF. Aivan sen yläpuolella on startxref jota seuraa yksi numero, ja kyseinen numero on xref-avainsanan tavusiirtymä. Sen avulla lukija hyppää suoraan ristiinviittaustaulukkoon skannaamatta yhtäkään objektia:

trailer
<<
/Root 5 0 R          % the document catalog
/Size 6              % one more than the highest object number
>>
startxref
459                  % byte offset of the xref table
%%EOF

Trailerin sanakirja sisältää ne kaksi arvoa, jotka lukija tarvitsee ennen kuin se voi tehdä mitään muuta. /Root osoittaa asiakirjaluetteloon, tässä tapauksessa objektiin 5, joka on objektikaavion huippu ja reitti sivupuuhun. /Size on niiden merkintöjen määrä, jotka ristiinviittaustaulukon pitäisi sisältää, mikä on yksi enemmän kuin suurin objektinumero paikassa nolla olevan vapaan merkinnän vuoksi. Merkistä %%EOF lähtee koko lukujärjestys: etsi merkki, lue startxref löytääksesi taulukon, lataa taulukko oppiaksesi missä kukin objekti asuu, lue /Root löytääksesi luettelon ja ratkaise objektit tarpeen mukaan sieltä. Yläosassa sijaitsevaa otsaketta ei juurikaan katsota ennen kuin vasta myöhään. Alhaalla oleva kartta on se, mitä lukija tarvitsee ensin

Inkrementaalinen päivitys lisää toisen kartan uudelleenkirjoituksen sijaan

Tuo häntä edellä -suunnittelu maksaa itsensä takaisin, kun tiedosto muuttuu. PDF-tiedostoa voidaan muokata kirjoittamatta uudelleen yhtäkään levyllä jo olevaa tavua. Uudet ja muokatut objektit lisätään loppuun, mitä seuraa tuore ristiinviittausosio ja tuore trailer, ja alkuperäinen tiedosto niiden alla jätetään ennalleen. Ainoa uusi osa kirjanpitoa on uudessa trailerissa oleva /Prev-merkintä, joka sisältää edellisen ristiinviittaustaulukon tavusiirtymän:

% ... original file, unchanged, ends here ...

6 0 obj                          % an object added by this edit
<< /Type /Annot /Subtype /Text /Rect [100 700 120 720] >>
endobj

xref                             % a second xref section, for the new object only
6 1
0000000612 00000 n

trailer
<<
/Root 5 0 R
/Size 7
/Prev 459                        % byte offset of the earlier xref table
>>
startxref
680                              % offset of this new xref section
%%EOF

Lukija aloittaa edelleen lopullisesta %%EOF-merkistä, seuraa edelleen startxref-viittausta uusimpaan taulukkoon, mutta seuraa nyt /Prev-ketjua taaksepäin vanhempiin taulukoihin yhdistäen ne niin, että uusin merkintä voittaa minkä tahansa objektinumeron osalta. Ristiinviittausosiot muodostavat linkitetyn luettelon alaspäin tiedoston läpi, ja jokainen niistä ohittaa edellisen niiden objektien osalta, joihin se koskee. Muokkauksen korvaama objekti on yhä fyysisesti olemassa vanhassa siirtymässään; se ei yksinkertaisesti ole enää saavutettavissa, koska myöhempi xref-merkintä osoittaa jonnekin uudempaan paikkaan

Tämä on mekanismi, joka tekee allekirjoitetuista PDF-tiedostoista todennettavia. Digitaalinen allekirjoitus kattaa tiedoston tavualueen, ja koska inkrementaalinen päivitys aina vain lisää alkuperäisen loppuun, allekirjoitetut tavut eivät koskaan siirry. Allekirjoitus on edelleen vahvistettavissa alkuperäistä aluetta vasten, kun taas myöhemmät muutokset sijaitsevat sen ulkopuolella, kukin omalla xref:llä ja trailerilla varustettuna. Se on myös syy siihen, miksi PDF voi kantaa mukanaan palautettavaa historiaa: jokainen syrjäytetty objekti on yhä levyllä aiemman ristiinviittausosion alla, mikä on ominaisuus versionseurannalle ja riski kenelle tahansa, joka luuli sanan "poista" tarkoittavan, että tavut ovat poissa

Kustannuksena on kasvu. Jokainen muokkaus lisää perään; mitään ei oteta talteen paikallaan, joten useita kertoja muokattu tiedosto kerää kuolleita objekteja ja pitkän ketjun xref-osioita. Lääke tähän on täydellinen uudelleenkirjoitus: lataa asiakirja ja tallenna se uudelleen, mikä numeroi eloonjääneet objektit uudelleen, pudottaa saavuttamattomat ja lähettää yhden puhtaan ristiinviittaustaulukon. Nämä kaksi strategiaa toimivat suoraan toisiaan vastaan. Perään lisääminen on nopeaa ja säilyttää allekirjoitukset ja historian; uudelleenkirjoittaminen on hitaampaa ja hylkää molemmat vastineena kompaktista tiedostosta

Neljän osan lukeminen käytännössä

Asettelun tunteminen riittää useimpien "tämä tiedosto ei aukea" -ongelmien virheenkorjaukseen käsin. Jos lukija hylkää PDF-tiedoston, tavalliset syylliset löytyvät molemmista päistä, eivät keskeltä. Typistetty lataus hukkaa trailerin, joten startxref tai %%EOF puuttuu eikä lukijalla ole sisääntulokohtaa; suvaitsevaiset lukijat turvautuvat skannaamaan koko tiedoston rakentaakseen xref:n uudelleen, mikä on juuri se hidas reitti, jota taulukon oli tarkoitus välttää. Huonosti tehty tekstitilan siirto vioittaa virtatavuja tai siirtymät eivät enää vastaa todellisuutta, ja objektit latautuvat väärästä sijainnista. Kun taulukon siirtymät eivät enää osoita todellisiin obj-avainsanoihin, tiedosto on rakenteellisesti rikki, vaikka jokainen objekti olisi erikseen kunnossa

Uuden koodin osalta asettelun opetus on antaa kirjaston huolehtia tavujen kirjanpidosta. Ristiinviittaustaulukon siirtymien on vastattava jokaisen objektin todellista sijaintia tavuilleen, trailerin on osoitettava oikeaan taulukkoon ja inkrementaalisten päivitysten on ketjututtava oikein /Prev:n kautta. Natiivi komponentti, kuten HotPDF Component Delphille ja C++Builderille, hoitaa kaiken tämän kirjoittaessaan tiedostoa, mukaan lukien valinnan inkrementaalisen version liittämisen ja kompaktin sellaisen uudelleenkirjoittamisen välillä. Jos haluat nähdä saman rakenteen pystytettävän tyhjästä pikemminkin kuin purettavan, kumppaniartikkeli aiheesta PDF-asiakirjan rakentaminen alusta alkaen opastaa otsakkeen, objektien, xref:n ja trailerin lähettämisen järjestyksessä