HotPDF rašo linearizuotus PDF failus, tvarką, kurią Acrobat vadina Fast Web View, per LinearizeOutput savybę THotPDF klasėje. Jos nustatymas prieš BeginDoc priverčia HotPDF pertvarkyti baigtą objektų grafą taip, kad baitų intervalus suprantantis skaitytuvas galėtų parodyti pirmą puslapį, atsiuntęs tik pradinę failo dalį, vietoj viso dokumento atsisiuntimo pirmiausia. Mechanizmas — ISO 32000-1 F priedas
Priežastis, kodėl tai svarbu, nėra rafinuota. Įprastas PDF savo kryžminių nuorodų lentelę deda pačioje pabaigoje, todėl žiūryklei tenka pasiekti paskutinį baitą, prieš sužinant, kur kas yra. Perduokite naršyklei 200 puslapių nuskaitytą ataskaitą, ir vartotojas žiūri į besisukantį ratuką visą perdavimo laiką, nors vienintelis dalykas, kurio jis norėjo, buvo pirmas puslapis. Linearizacija tai išsprendžia, apmokant kainą rašymo metu. Šis straipsnis skirtas būtent šiam rašymo keliui, skaidymui, matavimo ciklui ir griežtoms riboms; konceptualų pagrindą apie tai, ką suteikia Fast Web View, apima ankstesnis straipsnis apie PDF linearizaciją ir Fast Web View
Ką iš tikrųjų garantuoja linearizuotas išdėstymas
Linearizuotas failas — paprastas PDF su itin konkrečia fizine tvarka, ir kiekviena jo garantija kyla iš tos tvarkos, ne iš kokio nors naujo objekto tipo. HotPDF išveda dalis F priedo nurodyta seka: linearizacijos parametrų žodynas pirmuosiuose 1024 baituose, ankstyva kryžminių nuorodų lentelė, dokumento lygio objektai, pirminis pagalbinis srautas, pirmasis puslapis su savo privačiais objektais, tada likę puslapiai, tada bendri objektai, tada viskas kita, ir galiausiai pagrindinė kryžminių nuorodų lentelė
Skaidymas yra kildinamas, ne deklaruojamas. HotPDF eina per nuorodų grafą nuo kiekvieno puslapio objekto ir kiekvienam netiesioginiam objektui užrašo, kiek puslapių jį pasiekia ir kuris puslapis jį pasiekė pirmas. Objektas, naudojamas lygiai vieno puslapio, tampa privatus tam puslapiui. Objektas, pasiekiamas daugiau nei vieno, tampa bendras. Katalogas, plius viskas, ką jis nurodo per /ViewerPreferences, /OpenAction, /Threads ir /AcroForm, plius šifravimo žodynas, kai apsauga aktyvi, sudaro dokumento lygio grupę, kuri turi eiti prieš visa kita. Puslapių medžio mazgai sąmoningai sulaikomi, kad neužterštų pirmo puslapio skyriaus
Parametrų žodynas neša skaičius, kurių skaitytuvui reikia, prieš jam perskaitant bet ką kita: /L viso failo ilgiui, /H pagalbinio srauto poslinkiui ir ilgiui, /O pirmojo puslapio objekto numeriui, /E baitui, kur baigiasi pirmojo puslapio skyrius, /N puslapių skaičiui ir /T pagrindinio kryžminių nuorodų lentelės įrašo poslinkiui. Kiekvienas iš jų — baitų poslinkis į failą, kuris tuo momentu, kai jums reikia juos rašyti, dar neegzistuoja
Kodėl pagalbinės lentelės poslinkiai turi konverguoti?
Todėl, kad skaičiai parametrų žodyne apibūdina failą, kuriame jie yra, o bet kurio iš jų keitimas keičia failą. Tai — pagrindinis linearizuoto rašytojo sunkumas, ir kaip tik dėl to HotPDF matuoja pakartotinai vietoj vienkartinio rašymo. Praplėskite /T nuo 6 skaitmenų iki 7, ir parametrų žodynas išauga vienu baitu; antraštė išauga; kiekvienas objektas pasislenka; pagrindinė kryžminių nuorodų lentelė persikelia; /T dabar reikalauja kitokios reikšmės. Išdėstymas turi pasiekti fiksuotą tašką prieš įrašant nei vieną tikros išvesties baitą
HotPDF tai tvarko su ribota iteracija. Pirmiausia ji serializuoja kiekvieną objektą į skaičiuojantį srautą, kuris registruoja ilgį, nesaugodamas baitų, todėl kiekvienas objektas turi žinomą serializuotą dydį. Tada ji vykdo išdėstymo etapą, kuris priskiria poslinkius dokumento lygio grupei, pagalbiniam srautui, pirmojo puslapio grupei, vėlesnėms puslapių grupėms, bendrai grupei ir likučiui, ir praneša, kur atsidurtų pagrindinė kryžminių nuorodų lentelė. Šis rezultatas grąžinamas kaip įvestis kitam etapui. Ciklas apribotas iki aštuonių bandymų, o nekonvergavimas iškelia išimtį, o ne sukuria failą su patikimai atrodančiais neteisingais poslinkiais
CandidateMainOffset := 0;
for Attempt := 0 to 7 do
begin
CalculateLayout(CandidateMainOffset, FirstXRefData,
HintOffset, EndFirstPage, NewMainOffset);
if NewMainOffset = CandidateMainOffset then
Break;
CandidateMainOffset := NewMainOffset;
end;
if NewMainOffset <> CandidateMainOffset then
raise Exception.Create('Linearization layout did not converge');
Dvi detalės saugo ciklą nuo blaškymosi. Parametrų žodynas rašomas į fiksuotą 384 baitų lizdą, užpildytą tarpais, todėl jo paties augimas niekada negali destabilizuoti išdėstymo; jei žodyno tekstas kada nors viršytų šią rezervaciją, HotPDF iškeltų išimtį vietoj tylaus visko perstūmimo. Ir po konvergencijos HotPDF paleidžia dar vieną patvirtinantį išdėstymo etapą bei perbaigia pagalbinio srauto ilgį, nes pats pagalbinis srautas užkoduoja poslinkius, kurie buvo žinomi tik nusistovėjus išdėstymui. Viso šio matavimo nauda — HotPDF niekada nebuferuoja antros dokumento kopijos: kai poslinkiai fiksuoti, objektai serializuojami tiesiai į paskirties srautą, su tvirtinimu kiekvienoje skyriaus riboje, kad įrašyti baitai atitinka pažadėtą poslinkį
Įjungimas iš Delphi
API paviršius — vienas loginis dydis, ir vienintelis reikalavimas — nustatyti jį prieš prasidedant generavimui. LinearizeOutput pagal nutylėjimą yra False, o išdėstymo etapas vyksta rašant dokumentą, todėl jo priskyrimas po EndDoc nieko nepasiekia
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.FileName := 'fast-view.pdf';
PDF.Version := pdf17;
PDF.LinearizeOutput := True; // must precede BeginDoc
PDF.BeginDoc;
PDF.Canvas.TextOut(72, 72, 'First page');
PDF.EndDoc;
finally
PDF.Free;
end;
end;
Vienas diegimo įspėjimas svarbesnis už viską kodo pusėje. Linearizacija atsiperka tik tada, kai transportas palaiko HTTP intervalų užklausas. Aptarnaukite tą patį failą per galinį tašką, kuris jį siunčia visą, arba per CDN konfigūraciją, ignoruojančią Range, ir jūs nusipirkote lėtesnį rašymo kelią bei didesnį failą be jokios naudos vartotojui. Patikrinkite serverį prieš tikrindami kodą
Kodėl linearizacija nustelbia UseXRefStream ir UseObjectStreams?
Todėl, kad linearizuotam rašytojui reikia, kad kiekvienas objektas turėtų savo tiesiogiai adresuojamą baitų poslinkį, o abi šios funkcijos tai atima. HotPDF todėl išveda tradicines tekstines kryžminių nuorodų lenteles ir nesuspaustus netiesioginius objektus visada, kai LinearizeOutput įjungta, net jei iškviečiantysis taip pat nustatė UseXRefStream ar UseObjectStreams. Tai — sąmoningas nustelbimas, ne konfliktas, kurį jums reikėtų spręsti pačiam
Šis pagrindimas seka iš pagalbinių lentelių. Pagalbinė lentelė aprašo, kur prasideda puslapio skyrius ir koks jo ilgis, todėl skaitytuvas gali paprašyti būtent to intervalo. Objektas, sudėtas į /ObjStm konteinerį, visai neturi savarankiško poslinkio; jis egzistuoja tik kaip griežinys kitame suglaudintame sraute, kurį reikia atsiųsti ir išplėsti kaip vienetą. Jei skaičiavote objektų srautus dėl failo dydžio, suprask kite, kad linearizacija ir glaudinimas čia traukia priešingas kryptis, ir perskaitykite šį kompromisą papildomame straipsnyje apie objektų srautus ir priaugančius atnaujinimus HotPDF. Ta pati įtampa formuoja hibridinių nuorodų failus, kurie egzistuoja būtent tam, kad senesnės skaityklės veiktų kartu su srautu pagrįstomis lentelėmis, kaip aprašyta straipsnyje apie hibridinius kryžminių nuorodų srautus Office generuotuose PDF failuose
Yra ir versijos grindys. Linearizacijai reikia PDF 1.2 ar naujesnės. Jei pasirinkta versija senesnė, HotPDF ją automatiškai pakelia, nebent nustatyta StrictVersionLock, tokiu atveju rašymas iškelia išimtį, o ne tyliai paaukština dokumentą, kurį jūs sąmoningai užfiksavote
4 GiB siena, ir kodėl HotPDF atsisako, o ne apkarpo
Linearizacijos pagalbinės lentelės saugo poslinkius kaip 32 bitų reikšmes, todėl linearizuotas failas negali adresuoti nieko 4 GiB ar daugiau, ir HotPDF tokią išvestį atmeta su aiškia išimtimi, o ne rašo failą su suvyniotais poslinkiais. Ši riba — ne HotPDF realizacijos pasirinkimas; tai — F priedo apibrėžtų laukų plotis
Patikra taikoma trijose vietose, ir visos trys svarbios. HotPDF patikrina kiekvieną objektą, kai tik žinomas jo serializuotas ilgis, patikrina kiekvieno puslapio skyriaus ilgį, kuriant pagalbinius įrašus, ir patikrina galutinį failo ilgį, kai nustatytas pagrindinės kryžminių nuorodų lentelės dydis. Ankstyvas nepavykimas — visas dalykas: pagalbinė lentelė su tyliai apkarpytu poslinkiu sukuria failą, kuris teisingai atsiveria žiūryklėje, atsisiunčiančioje jį visą, ir nepavyksta tik baitų intervalus naudojančiam klientui, kuriam linearizacija ir buvo skirta, o tai — pats blogiausias gedimo tipas, nes jūsų testinė žiūryklė niekada to nepakartos. Jei generuojate daugiagigabaitinę išvestį, linearizacija — ne tas įrankis, o srautinis metodas, aprašytas užrašuose apie Direct File API dideliems PDF darbo srautams, yra kryptis, į kurią verta žiūrėti
Linearizacijos aptikimas įkeltame faile
THotPDF.IsLoadedLinearized praneša, ar šiuo metu įkeltas dokumentas jau buvo parašytas linearizuota forma, ir atsako iš nuotraukos, paimtos prieš analizę, ne iš gyvo srauto. HotPDF perskaito pirmus 1024 baitus nuo šaltinio srauto pozicijos nulis, juose ieško pirmo obj rakto žodžio, tada — /Linearized įrašo su reikšme 1, ir talpina loginį rezultatą podėlyje
var
PDF: THotPDF;
PageCount: Integer;
begin
PDF := THotPDF.Create(nil);
try
PageCount := PDF.LoadFromFile('incoming.pdf');
if (PageCount > 0) and (not PDF.IsLoadedLinearized) then
Writeln('Source is not Fast Web View ready');
finally
PDF.Free;
end;
end;
Du apribojimai šiame apraše yra svarbūs. Aptikimas negali pasikliauti srauto pozicija, nes iki tada, kai programos kodas užduoda šį klausimą, analizatorius ją jau pastūmė, ir negali skaityti iš naujo pagal poreikį, nes LoadFromFile paleidžia vidinį šaltinio srautą, kai įkėlimas baigiasi. Todėl — nuotraukos-prieš-analizę-ir-podėlio dizainas. Skenavimas taip pat sąmoningai griežtas dėl reikšmės: priimama tik /Linearized 1 arba skaitmeniniu požiūriu lygiavertė forma su visiškai nuline trupmenine dalimi, nes failas, kurio parametrų žodynas sako ką nors kita, nedaro F priedo pažado
Delphi įrašo spąstas, vertas pasiskolinti
Vietiniai įrašai su dinaminiais masyvais inicializuoja savo valdomus laukus ir nieko daugiau, ir jei šalia masyvo laikote paprastą Count lauką, jį reikia išvalyti pačiam. Tai kliudė linearizacijos skaidymui kūrimo metu, ir tai — klaidos rūšis, kainuojanti dieną kaip tik todėl, kad viena platforma ją paslepia
type
THPDFLinearIndexList = record
Values: THPDFIntegerArray; // managed field: cleared for you
Count: Integer; // plain field: whatever was on the stack
end;
// Required, not cosmetic:
Part4 := Default(THPDFLinearIndexList);
Part6 := Default(THPDFLinearIndexList);
Part8 := Default(THPDFLinearIndexList);
Part9 := Default(THPDFLinearIndexList);
Dinaminio masyvo laukas yra su nuoroda skaičiuojamas, todėl kompiliatorius jį nulina. Šalia esantis Count — paprastas sveikasis skaičius be tokios garantijos, ir neinicializuotas Count nusiunčia patį pirmą pridėjimą į savavališką indeksą. Win32 platformoje steko lizdas atsitiktinai turėjo nulį, pridėjimas nukrito į indeksą 0, ir visi testai praėjo. Win64 platformoje tas pats kodas rašė už masyvo ribų. Ši pamoka apibendrinama gerokai plačiau nei linearizacija: kai įrašas maišo valdomus ir nevaldomus laukus, priskirkite Default(TRecord) ir liaukitės spėlioję, kuriuos laukus dengia kompiliatorius, ir niekada nelaikykite žalio Win32 paleidimo įrodymu, kad inicializacija teisinga
LinearizeOutput ir IsLoadedLinearized nariai, aprašyti čia, dalyvauja standartiniame HotPDF Component Delphi ir C++Builder platformoms; produkto puslapyje pateikta pilna savybių dokumentacija, įskaitant sąveikos taisykles su kryžminių nuorodų srautais, objektų srautais ir versijos fiksavimu