Tekninen artikkeli

PDF ilman Pages-sanakirjaa: Jäsennysvaikutukset

PDF Catalog -sanakirjalla on täsmälleen yksi pakollinen navigointiavain: /Pages. Tämän avaimen on osoitettava epäsuoraan objektiin, jonka tyyppi on /Pages, ja joka puolestaan pitää sisällään /Kids-taulukon ja sivujen kokonaismäärän (/Count). Ota tämä osoitin pois, ja yksikään standardinmukainen katseluohjelma ei pysty paikantamaan yhtäkään sivua tiedostosta. ISO 32000-1 §7.7.2 on yksiselitteinen tässä asiassa: Catalog-sanakirjassa on oltava /Pages-tietue, ja viitatun objektin tyypin on oltava /Pages. Tiedostot, jotka rikkovat tätä vaatimusta, eivät ole pelkästään epästandardeja; ne ovat rakenteellisesti rikki tavalla, jota useimmat jäsentimet käsittelevät huonosti

Mitä spesifikaatio oikeasti sanoo

Minimaalisessa standardinmukaisessa PDF:ssä on vähintään kolme objektia. Objekti 1 on Catalog, objekti 2 on Pages-juuri (root), ja objektista 3 eteenpäin ovat yksittäiset Page-sanakirjat. Catalog osoittaa Pages-juureen; Pages-juuri listaa lapsensa (children) kohdassa /Kids; jokaisella Page-sivulla on /Parent-takaisinviittaus. Koko ketju on tarkoituksella kaksisuuntainen, jotta jäsennin voi aloittaa kummasta tahansa päästä ja kulkea mille tahansa sivulle O(log n) -ajassa tasapainoisissa puissa

% Minimal conforming structure (ISO 32000-1 §7.7.2)
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj

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

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

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

Pages-puu voi olla sisäkkäinen. Asiakirja, jossa on tuhansia sivuja, tyypillisesti ryhmittelee sivut väli-solmuobjekteiksi, jotka myös kantavat tyyppiä /Pages, ja joilla on oma /Kids ja alipuuta vastaava /Count. Juurisolmun /Count on aina yhtä kuin kokonaissivumäärä. Tämä määrä on se, mitä katseluohjelmat näyttävät sivunumero-kentässä ennen kuin ne ovat jäsentäneet yhtäkään sivua, koska yhden kokonaisluvun lukeminen objektista 2 on paljon halvempaa kuin koko puun läpikäynti

Miltä tiedosto ilman Pages-sanakirjaa näyttää

Tiedostot, joista Pages-sanakirja puuttuu, ovat tyypillisesti peräisin PDF-generaattoreista, jotka kirjoittavat sivuobjekteja suoraan kokoamatta niitä puuksi, tai korruptiosta, joka poistaa juurisolmun mutta jättää lehtien (leaf) Page-objektit koskemattomiksi. Tällaisen tiedoston Catalogista joko puuttuu /Pages-avain kokonaan, tai se sisältää viittauksen objektiin, jota ei enää ole ristiviitetaulukossa

% Non-conforming: Catalog with no /Pages reference
1 0 obj
<< /Type /Catalog >>
endobj

% Page objects exist but are unreachable from the Catalog
5 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj

15 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 16 0 R /Resources << >> >>
endobj

25 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 26 0 R /Resources << >> >>
endobj

Jäsennin, joka seuraa spesifikaatiota, lukee Catalogin, yrittää ratkaista /Pages, ei löydä mitään (tai löytää kuolleen viittauksen), ja joko nostaa virheen tai ilmoittaa nolla sivua. Sitä se ei saa tehdä, on jatkaa kuin tiedostossa olisi nolla sivua ja onnistua äänettömästi; se tuottaa tyhjän tulosteen, joka näyttää oikealta automatisoiduille työkaluille, mutta väärältä jokaiselle sen avaavalle ihmiselle

Miksi jäsentimet kaatuvat

Useimmat PDF-jäsentimet varaavat muistia sisäiselle sivutaulukolleen latausvaiheessa perustuen Pages-juuren /Count-arvoon. Kun juuri puuttuu, jäsennin joko lukee nolla, varaa nollakokoisen muistin ja purkaa nolla-osoittimen heti kun mikä tahansa koodi kysyy sivua 1, tai se lukee roskaa ja varaa rajusti vääränkokoisen puskurin. Kumpikaan lopputulos ei ole siro. Käyttöoikeusrikkomus (access violation) osoitteessa 0x008E5D78, joka näkyy kaatumislokeissa tällaisen tiedoston käsittelystä, on tarkalleen tätä: nolla-osoittimen (null-pointer) dereferointi sivuun pääsyn (page-access) polulla, jonka laukaisi sen rakenteen puuttuminen, jonka jäsennin oletti olevan aina siellä

