Laita 80 megatavun skannattu raportti linkin taakse, avaa se selaimella ja katso mitä tapahtuu: katseluohjelma (viewer) pysyttelee tyhjänä ruutuna, kunnes suuri osa noista tavuista on saapunut, ja sitten maalaa ensimmäisen sivun kerralla. Hyppää sivulle 40 ja, huonosti rakennetulla tiedostolla, koko lataus saattaa alkaa alusta. Turhauttava osa tässä on se, että lukija halusi missään vaiheessa ainoastaan nähdä ensimmäisen sivun. Linearisointi (linearization) on rakenteellinen vastaus tähän ongelmaan. Se järjestelee PDF:n uudelleen siten, että katseluohjelma voi renderöidä avaussivun pienestä tiedoston alkuosasta (prefix) ja hakea loput pyydettäessä, minkä vuoksi Adobe markkinoi ominaisuutta nimellä "Fast Web View"
Mikään tästä ei ole eri tiedostomuoto. Linearisointi-PDF on tavallinen PDF, jonka standardinmukainen lukija avaa ilman mitään erikoiskäsittelyä. Temppu piilee täysin siinä, miten tavut on järjestetty, sekä kahdessa ylimääräisessä rakenteessa, joita tiedosto kantaa mukanaan. ISO 32000-1 määrittelee koko järjestelyn liitteessä F (Annex F), ja kun olet nähnyt asettelun, käyttäytyminen lakkaa näyttämästä taikuudelta ja alkaa näyttää harkitulta kompromissilta (trade) tiedoston järjestyksen ja ensimmäisen piirron viiveen (first-paint latency) välillä
Mitä linearisointi todella järjestelee uudelleen
Tavallinen PDF voi sirotella objektinsa lähes mihin tahansa järjestykseen. Ristiviitetaulukko (cross-reference table) tiedoston lopussa saa sen toimimaan: lukija siirtyy (seeks) loppuun, lukee startxref-osoittimen, lataa xrefin, ja sieltä käsin voi paikantaa jokaisen objektin sen tavupoikkeaman (offset) perusteella. Tämä suunnittelu on erinomainen paikallisille tiedostoille, joissa loppuun siirtyminen ei maksa mitään, ja huono tiedostolle, joka suoratoistetaan verkon yli, jossa loppu on juuri se osa, joka saapuu viimeisenä. Renderöidäkseen sivun yksi tavanomainen lukija tarvitsee sivuobjektin, sen sisältövirran (content stream), sen viittaamat fontit ja kaikki sen piirtämät kuvat, ja järjestämättömässä tiedostossa ne voivat sijaita missä tahansa, jopa viimeisessä megatavussa
Linearisointi kiinnittää järjestyksen. Objektit, joita tarvitaan ensimmäisen sivun näyttämiseen, kerätään yhtenäiseksi lohkoksi (contiguous block) lähelle alkua, heti pienen otsikko-osion (header) jälkeen, joten ne saapuvat aikaisin tavuvirrassa. Kaikki muu, jäljellä olevat sivut ja niiden jakamat resurssit, seuraa ennustettavassa järjestyksessä. Toinen, täydellinen ristiviitetaulukko asuu yhä lopussa lukijoita varten, jotka ohittavat optimoinnin, mutta linearisoitu tiedosto sijoittaa myös ensimmäisen sivun ristiviitteen ja parametrit, joita suoratoistava lukija tarvitsee, aivan etuosaan. Lukijan ei enää tarvitse saavuttaa häntää ennen kuin se voi piirtää mitään
Ensimmäisen sivun objektijoukko ja linearisoinnin parametrisanakirja
Aivan ensimmäinen objekti linearisoidussa tiedostossa, heti %PDF-otsikon jälkeen, on linearisoinnin parametrisanakirja (linearization parameter dictionary). Sitä suoratoistava lukija etsii päättääkseen, onko optimointi läsnä ja kuinka sitä käytetään. Sanakirja kirjaa ylös koko tiedoston pituuden, tavupoikkeaman (byte offset), josta pääasiallinen ristiviiteosio alkaa, ensimmäisen sivun objektinumeron, sekä sitä seuraavan vihjevirran (hint stream) sijainnin ja pituuden. Näiden numeroiden avulla lukija tietää, pelkästään ensimmäisten kilotavujen perusteella, kuinka paljon sen on noudettava (fetch) näyttääkseen sivun yksi, ja mistä etsiä hakemistoa, joka antaa sen hypätä muualle
Liite F on tiukka sen suhteen, mitä "ensimmäinen sivu" tässä tarkoittaa. Ensimmäisen sivun osion on sisällettävä itse sivuobjekti, sen sisältövirrat ja resurssit, joihin nuo virrat viittaavat, niin että sivu on omavarainen (self-sufficient), kun tuo alkuosa on latautunut. Jaetut resurssit (shared resources) – fontti jota käytetään jokaisella sivulla, logo joka toistuu ylätunnisteessa (header) – käsitellään erikoistapauksina: ne ilmestyvät riittävän aikaisin palvellakseen ensimmäistä sivua, mutta ne liputetaan jaetuiksi, jotta lukija ei nouda niitä uudelleen, kun se myöhemmin renderöi sivua 30. Tämä ero yksityisten sivuobjektien ja jaettujen objektien välillä on se osa, jonka useimmat itsekoodatut "optimoijat" tekevät väärin, ja väärin tekeminen on se, mikä tuottaa tiedoston, joka väittää olevansa linearisoitu, mutta silti jumittaa (stalls)
Vihjevirrat (hint streams): hakemisto, joka tekee sivuhypyistä halpoja
Sivun yksi näyttäminen nopeasti on vain puolet arvosta. Toinen puoli on hyppääminen satunnaiselle sivulle ilman, että ladataan kaikkea siltä väliltä, ja sitä vihjevirrat (hint streams) tarjoavat. Linearisoitu tiedosto kantaa mukanaan sivupoikkeamien vihjotaulukkoa (page offset hint table) ja jaettujen objektien vihjotaulukkoa, jotka on tallennettu virtana ja joihin viitataan parametrisanakirjasta. Sivupoikkeamataulukko kirjaa jokaisesta sivusta ylös sen, mistä kohtaa tiedostoa sen objektit alkavat ja kuinka pitkälle ne ulottuvat. Jaettujen objektien taulukko tekee saman resursseille, joita käytetään useammalla kuin yhdellä sivulla
Näiden taulukoiden avulla lukija, joka haluaa sivulle 40, ei jäsennä tiedostoa järjestyksessä. Se konsultoi vihjotaulukkoa oppiakseen sen tavualueen (byte range), jonka sivu 40 varaa, pyytää palvelimelta tarkalleen tuon alueen ja renderöi sivun heti, kun nuo tavut saapuvat, vetäen kaikki tarvitsemansa jaetut resurssit saman mekanismin kautta, ellei sillä ole jo niitä. Vihjevirta on, käytännössä, asiakirjan ylle asetettu satunnaissaantokartta (random-access map), ja se on syy siihen, miksi hyvin linearisoitu 500-sivuinen tiedosto tuntuu reagoivalta hitaalla yhteydellä, kun taas saman kokoinen optimoimaton tiedosto ei tunnu
Miksi palvelimen on tehtävä yhteistyötä
Linearisointi olettaa, että siirtotie voi toimittaa (deliver) satunnaisia palasia (slices) tiedostosta, ja tuo oletus kannattaa tarkistaa ennen kuin syytät tiedostomuotoa huonoista tuloksista. Mekanismi on HTTP:n byte-serving: lukija lähettää range-pyyntöjä (range requests), ja palvelin vastaa niihin 206 Partial Content -vastauksilla. Jos palvelin ei mainosta (advertise) Accept-Ranges: bytes, tai jos sen edessä oleva välityspalvelin tai CDN tiivistää range-pyynnöt täysiksi tiedostosiirroiksi (full transfers), lukijalla ei ole mitään tapaa noutaa sivua 40 erikseen (in isolation), ja se putoaa takaisin (falls back) lataamaan koko tiedoston. PDF:n sisällä oleva rakenne on silloin täysin oikea ja täysin hukkaan heitetty
Tämä on se vika, joka useimmiten diagnosoidaan väärin sanomalla "linearisointi ei toimi". Tiedosto on kunnossa; toimituspolku ei ole. Ennen kuin rakennat asiakirjan uudelleen, varmista ehdollisella pyynnöllä (conditional request), että isäntä (host) todella palauttaa osittaista sisältöä sille URL:lle, jota lukija kutsuu. Monet staattiset isännät tekevät tämän oletuksena, ja monet väärin konfiguroidut sovelluspalvelimet ja välimuistikerrokset (caching layers) eivät tee
Inkrementaaliset päivitykset rikkovat linearisoinnin kaikessa hiljaisuudessa
Tässä on rajoite, joka yllättää ihmiset, jotka luovat linearisoituja tiedostoja oikein ja sitten ihmettelevät, miksi optimointi haihtuu ilmaan. Linearisointi riippuu yhdestä ainoasta, huolellisesti järjestetystä asettelusta, jonka hakemisto on etuosassa. Inkrementaalinen päivitys (incremental update) rikkoo tätä rakenteellisesti. Kun työkalu lisää allekirjoituksen, täyttää lomakekentän tai lisää annotaation (annotation) inkrementaalisen tallennuksen kautta, se ei kirjoita tiedostoa uudelleen. Se liittää (appends) muuttuneet objektit, uuden ristiviiteosion ja uuden trailerin tiedoston loppuun, jättäen alkuperäiset tavut koskemattomiksi. Tämä liittäminen on koko inkrementaalisten päivitysten ydin: se on nopea, ja se säilyttää aiemman revision auditointia tai allekirjoitusten validointia varten
Sivuvaikutuksena on se, että tiedoston uusin ristiviitetieto on nyt hännässä (tail), sen huolella sijoitetun ensimmäisen sivun lohkon (block) jälkeen, ja etuosan linearisoinnin parametrisanakirja kuvailee asettelua, joka ei enää vastaa tiedostoa. Standardinmukainen lukija havaitsee epäsuhdan (mismatch) ja käsittelee asiakirjaa normaalina, epälinearisoituna PDF:nä. Fast Web View on poissa, vaikka alkuperäinen linearisoitu rakenne yhä istuisi siellä tiedoston ensimmäisessä puolikkaassa. Jos liität (append) useita päivityksiä, jokainen niistä pinoaa toisen revision loppuun, ja kuilu vanhentuneen etuosan hakemiston ja todellisen tilan välillä kasvaa
Jos työnkulkusi (workflow) tarvitsee sekä muokkauksia että Fast Web View:tä, sääntö seuraa suoraan rakenteesta: muokkaa inkrementaalisesti, kun asiakirja on elävässä tilassa (in flux), ja linearisoi uudelleen kerran lopuksi. Täysi uudelleenkirjoitus on se, joka palauttaa asettelun. HotPDF-termein, se tarkoittaa, että keskeneräinen muokkaus (in-progress edit) menee BeginIncrementalUpdate:n ja SaveIncrementalUpdate:n kautta, jotka liittävät deltan, kun taas viimeistelevä askel lataa koko asiakirjan ja serialisoi (serializes) sen tuoreena metodilla LoadFromFile seurattuna SaveLoadedDocument:lla, joka pudottaa kertyneet vanhat revisiot ja tuottaa yhden puhtaan asettelun. Sama kompromissi (trade) näkyy objektivirtojen (object streams) kanssa: UseObjectStreams:n salliminen yhdessä UseXRefStream:n kanssa pakkaa ristiviitteen ja pakkaa objektit tiiviisti, mikä auttaa tiedostokokoon mutta, kuten mikä tahansa rakenteellinen valinta, se on sovellettava tuon viimeisen uudelleenkirjoituksen aikana sen sijaan, että se pultattaisiin kiinni liitettyyn revisioon
// In-flight edits: append a delta, keep prior revisions intact.
// This leaves the file NOT linearized.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');
// Finishing step: full re-serialization produces one clean layout,
// dropping the stacked revisions. Re-run your linearizer on the output.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');
HotPDF ei paljasta yhden kutsun (one-call) "linearisoi"-rutiinia, joten käytännön malli on tuottaa puhdas, täysin uudelleenkirjoitettu tiedosto ja ajaa omistettu optimoija (dedicated optimizer) sen yli. Komentoriviyökalut (command-line tools) käsittelevät uudelleenjärjestelyn (rearrangement) suoraan. qpdf kirjoittaa tiedoston uudelleen linearisoituun muotoon yhdellä lipulla (flag):
qpdf --linearize report-final.pdf report-web.pdf
Kuinka kertoa, onko tiedosto linearisoitu
Älä luota tiedostonimeen tai työkaluun, joka väittää sen tuottaneen; varmista tavut. Suorin tarkistus on tiedoston otsikko (head): avaa se ja etsi linearisoinnin parametrisanakirjaa ensimmäisenä objektina otsikon jälkeen, ja jolla on /Linearized-avain. Lukijalle suunnattu oikotie on Acrobatin Asiakirjan ominaisuudet -valintaikkuna (Document Properties dialog), joka raportoi "Fast Web View: Yes" ainoastaan silloin, kun rakenne on aidosti läsnä ja ajantasainen
Skriptatuissa tarkistuksissa qpdf raportoi sekä rakenteen olemassaolon että eheyden (integrity), millä on väliä, koska tiedosto voi kantaa linearisointisanakirjaa, joka ei enää vastaa sen asettelua, mikä on täsmälleen se tila, jonka inkrementaalinen päivitys (incremental update) jättää jälkeensä:
# Reports "File is linearized" and validates hint tables against the layout
qpdf --check report-web.pdf
# Dumps the linearization parameters and hint data in detail
qpdf --show-linearization report-web.pdf
Validointivaihe on se, joka ansaitsee paikkansa. Tarkistus, joka vain vahvistaa sanakirjan olemassaolon, siunaa iloisesti tiedoston, jonka hakemisto osoittaa vääriin poikkeamiin (offsets); tarkistus, joka sovittaa (reconciles) vihjotaulukot todellisia objektipositioita vasten, on se, joka kertoo sinulle, kestääkö optimointi todellisen lukijan range-pyynnöt
Linearisointia on edelleen syytä soveltaa mihin tahansa suureen asiakirjaan, joka tarjoillaan verkon (web) yli, erityisesti mobiililukijoille epävakailla yhteyksillä, ja se maksaa muutaman prosentin tiedostokoosta etupainotteisen (front-loaded) hakemiston osalta. Kaksi asiaa, jotka on syytä pitää suorina, ovat, että rakenteen PDF:n sisällä ja byte-serving:n sen ulkopuolella on molempien oltava oikein, ja että mikä tahansa jälkikäteen tehty muokkaus (edit after the fact) kumoaa optimoinnin, kunnes kirjoitat tiedoston uudelleen. Käsittele uudelleenlinearisointia putken (pipeline) viimeisenä vaiheena sen jälkeen, kun kaikki muut muutokset on ratkaistu. Tässä kuvattu ristiviite-, objektivirta- ja inkrementaalinen päivityskäyttäytyminen on osa rakenteellista mallia, jonka HotPDF Component Delphille ja C++Builderille toteuttaa; laajempaa tiedostoasettelu-taustaa varten, katso kuinka PDF on rakentunut, ja nähdäksesi inkrementaalisen päivityksen ja suurten tiedostojen työnkulun koodissa, katso suurten PDF-tiedostojen käsittely Delphistä