Tegyen egy 80 MB-os beszkennelt jelentést egy hivatkozás mögé, nyissa meg egy böngészőben, és figyelje meg, mi történik: a megjelenítő egy üres panelen (blank pane) ül, amíg a bájtok nagy része meg nem érkezik, majd egyszerre megfesti (paints) az első oldalt. Ugorjon a 40. oldalra, és egy rosszul felépített fájl esetén a teljes letöltés újraindulhat. A frusztráló rész az, hogy az olvasó mindig csak az első oldalt akarta. A linearizálás (linearization) a strukturális válasz erre a problémára. Úgy rendezi át a PDF-et, hogy a megjelenítő az első oldalt a fájl egy kis előtagjából (small prefix) renderelheti, a többit pedig igény szerint lehívja (fetch), és ezért forgalmazza (markets) az Adobe a funkciót "Fast Web View" néven
Ezek közül egyik sem jelent más fájlformátumot. Egy linearizált PDF egy közönséges PDF, amelyet egy megfelelő (conforming) olvasó mindenféle speciális kezelés nélkül meg tud nyitni. A trükk teljes egészében abban rejlik, hogy hogyan vannak a bájtok sorba rendezve, és abban a két extra struktúrában, amelyet a fájl hordoz. Az ISO 32000-1 a teljes elrendezést az F. mellékletben (Annex F) határozza meg, és ha egyszer látta az elrendezést (layout), a viselkedés már nem tűnik varázslatnak, hanem inkább a fájl sorrendjének tudatos feláldozásaként (deliberate trade) az első megfestés késleltetésének (first-paint latency) csökkentéséért
Mit is rendez át valójában a linearizálás
Egy normál PDF az objektumait szinte bármilyen sorrendben szétszórhatja. A fájl végén található kereszthivatkozási tábla (cross-reference table) teszi ezt lehetővé: az olvasó a végére ugrik (seeks to the end), beolvassa a startxref mutatót, betölti az xref-et, és onnantól kezdve minden objektumot megtalál az eltolás (offset) alapján. Ez a kialakítás kiváló a helyi fájloknál, ahol a végére ugrás semmibe sem kerül, de gyenge a hálózaton keresztül érkező (streaming in) fájloknál, ahol a vége pontosan az a rész, amely utoljára érkezik meg. Az első oldal megjelenítéséhez egy hagyományos olvasónak szüksége van az oldal objektumra, annak tartalomfolyamára (content stream), az általa hivatkozott betűtípusokra és a megrajzolt képekre, és egy rendezetlen fájlban ezek bárhol lehetnek, beleértve az utolsó megabájtot is
A linearizálás rögzíti a sorrendet. Az első oldal megjelenítéséhez szükséges objektumokat egy összefüggő blokkba gyűjtik az eleje közelében, közvetlenül egy kis fejléc rész (header section) után, így azok korán megérkeznek a bájtfolyamban. Minden más, a hátralévő oldalak és az általuk megosztott erőforrások, kiszámítható sorrendben következnek. Egy második, teljes kereszthivatkozási tábla továbbra is a végén él azon olvasók számára, akik figyelmen kívül hagyják az optimalizálást, de egy linearizált fájl az első oldal kereszthivatkozását és a streaming olvasónak szükséges paramétereket is előre helyezi. Az olvasónak többé nem kell elérnie a végét (tail), mielőtt bármit is rajzolhatna
Az első oldal objektumkészlete és a linearizálási paraméter szótár (linearization parameter dictionary)
A legelső objektum egy linearizált fájlban, a %PDF fejléc után, a linearizálási paraméter szótár (linearization parameter dictionary). Ezt keresi egy streaming olvasó (streaming reader), hogy eldöntse, jelen van-e az optimalizálás, és hogyan használja azt. A szótár rögzíti a teljes fájl hosszát, azt a bájteltolást, ahol a fő kereszthivatkozási rész kezdődik, az első oldal objektumszámát, valamint a következő tippfolyam (hint stream) helyét és hosszát. Ezekkel a számokkal egy olvasó már a nyitó kilobájtokból tudja, hogy mennyit kell lehívnia (fetch) az első oldal megjelenítéséhez, és hol kell keresnie azt az indexet, amely lehetővé teszi, hogy máshová ugorjon
Az F. melléklet (Annex F) szigorú azzal kapcsolatban, hogy mit jelent itt az "első oldal". Az első oldal szakasznak tartalmaznia kell magát az oldal objektumot, a tartalomfolyamait és azokat az erőforrásokat, amelyekre ezek a folyamok hivatkoznak, úgyhogy az oldal önellátóvá (self-sufficient) válik, miután ez az előtag letöltődött. A megosztott erőforrásokat, egy minden oldalon használt betűtípust, egy fejlécben ismétlődő logót, speciálisan kezelik: elég korán jelennek meg ahhoz, hogy kiszolgálják az első oldalt, de megosztottként (shared) vannak megjelölve, így az olvasó nem hívja le őket újra, amikor később a 30. oldalt rendereli. Ez a különbségtétel (distinction) az oldal-privát és a megosztott objektumok között az a rész, amelyet a legtöbb házi (home-grown) "optimalizáló" elront, és ha elrontják, az olyan fájlt eredményez, amely azt állítja magáról, hogy linearizált, de mégis elakad (stalls)
Tippfolyamok (Hint streams): az index, amely olcsóvá teszi az oldalugrásokat
Az első oldal gyors megjelenítése csak az érték fele. A másik fele az, hogy tetszőleges (arbitrary) oldalra ugorhatunk anélkül, hogy mindent letöltenénk a kettő között, és ezt biztosítják a tippfolyamok (hint streams). Egy linearizált fájl egy oldaleltolási tipptáblát (page offset hint table) és egy megosztott objektum tipptáblát hordoz, amelyeket a paraméterszótárból (parameter dictionary) hivatkozott folyamként tárolnak. Az oldaleltolási tábla minden oldal esetében rögzíti, hogy annak objektumai hol kezdődnek a fájlban, és milyen hosszan futnak. A megosztott objektum tábla ugyanezt teszi a több oldalon át használt erőforrásokkal
E táblázatok birtokában a 40. oldalt kérő olvasó nem szekvenciálisan elemzi a fájlt. Megnézi a tipptáblát (hint table), hogy megtudja, milyen bájttartományt (byte range) foglal el a 40. oldal, pontosan ezt a tartományt kéri a szervertől, és megjeleníti az oldalt, amint ezek a bájtok megérkeznek, behúzva minden megosztott erőforrást, amellyel még nem rendelkezik, ugyanezen a mechanizmuson keresztül. A tippfolyam valójában (in effect) egy közvetlen hozzáférésű térkép (random-access map), amely a dokumentumra van fektetve, és ez az oka annak, hogy egy jól linearizált 500 oldalas fájl reszponzívnak tűnik (feels responsive) egy lassú kapcsolaton, míg egy ugyanolyan méretű optimalizálatlan fájl nem
Miért kell a szervernek együttműködnie (cooperate)
A linearizálás feltételezi, hogy a transzport (transport) képes a fájl tetszőleges szeleteinek (arbitrary slices) kézbesítésére, és ezt a feltételezést érdemes ellenőrizni, mielőtt a formátumot okolná (credit) a gyenge eredményekért. A mechanizmus a HTTP byte-serving: az olvasó tartomány kéréseket (range requests) ad ki, a szerver pedig 206 Partial Content válaszokkal válaszol. Ha a szerver nem hirdeti az Accept-Ranges: bytes-ot, vagy ha egy proxy vagy CDN előtte összeomlasztja (collapses) a tartomány kéréseket teljes átvitellé (full transfers), az olvasónak nincs módja elszigetelten lehívni a 40. oldalt, és visszaesik (falls back) a teljes fájl letöltésére. A PDF-en belüli struktúra ekkor tökéletesen helyes, de teljesen kárba vész (wasted)
Ez az a hiba (failure), amelyet a leggyakrabban félrediagnosztizálnak (misdiagnosed) "a linearizálás nem működik" néven. A fájl rendben van; a kézbesítési útvonal (delivery path) nincs. Mielőtt újraépítene egy dokumentumot, erősítse meg egy feltételes kéréssel (conditional request), hogy a gazdagép (host) valóban részleges tartalmat (partial content) ad vissza ahhoz az URL-hez, amelyet az olvasó elér. Sok statikus gazdagép ezt alapértelmezés szerint megteszi, és sok rosszul konfigurált alkalmazásszerver és gyorsítótárazási réteg (caching layers) nem
Az inkrementális frissítések csendben megtörik a linearizálást
Íme a korlátozás (constraint), amely meglepi azokat az embereket, akik helyesen generálnak linearizált fájlokat, majd csodálkoznak, miért párolog el (evaporates) az optimalizálás. A linearizálás egyetlen, gondosan megrendezett elrendezéstől (layout) függ, amelynek indexe az elején van. Egy inkrementális frissítés (incremental update) ezt tervezéséből adódóan (by design) megsérti. Amikor egy eszköz egy inkrementális mentéssel aláírást ad hozzá, kitölt egy űrlapmezőt (form field) vagy hozzáfűz egy megjegyzést (annotation), nem írja újra a fájlt. Hozzáfűzi (appends) a módosított objektumokat, egy új kereszthivatkozási részt és egy új trailert a végéhez, érintetlenül hagyva az eredeti bájtokat. Ez a hozzáfűzés a teljes lényege (whole point) az inkrementális frissítéseknek: gyors, és megőrzi a korábbi revíziót az auditáláshoz vagy az aláírás érvényesítéséhez
A mellékhatás (side effect) az, hogy a fájlnak most a legújabb kereszthivatkozási adatai vannak a végén (tail), a gondosan elhelyezett első oldal blokk (first-page block) után, és az elején lévő linearizálási paraméter szótár (linearization parameter dictionary) olyan elrendezést ír le, amely már nem felel meg a fájlnak. Egy megfelelő (conforming) olvasó észleli az eltérést (mismatch), és a dokumentumot normál, nem linearizált PDF-ként kezeli. A Fast Web View eltűnt (gone), annak ellenére, hogy az eredeti linearizált struktúra még mindig ott csücsül a fájl első felében. Ha több frissítést fűz hozzá, mindegyik egy újabb revíziót (revision) halmoz (stacks) a végére, és a szakadék az elavult (stale) elülső index és a valós állapot között szélesedik
Ha a munkafolyamata mind szerkesztéseket, mind Fast Web View-t igényel, a szabály közvetlenül a struktúrából következik: szerkesszen inkrementálisan, amíg a dokumentum változóban (in flux) van, majd a végén egyszer linearizálja újra. A teljes újraírás (full rewrite) az, ami helyreállítja az elrendezést. A HotPDF kifejezéseivel (terms) ez azt jelenti, hogy egy folyamatban lévő szerkesztés a BeginIncrementalUpdate és a SaveIncrementalUpdate metódusokon megy keresztül, amelyek hozzáfűznek egy deltát, míg a befejező lépés betölti az egész dokumentumot, és frissen (fresh) szerializálja (serializes) azt a LoadFromFile majd a SaveLoadedDocument segítségével, amely eldobja a felhalmozódott régi revíziókat, és egy tiszta elrendezést (clean layout) bocsát ki. Ugyanez a csere (trade) jelenik meg az objektum folyamok (object streams) esetében: a UseObjectStreams és a UseXRefStream együttes engedélyezése tömöríti (compresses) a kereszthivatkozást, és szorosan (tightly) csomagolja az objektumokat, ami segít a fájlméreten, de – mint minden strukturális választást – ezt is a végső újraírás során kell alkalmazni, nem pedig egy hozzáfűzött (appended) revízióra rácsavarozni (bolted onto)
// Folyamatban lévő szerkesztések (In-flight edits): delta hozzáfűzése, előzetes revíziók érintetlenül hagyása.
// Ez a fájlt NEM linearizált formában hagyja.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');
// Befejező lépés (Finishing step): a teljes újraszerializálás (re-serialization) egy tiszta elrendezést (clean layout) hoz létre,
// eldobva a felhalmozott revíziókat. Futtassa újra a linearizálóját a kimeneten.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');
A HotPDF nem tesz közzé egyetlen hívásos (one-call) "linearizáló" rutint, így a praktikus minta (practical pattern) az, hogy egy tiszta, teljesen újraírt fájlt állít elő, és egy dedikált (dedicated) optimalizálót futtat rajta. A parancssori eszközök (command-line tools) közvetlenül kezelik az átrendezést. A qpdf egyetlen jelzővel (flag) írja újra a fájlt linearizált formába:
qpdf --linearize report-final.pdf report-web.pdf
Hogyan lehet megállapítani, hogy egy fájl linearizált-e
Ne bízzon a fájlnévben vagy abban az eszközben, amely azt állítja magáról (claims), hogy létrehozta azt; ellenőrizze a bájtokat. A legközvetlenebb ellenőrzés a fájl feje (head): nyissa meg, és keresse a linearizálási paraméter szótárat első objektumként a fejléc (header) után, amely a /Linearized kulcsot hordozza. Egy olvasó felé irányuló hivatkozás (reader-facing shortcut) az Acrobat dokumentumtulajdonságok (Document Properties) párbeszédpanele, amely csak akkor jelenti a "Fast Web View: Yes" (Igen) értéket, ha a struktúra valóban (genuinely) jelen van és aktuális (current)
A szkriptelt ellenőrzésekhez (scripted checks) a qpdf mind a struktúra jelenlétét, mind az integritását (integrity) jelenti, ami azért számít, mert egy fájl hordozhat olyan linearizálási szótárat, amely már nem tükrözi az elrendezését, pontosan abban az állapotban, amelyet egy inkrementális frissítés hátrahagy (leaves behind):
# Jelenti, hogy "File is linearized", és érvényesíti a tipptáblákat (hint tables) az elrendezés (layout) alapján
qpdf --check report-web.pdf
# Részletesen kiírja (Dumps) a linearizálási paramétereket és a tippadatokat (hint data)
qpdf --show-linearization report-web.pdf
Az érvényesítési (validation) lépés az, amelyik megdolgozik a pénzéért (earns its keep). Egy olyan menet (pass), amely csak a szótár létezését erősíti meg, boldogan áldását adja (bless) egy olyan fájlra, amelynek indexe rossz eltolásokra (offsets) mutat; egy olyan ellenőrzés, amely egyezteti (reconciles) a tipptáblákat (hint tables) a tényleges (actual) objektumpozíciókkal, az mondja meg önnek, hogy az optimalizálás megállja-e a helyét egy valódi olvasó tartomány kérései (range requests) alatt
A linearizálást (Linearization) továbbra is érdemes alkalmazni minden weben keresztül szolgáltatott (served over the web) nagyméretű dokumentumra, különösen az egyenetlen (uneven) kapcsolatokkal rendelkező mobil olvasóknál, és a fájlméret néhány százalékába kerül az előre betöltött index (front-loaded index) miatt. A két dolog, amit egyenesben (keep straight) kell tartani, hogy mind a PDF-en belüli struktúrának, mind az azon kívüli bájt-kiszolgálásnak (byte-serving) helyesnek kell lennie, és hogy minden utólagos (after the fact) szerkesztés visszavonja (undoes) az optimalizálást, amíg újra nem írja a fájlt. Kezelje az újralinearizálást (re-linearization) a folyamat utolsó lépéseként, miután minden más változás rendeződött. Az itt leírt kereszthivatkozás (cross-reference), objektumfolyam (object-stream) és inkrementális frissítés (incremental-update) viselkedés annak a strukturális modellnek a része, amelyet a Delphihez és C++Builderhez készült HotPDF Component megvalósít (implements); a tágabb fájlelrendezési (file-layout) háttérhez lásd a PDF struktúráját (how a PDF is structured), és az inkrementális frissítéssel és a nagyméretű fájlokkal kapcsolatos kód szintű munkafolyamatokhoz lásd nagyméretű PDF-ek feldolgozása Delphiből című cikket