Tekninen artikkeli

PDF-sivujärjestys (Page Ordering): Kuinka (How the) sivupuu (Page Tree Controls) ohjaa sivusekvenssiä (Page Sequence)

Objektinumero 1 ei ole sivu 1. Tämä yksinkertainen tosiasia kaatuu enemmän PDF-käsittelykoodia kuin mikään muu muodon osa-alue, ja syyn ymmärtäminen edellyttää katsomista sen taakse, mitä katseluohjelma näyttää, ja sisälle siihen objektikaavioon, jota katseluohjelma todellisuudessa lukee

PDF-tiedosto on numeroitujen epäsuorien objektien kokoelma. Jokainen objekti kantaa objektinumeron ja sukupolvinumeron, ja muut objektit viittaavat siihen viittauksella, joka kirjoitetaan muodossa N G R: 3 0 R tarkoittaa objektin 3 nykyistä versiota. Sivut kuuluvat näihin objekteihin, mutta niiden näyttöjärjestyksellä ei ole mitään tekemistä sen kanssa, missä ne sijaitsevat tiedostossa tai mitä numeroita ne kantavat. Näyttöjärjestyksen määrittää kokonaan /Pages-puu, dokumenttiluetteloon juurtuva linkitetty rakenne. Jos jätätte puun huomiotta ja skannaatte objekteja numerojärjestyksessä, kokoatte sivut väärään järjestykseen merkittävässä osassa todellisen maailman tiedostoja

Sivupuu: mikä todella määrää järjestyksen

Jokainen PDF alkaa dokumenttiluettelolla (ISO 32000-2 §7.7.2). Luettelo sisältää /Pages-merkinnän, joka osoittaa sivupuun juurisolmuun. Tämä juurisolmu on sanakirja, jossa on /Type /Pages, epäsuorien viittausten /Kids-taulukko, ja /Count, joka kertoo sen alla olevien lehtisivujen kokonaismäärän. Näyttöjärjestys on sen puun syvyyssuuntainen vasemmalta oikealle -läpikäynti, piste

PDF-kaavio PDF-sivupuusta, jossa Kids-taulukko määrää näyttöjärjestyksen riippumatta objektinumeroista
Katalogi saavuttaa juuren /Pages-solmun, ja /Kids-solmun syvyysensuuntainen läpikäynti kiinnittää jokaisen näyttösijainnin riippumatta objektien numeroinnista

Minimaalinen kolmisivuinen tiedosto tekee tämän konkreettiseksi:

%PDF-1.7

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

2 0 obj
<< /Type /Pages /Kids [20 0 R  4 0 R  9 0 R] /Count 3 >>
endobj

% Objekti 4 on tallennettu tiedostossa kolmantena, mutta on sivu 2 näyttöjärjestyksessä
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
   /Contents 5 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj

% Objekti 9 on tallennettu neljäntenä, mutta on sivu 3
9 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
   /Contents 10 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj

% Objekti 20 on tallennettu viimeisenä, mutta on sivu 1; Kids[0] ratkaisee, ei objektinumero
20 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
   /Contents 21 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj

Taulukko /Kids kuuluu [20 0 R 4 0 R 9 0 R], joten objekti 20 on sivu 1, objekti 4 on sivu 2, ja objekti 9 on sivu 3. Objektinumeroinnilla ei ole merkitystä. Mikä tahansa koodi, joka käy objektit läpi numerojärjestyksessä ja kerää ne, joilla on /Type /Page, tuottaa väärän järjestyksen tälle tiedostolle

Miksi generaattorit tuottavat epäjärjestyksessä olevia asetteluja? Useita syitä. Kirjasto, joka varaa objektinumerot etukäteen kaikille sivuille ennen niiden sisällön kirjoittamista, numeroi ne luontijärjestyksessä ja kirjoittaa sitten varsinaiset tavut siinä järjestyksessä, joka sopii serialisoijalle parhaiten. Yhdistämistyökalu, joka ompelee dokumentteja yhteen, numeroi kunkin lähdedokumentin objektit uudelleen törmäysten välttämiseksi; uudelleennumeroidut sivuobjektit päätyvät hajalleen yhdistettyyn objektitauluun, kun taas uusi juuren /Kids-taulukko sisältää oikean näyttöjärjestyksen. Asteittaiset päivitykset lisäävät uusia objekteja tiedoston loppuun tuorein numeroin, joten versiona lisätty sivu asuu lähellä tavuvirran loppua, vaikka se kuuluisikin näyttöjärjestyksen paikkaan 1

Litteät puut ja sisäkkäiset alipuut