Taustalla oleva suunnitteluoletus on kohtuullinen. Ylivoimaisella enemmistöllä olemassa olevista PDF-tiedostoista on Pages-sanakirja. Jäsentimet, jotka ohittavat olemassaolon tarkistuksen säästääkseen muutaman käskyn, eivät ole huolimattomia; ne optimoivat yleistä tapausta varten. Tiedostot, jotka rankaisevat tuota optimointia, ovat riittävän harvinaisia, että tuotantokoodi ei ehkä koskaan kohtaa sellaista ennen kuin kohtaa, jolloin kaatuminen on sekä toistettavissa että hämmentävä, jos insinööri ei ole lukenut kohtaa §7.7.2

Palautuminen ilman Pages-puuta

Jos jäsentimen on käsiteltävä nämä tiedostot sen sijaan, että hylkäisi ne, palautuminen seuraa ennustettavaa polkua: skannaa jokainen epäsuora objekti ristiviitetaulukossa, kerää ne joilla on /Type /Page, ja lajittele ne objektinumeron mukaan. Objektinumeron järjestyksen ei taata vastaavan spesifikaation mukaista lukemisjärjestystä, mutta käytännössä generaattorit, jotka jättävät Pages-puun pois, pyrkivät tuottamaan sivut peräkkäin, joten objektinumeron järjestys on oikea useammin kuin ei

Itse tarkistus on halpa. Ennen kuin kävelet Catalogin /Pages-osoitinta, varmista, että osoitin on olemassa, että se ratkeaa todelliseen objektiin, ja että ratkaistun objektin /Type on yhtä kuin /Pages. Jos jokin näistä kolmesta ehdosta ei täyty, pudottaudu lineaariseen skannaukseen. Skannaus on suurille asiakirjoille hitaampaa kuin puun läpikäynti, koska se lukee jokaisen objektin otsikon (header) tasapainoisen polun seuraamisen sijaan, mutta se toimii, ja tiedostolle, joka on jo valmiiksi epämuodostunut, oikeellisuus ajaa nopeuden edelle

Yksi rajatapaus, jota lineaarinen skannaus ei ratkaise automaattisesti: sivujen järjestys. Ilman /Kids-taulukkoa, joka määrittäisi järjestyksen, "oikea" järjestys on määrittelemätön spesifikaatiossa. Objektinumeron järjestys on pragmaattinen oletus; jos tiedosto on riittävän tärkeä huolellisesti käsiteltäväksi, on syytä tarkistaa, kantavatko Page-objektit eksplisiittistä /StructParents- tai annotaatioviittauksia, jotka vihjaavat lukemisjärjestyksestä

Vaikutukset PDF-generaattoreille

Kaikille, jotka kirjoittavat PDF-generaattoria jäsentimen sijaan, opetus on kapea: tuota (emit) aina Pages-juuri ennen tiedoston sulkemista. Catalog ilman /Pages-tietuetta ei ole kelvollinen PDF minkään spesifikaation version mukaan. Generaattorit, jotka rakentavat sivuobjekteja lennossa ja kokoavat puun viimeistelyvaiheessa (lähestymistapa, jota useimmat stream-kirjoittajat käyttävät), ovat kunnossa niin kauan kuin viimeistely todella suoritetaan. Yleinen virhetila on poikkeus tai varhainen paluu (early return), joka keskeyttää kirjoituksen ennen kuin traileri on valmis, jättäen jälkeensä tiedoston, joka aukeaa joissakin katseluohjelmissa (joissa on palautumisheuristiikkaa) ja epäonnistuu toisissa (joissa ei ole)

PDF/A ja PDF/UA asettavat sivupuulle lisärajoituksia perusspesifikaation vaatimusten lisäksi, mutta kumpikaan ei lievennä /Pages-vaatimusta. Validaattori, joka tarkistaa yhteensopivuuden ISO 19005 tai ISO 14289 -standardien kanssa, nappaa puuttuvan Pages-sanakirjan perusspesifikaation rikkomuksena ennen kuin se edes saavuttaa profiilikohtaisia sääntöjä