Patalpinkite 80 MB nuskenuotą ataskaitą už nuorodos, atidarykite ją naršyklėje ir stebėkite, kas vyksta: peržiūros programa rodo tuščią ekraną, kol atkeliauja didžioji dalis baitų, o tada iškart atvaizduoja pirmąjį puslapį. Šokite į 40-ąjį puslapį ir, jei failas sukurtas prastai, visas siuntimas gali prasidėti iš naujo. Labiausiai varginanti dalis yra ta, kad skaitytuvas norėjo pamatyti tik pirmąjį puslapį. Šios problemos struktūrinis sprendimas yra tiesinimas (angl. linearization). Jis pertvarko PDF failą taip, kad peržiūros programa galėtų atvaizduoti pradinį puslapį iš nedidelio failo pradžios segmento, o likusią dalį atsiųsti pagal poreikį. Štai kodėl „Adobe“ šią funkciją vadina „Fast Web View“ (greita peržiūra internete).
Tai nėra koks nors kitas failų formatas. Tiesintas PDF yra įprastas PDF failas, kurį bet kuris standartus atitinkantis skaitytuvas atidarys be jokio papildomo apdorojimo. Visa esmė slypi baitų išdėstymo tvarkoje bei dviejose papildomose struktūrose, kurias neša failas. Standartas ISO 32000-1 visą šią struktūrą aprašo F priede, ir pamačius šį išdėstymą elgsena nustoja atrodyti kaip magija ir tampa sąmoningu failo tvarkos pakeitimu, siekiant sumažinti pirmojo puslapio atvaizdavimo delsą.
Ką iš tikrųjų pertvarko tiesinimas
Įprastas PDF failas gali išmėtyti savo objektus bei puslapius beveik bet kokia tvarka. Kryžminių nuorodų lentelė failo pabaigoje užtikrina sklandų veikimą: skaitytuvas peršoka į pabaigą, perskaito startxref rodyklę, įkelia xref lentelę ir pagal ją suranda bet kurį objektą pagal jo poslinkį. Šis dizainas puikiai tinka vietiniams failams, kur peršoti į pabaigą nieko nekainuoja, tačiau visiškai netinka srautiniam failų siuntimui per tinklą, kur failo pabaiga atkeliauja paskutinė. Kad atvaizduotų pirmąjį puslapį, įprastas skaitytuvas turi gauti puslapio objektą, jo turinio srautą, šriftus bei paveikslėlius, o netvarkingame faile šie duomenys gali būti bet kur, net ir pačiame paskutiniame megabaite.
Tiesinimas ištaiso šią tvarką. Objektai, reikalingi pirmajam puslapui atvaizduoti, surenkami į vientisą bloką netoli pradžios, iškart po nedidelės antraštės dalies, todėl baitų sraute jie atkeliauja anksti. Visa kita (likę puslapiai ir bendri ištekliai) seka nuspėjama tvarka. Antroji, pilna kryžminių nuorodų lentelė vis tiek išlieka pabaigoje skaitytuvams, kurie ignoruoja šį optimizavimą, tačiau tiesintas failas taip pat pradžioje patalpina pirmojo puslapio kryžmines nuorodas ir parametrus, kurių reikia srautiniam skaitytuvui. Skaitytuvui nebereikia pasiekti failo pabaigos, kad galėtų ką nors nupiešti.
Pirmojo puslapio objektų rinkinys ir tiesinimo parametrų žodynas
Pats pirmasis objektas tiesintame faile, einantis iškart po %PDF antraštės, yra tiesinimo parametrų žodynas. Būtent jo ieško srautinis skaitytuvas, kad nuspręstų, ar optimizavimas pritaikytas ir kaip jį naudoti. Žodynas įrašo viso failo ilgį, baitų poslinkį, kur prasideda pagrindinė kryžminių nuorodų sekcija, pirmojo puslapio objekto numerį bei sekančio užuominų srauto (hint stream) vietą ir ilgį. Turėdamas šiuos skaičius, skaitytuvas jau iš pirmųjų kilobaitų žino, kiek duomenų turi atsisiųsti pirmajam puslapiui parodyti ir kur ieškoti indekso, leidžiančio peršokti kitur.
F priedas griežtai apibrėžia, ką čia reiškia „pirmasis puslapis“. Pirmojo puslapio sekcija privalo turėti patį puslapio objektą, jo turinio srautus bei išteklius, į kuriuos šie srautai nurodo, kad atsiuntus šis pradžios segmentas puslapis būtų visiškai savarankiškas. Bendri ištekliai (pavyzdžiui, kiekviename puslapyje naudojamas šriftas ar antraštėje pasikartojantis logotipas) apdorojami specialiai: jie atsiranda pakankamai anksti, kad pasitarnautų pirmajam puslapiui, tačiau pažymimi kaip bendri, kad skaitytuvas jų nesiųstų iš naujo, kai vėliau atvaizduos 30-ąjį puslapį. Šis skirtumas tarp puslapiui privačių ir bendrų objektų yra ta dalis, kurią dažniausiai sugadina pačių sukurti „optimizuotojai“, ir būtent dėl šios klaidos failas deklaruoja esąs tiesintas, tačiau vis tiek stringa.
Užuominų srautai: indeksas, atpiginantis puslapių šuolius
Greitas pirmojo puslapio parodymas yra tik pusė naudos. Kita pusė yra galimybė peršokti į bet kurį puslapį neatsisiunčiant visko, kas yra tarp jų – būtent tai užtikrina užuominų srautai (hint streams). Tiesintas failas nešiojasi puslapio poslinkio užuominų lentelę (page offset hint table) bei bendrų objektų užuominų lentelę, saugomas kaip srautas, kurį nurodo parametrų žodynas. Puslapio poslinkio lentelė kiekvienam puslapiui įrašo, kur faile prasideda jo objektai ir kokio jie yra ilgio. Bendrų objektų lentelė atlieka tą patį ištekliams, naudojamiems keliuose puslapiuose.
Turėdamas šias lenteles, skaitytuvas, kuriam reikia 40-ojo puslapio, neanalizuoja failo nuosekliai. Jis kreipiasi į užuominų lentelę, kad sužinotų, kokį baitų diapazoną užima 40-asis puslapis, paprašo serverio tiksliai to diapazono ir atvaizduoja puslapį jam atkeliavus, per tą patį mechanizmą atsisiųsdamas bendrus išteklius, kurių dar neturi. Užuominų srautas iš esmės yra tiesioginės prieigos žemėlapis, užklotas ant dokumento, ir būtent dėl jo gerai tiesintas 500 puslapių failas veikia greitai per lėtą ryšį, o neoptimizuotas tokio paties dydžio failas stringa.
Kodėl serveris turi bendradarbiauti
Tiesinimas daro prielaidą, kad transportavimo sluoksnis gali pateikti bet kokius failo segmentus, ir šią prielaidą verta patikrinti prieš kaltinant formatą dėl prastų rezultatų. Šis mechanizmas yra HTTP baitų pateikimas (byte-serving): skaitytuvas siunčia diapazono užklausas (range requests), o serveris atsako 206 Partial Content pranešimais. Jei serveris nedeklaruoja Accept-Ranges: bytes arba priešais jį esantis proksis ar CDN sujungia diapazono užklausas į pilnus siuntimus, skaitytuvas negali gauti 40-ojo puslapio izoliuotai ir grįžta prie viso failo siuntimo. Tokiu atveju PDF vidinė struktūra yra visiškai teisinga, tačiau visiškai nenaudinga.
Tai gedimas, kuris dažniausiai klaidingai diagnozuojamas kaip „tiesinimas neveikia“. Failas yra tvarkingas, tačiau perdavimo kelias – ne. Prieš perstatydami dokumentą, su sąlygine užklausa įsitikinkite, kad hostingo paslaugų teikėjas iš tikrųjų grąžina dalinį turinį tam URL, kurį naudoja skaitytuvas. Daugelis statinių svetainių teikėjų tai daro pagal numatytuosius nustatymus, tačiau daugelis neteisingai sukonfigūruotų programų serverių ir talpyklos sluoksnių to nedaro.
Priaugamieji atnaujinimai tyliai sugadina tiesinimą
Štai apribojimas, kuris stebina žmones, teisingai sugeneravusius tiesintus failus ir besistebinčius, kodėl optimizavimas pradingsta. Tiesinimas priklauso nuo vieno, kruopščiai sutvarkyto išdėstymo su indeksu pradžioje. Priaugamasis atnaujinimas (incremental update) tai pažeidžia iš esmės. Kai įrankis prideda parašą, užpildo formos lauką ar prideda anotaciją per priaugamąjį išsaugojimą, jis neperrašo failo. Jis tiesiog prideda pakeistus objektus, naują kryžminių nuorodų sekciją ir naują trailer dalį į pabaigą, palikdamas pradinius baitus nepaliestus. Šis pridėjimas yra visa priaugamųjų atnaujinimų esmė: tai greita ir išlaiko ankstesnę versiją nepakeistą auditui ar parašo patvirtinimui.
Pašalinis poveikis yra tas, kad failas dabar turi savo naujausius kryžminių nuorodų duomenis pabaigoje (po kruopščiai išdėstyto pirmojo puslapio bloko), o pradžioje esantis tiesinimo parametrų žodynas aprašo išdėstymą, kuris nebeatitinka realaus failo. Standartus atitinkantis skaitytuvas aptinka šį neatitikimą ir traktuoja dokumentą kaip įprastą, netiesintą PDF. „Fast Web View“ elgsenos nebelieka, nors originali tiesinta struktūra vis dar stovi pirmoje failo pusėje. Jei pridedate kelis atnaujinimus, kiekvienas iš jų pabaigoje sukrauna dar vieną reviziją, ir atotrūkis tarp pasenusio pradžios indekso bei realios būsenos padidėja.
Jei jūsų procesams reikia tiek redagavimo, tiek „Fast Web View“, taisyklė kyla tiesiogiai iš struktūros: redaguokite priaugamuoju būdu, kol dokumentas keičiasi, o pabaigoje jį vėl ištiesinkite. Tik pilnas perrašymas atkuria išdėstymą. Naudojant HotPDF, tai reiškia, kad vykdomas redagavimas eina per BeginIncrementalUpdate ir SaveIncrementalUpdate (kurie prideda skirtumą), o baigiamasis žingsnis įkelia visą dokumentą ir iš naujo jį suarchyvuoja per LoadFromFile bei SaveLoadedDocument, kas pašalina sukauptas senas versijas ir sugeneruoja vieną švarų išdėstymą. Tas pats galioja ir objektų srautams: įjungus UseObjectStreams kartu su UseXRefStream, suspaudžiamos kryžminės nuorodos ir objektai supakuojami tankiai, kas padeda sumažinti failo dydį, tačiau, kaip ir bet kuris struktūrinis sprendimas, turi būti pritaikyta per tą galutinį perrašymą, o ne prikabinta prie pridėtos revizijos.
// 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');
Kaip sužinoti, ar failas yra tiesintas
Nepasitikėkite failo pavadinimu ar įrankiu, kuris teigia jį sukūręs; patikrinkite pačius baitus. Griežčiausias patikrinimas yra failo pradžioje: atidarykite jį ir ieškokite tiesinimo parametrų žodyno kaip pirmojo objekto po antraštės, turinčio /Linearized raktą. Vartotojo patikrinimo būdas yra „Acrobat“ dokumento savybių (Document Properties) langas, kuris rodo „Fast Web View: Yes“ tik tada, kai ši struktūra faile iš tikrųjų egzistuoja ir yra aktuali.
Automatizuotiems patikrinimams programa qpdf praneša tiek apie struktūros buvimą, tiek apie jos vientisumą, kas yra svarbu, nes failas gali vis dar turėti tiesinimo žodyną, kuris nebėra aktualus – būtent tokioje būsenoje failą palieka priaugamieji atnaujinimai:
# 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
Validavimo žingsnis yra pats svarbiausias. Patikrinimas, kuris tik patvirtina žodyno egzistavimą, palaimins ir failą, kurio indeksas rodo į neteisingus poslinkius; tik patikrinimas, kuris suderina užuominų lenteles su realiomis objektų pozicijomis, parodo, ar šis optimizavimas veiksmingai atlaikys realias skaitytuvo diapazono užklausas.
Tiesinimą visada verta taikyti bet kuriam dideliam dokumentui, pateikiamam internete, ypač mobiliosioms skaityklėms esant nestabiliam ryšiui, o tai kainuoja tik keletą procentų failo dydžio dėl pradžioje patalpinto indekso. Du dalykai, kuriuos svarbu suprasti: tiek PDF vidinė struktūra, tiek serverio baitų pateikimas išorėje turi būti teisingi, ir bet koks vėlesnis redagavimas panaikina šį optimizavimą, kol failas nebus perrašytas iš naujo. Traktuokite pakartotinį tiesinimą kaip patį paskutinį žingsnį jūsų procese, kai visi kiti pakeitimai jau baigti. Čia aprašyta kryžminių nuorodų, objektų srautų bei priaugamųjų atnaujinimų elgsena yra dalis struktūrinio modelio, kurį realizuoja HotPDF Component Delphi ir C++Builder aplinkoms; platesnį failo išdėstymo kontekstą rasite straipsnyje apie tai, kaip struktūrizuojamas PDF failas, o priaugamųjų atnaujinimų bei didelių failų apdorojimo procesus kode rasite straipsnyje apie didelių PDF apdorojimą iš Delphi.