Määrittely sallii sivupuulle kaksi muotoa. Yksinkertaiset generaattorit tuottavat litteän rakenteen: yhden juuren /Pages-solmun, jonka /Kids-taulukko sisältää pelkästään /Page-lehtiobjekteja. Sitä on helppo käydä läpi: yksi taso syvyyttä, yksi kierros

Suuret dokumentit käyttävät yleensä tasapainotettua puuta sen sijaan. Juuren /Pages-solmun /Kids-taulukko sisältää välitason /Pages-solmuja, joista jokaisella on puolestaan oma /Kids-taulukkonsa. Jokaisen välitason solmun /Count kertoo sen alipuun lehtisivujen kokonaismäärän, joten katseluohjelma voi ohittaa kokonaisia alipuita hypätessään sivulle indeksin perusteella jäsentämättä jokaista objektia. 1 000-sivuinen dokumentti, joka on rakennettu tasapainotettuna puuna kymmenellä sivulla per lehtisolmu, voi paikantaa sivun 750 binäärihaulla kolmen tai neljän sanakirjahaun kautta sen sijaan, että skannaisi 750 /Kids-merkintää

PDF-vertailu tasaisista ja tasapainoisista PDF-sivupuista välisivujen solmuilla ja /Count-arvoilla
Välitasojen /Pages-solmut kantavat /Count-kenttää, joten katselimet voivat hakea syviä dokumentteja nopeasti, kun taas vain ensimmäisen tason skannaus pudottaa kokonaisia alipuita hiljaa

Seuraus käsittelykoodille: ette voi olettaa, että /Kids-taulukon ensimmäinen taso sisältää /Page-objekteja. Jokainen lapsi on tarkistettava. Jos sen /Type on /Pages, rekursoikaa siihen. Jos sen /Type on /Page, se on lehti. Pysähtyminen ensimmäiselle tasolle pudottaa hiljaisesti kokonaisia alipuita jokaisessa dokumentissa, jossa generaattori valitsi sisäkkäisyyden. Miksi kirjoittajat ylipäätään valitsevat syviä puita, mitä litistystyökalut menettävät, ja miten /Count-korruptio ilmenee käytännössä, käsitellään oheisartikkelissamme sivupuun muoto, haarautuvuus ja /Count-eheys

Periytyvät sivuattribuutit

Sivupuu kantaa myös resurssien jakomekanismin. Tietyt sivuattribuutit: /MediaBox, /CropBox, /Resources, ja /Rotate ovat periytyviä (ISO 32000-2 §7.7.3.4). Jos /Page-sanakirjasta puuttuu jokin niistä, lukija kulkee /Parent-ketjua ylöspäin, kunnes se löytää attribuutin tai saavuttaa juuren. Jaetun fonttisanakirjan sijoittaminen juuren /Pages-solmuun sen kopioimisen sijaan jokaiselle lehtisivulle voi pienentää tiedostokokoa huomattavasti dokumenteissa, jotka käyttävät samoja kirjasintyyppejä läpi koko dokumentin

Periytymissääntö luo hienovaraisuuden koodille, joka lukee sivuominaisuuksia. On väärin lukea /MediaBox suoraan /Page-objektista ja käsitellä puuttuvaa avainta virheenä; avain saattaa yksinkertaisesti olla periytetty. Koodin, joka ratkaisee sivun geometrian oikein, on seurattava vanhempiketjua. Se tarvitsee myös silmukkasuojan: viallisessa tiedostossa voi olla /Parent-viittaus, joka osoittaa takaisin jo vierailtuun solmuun, mikä silmukoisi loputtomiin ilman vierailtujen objektien tarkistusta

Xref-taulukko ja ristiviittausvirrat

Epäsuora objektihaku kulkee ristiviittaustaulukon kautta (tai sen seuraajan, PDF 1.5:ssä esitellyn ristiviittausvirran kautta). Xref kartoittaa jokaisen objektinumeron tiedoston sisäiseksi tavusiirtymäksi. Vaatimustenmukainen lukija käyttää xref-taulukkoa hypätäkseen suoraan mihin tahansa objektiin; se ei skannaa tiedostoa peräkkäin. Tämä satunnaiskäyttöinen suunnittelu on se, mikä tekee nopeasta sivulle hyppäämisestä mahdollista: katseluohjelma lukee luettelon, ratkaisee /Pages-viittauksen xref-taulukon kautta, lukee juuren /Pages-solmun, ratkaisee /Kids-merkinnän, ja niin edelleen, koskettaen vain niitä objekteja, joita se tarvitsee

Asteittaiset päivitykset lisäävät uuden xref-osion tiedoston loppuun perälaudalla (trailer), joka ketjuttuu takaisin edelliseen. Versiossa päivitetty objekti saa uuden merkinnän liitettyyn xref-osioon; alkuperäiset tavut pysyvät paikallaan, mutta ne korvautuvat. Näin digitaalisesti allekirjoitetut PDF:t pysyvät todennettavina jopa kommentti- tai lomakkeentäyttöversioiden lisäämisen jälkeen: allekirjoitettua tavualuetta ei koskaan kosketa, ja uusi sisältö asuu liitetyssä osiossa. Sivupuutakin voidaan päivittää, joten sivujen lisäykset tai poistot versiossa tuottavat uuden /Pages-juuren, jossa on tarkistettu /Kids-taulukko, kun taas vanha juuriobjekti edelleen miehittää alkuperäisen paikkansa tiedostossa. Linearisoidut (web-optimoidut) tiedostot lisäävät tavuasettelun käänteen: sivun 1 objektit siirretään fyysisesti tiedoston alkuun, jotta katseluohjelma voi näyttää ensimmäisen sivun, kun loput vielä latautuu, mutta sivupuu pysyy silti ainoana auktoriteettina järjestykselle — vain xref-taulukkoon tallennetut siirtymät muuttuvat

Mikä menee pieleen ilman puun läpikäyntiä

Objektiskannausmenetelmien vikaantumistapa on hiljainen. Tulostettu dokumentti näyttää uskottavalta: siinä on oikea määrä sivuja ja jokainen sivu sisältää tunnistettavaa sisältöä. Järjestys on vain väärä, ja väärä tavalla, joka riippuu generaattorista, versioiden määrästä, ja siitä, yhdistettiinkö mitään sivuja ulkopuolisista lähteistä. Yhden työkalun tuottamien tiedostojen testikorpus saattaa mennä läpi kokonaan; toisen työkalun tai yhdistämistyönkulun tiedostot epäonnistuvat. Tämä epäjohdonmukaisuus on syy, miksi heuristiset korjaukset eivät koskaan kestä. Katsaus juuri tähän vikaan todellisessa asiakasdokumentissa — oire, väärä diagnoosi, ja läpikäyntikorjaus — löytyy sivujärjestyksen virheenkorjauksen tapaustutkimuksestamme

Asteittaisen päivityksen tiedostot ovat erityisen alttiita tälle, koska myöhemmissä versioissa lisätyt tai uudelleenjärjestetyt sivut kantavat suuria objektinumeroita, kun taas näyttöjärjestystä ohjaa päivitetty /Kids-taulukko. Skannaus, joka käsittelee objektit numerojärjestyksessä, sijoittaa nuo myöhään numeroidut sivut loppuun riippumatta siitä, mihin puu sanoo niiden kuuluvan

PDF: kulku PDF-vanhempaketjun läpi ratkaisemassa peritty MediaBox sivuominaisuuden haussa
Attribuutit, kuten /MediaBox ja /Resources, voivat asua esivanhempien solmuissa, ja vierailtujen objektien suoja estää epämuodostuneita vanhempien ketjuja kiertämästä

Korjaus ei ole monimutkainen. Aloittakaa luettelosta, ratkaiskaa /Pages-viittaus, kulkekaa /Kids-taulukko rekursiivisesti läpi, ja tuottakaa lehdet siinä järjestyksessä, jossa kohtaatte ne. Se on näyttöjärjestys määritelmän mukaan, riippumatta objektinumeroista, tavusiirtymistä, tai tiedostorakenteesta. Useimmat kypsät PDF-kirjastot tarjoavat sivumäärän ja indeksoidun sivunkäyttöliittymän, jotka jo tekevät tämän oikein; riski piilee koodissa, joka ohittaa kirjaston sivumallin ja koskettaa objektikerrosta suoraan

Yksi rakenteellinen poikkeama, joka kannattaa käsitellä nimenomaisesti: välitason /Pages-solmun /Count-arvo voi olla väärä virheellisissä tiedostoissa. Jos luotatte /Count-arvoon rajatarkistuksessa ja pysähdytte ennen täyttä läpikäyntiä, se jättää hiljaisesti sivuja pois, kun määrä on aliarvioitu. /Count-arvon käyttäminen vain suoritustehon vihjeenä kapasiteetin ennakkovaraukseen tai binäärihakuun, ja todellisen määrän johtaminen läpikäynnistä, on turvallisempi malli tärkeille dokumenteille

Seuraava artikkeli