Vienas A4 formato puslapis, atvaizduojamas patogiu skaitymui masteliu, užima apie kelis megabaitus 32 bitų rastrinio paveikslėlio. Padauginkite tai iš 400 puslapių sutarties ir aritmetika nustoja būti abstrakti: jei iš anksto atvaizduosite kiekvieną puslapį, paprašysite „Windows“ daugiau nei gigabaito rastrinių paveikslėlių, kuriuos vartotojas žiūrės po vieną ekraną vienu metu. Programa arba pritrūksta adresų erdvės 32 bitų versijoje, arba pirmąsias kelias sekundes praleidžia užšaldyta, kol GPU ir puslapių analizatorius apdoroja puslapius, iki kurių dar niekas nenuėjo. Nuolatinio slinkimo skaitytuvas turi jaustis kaip viena ilga puslapių juosta, bet iš tikrųjų jis negali jų visų vienu metu laikyti atmintyje
Ši įtampa ir yra visa problema. PDFium komponentas tai išsprendžia TPdfView viduje, todėl didžioji dalis darbo yra pasirinkti tinkamą rodymo režimą ir suprasti, ką komponentas daro jūsų vardu. Dalys, kurių jis už jus nepadaro – puslapių dydžio pritaikymas skaitymo srautui ir greito slinkimo reagavimo palaikymas – yra ten, kur nedidelis kodo kiekis atsiperka. Jei vis dar surenkate aplinkinius elementus (įrankių juostą, miniatiūras, paieškos laukelį), tai aprašyta daugiafunkcės peržiūros programos kūrimo vadove; čia pagrindinė tema yra pats slinkimas
Išdėstymas yra rodymo režimas, o ne rastrinių paveikslėlių skydelis
Kuriant VCL formas dažnai kyla instinktas naudoti slinkimo laukelį (scroll box) ir jame sudėti vaizdų valdiklius, po vieną kiekvienam puslapiui. Atsispirkite tam. Toks dizainas verčia jus pačius rūpintis puslapių padėties nustatymu, slinkimo matematika ir atminties klausimais vienu metu, ir visa tai jūs iš naujo išrasite prastai. TPdfView jau modeliuoja dokumentą kaip nuolatinį puslapių srautą ir atskleidžia išdėstymą per DisplayMode savybę
Pdf := TPdf.Create(Self);
PdfView := TPdfView.Create(Self);
PdfView.Parent := Self;
PdfView.Align := alClient;
PdfView.Pdf := Pdf;
PdfView.DisplayMode := dmSingleContinuous; // one page wide, scrolls vertically
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
if not Pdf.Active then
ShowMessage('Could not open the document');
Tai yra visa nuolatinio slinkimo sąranka. dmSingleContinuous išdėsto puslapius į vieną vertikalų stulpelį, tarpus tarp jų tvarkydamas viduje, ir vaizdas slenka per tą stulpelį kaip vienas paviršius. Nereikia jungti jokio atskiro puslapio valdiklio ir nereikia rašyti slinkimo apdorojimo programos įprastai navigacijai. Atkreipkite dėmesį į Pdf.Active patikrinimą po priskyrimo: atidarant dokumentą niekada neiškeliama išimtis (exception), todėl pažeistas ar slaptažodžiu apsaugotas failas palieka Active savybę kaip False be jokios išimties, kurią būtų galima sugauti, o peržiūros programa, praleidžianti šį patikrinimą, atvaizduoja tuščią skydelį ir dėl to kaltina save
Ta pati savybė valdo ir atvertimo režimus. dmTwoPageContinuous pateikia puslapius greta vienas kito, po du eilutėje, knygos stiliaus skaitymui, kurio reikalauja kai kurie dokumentai; dmTwoPageContinuousWithCover daro tą patį, bet leidžia pirmajam puslapiui būti atskirai kaip viršeliui, kad likę atvertimai patektų į natūralią lyginių-nelyginių puslapių ribą. Visi trys slenka nepertraukiamai. Perjungimas tarp jų yra vienas priskyrimas, todėl vėliau pridėti rodymo režimo pasirinkimo sąrašą (combo box) yra labai paprasta
Rastruojami tik matomi puslapiai
Priežastis, kodėl tai puikiai veikia su 400 puslapių failu, yra ta, kad stulpelis yra virtualus. TPdfView žino kiekvieno puslapio aukštį iš dokumento puslapių medžio, tos gali apskaičiuoti bendrą slinkties dydį ir kiekvieno puslapio padėtį nieko nerastruodamas. Rastravimas – brangus žingsnis, paverčiantis puslapio turinio srautą pikseliais, vyksta tik tiems puslapiams, kurie šiuo metu patenka į matomą sritį (viewport), pridedant nedidelę atsargą, kad puslapis būtų paruoštas tuo metu, kai jis įslenkamas į vaizdą. Slenkant žemyn, į matomą sritį patenkantys puslapiai atvaizduojami, o paliekantys ją atlaisvina savo rastrinius paveikslėlius. Atminties naudojimas išlieka proporcingas tam, kas telpa ekrane, o ne dokumento ilgiui
Tai verta įsisąmoninti, nes tai keičia jūsų požiūrį į sąnaudas. Atidaryti 400 puslapių dokumentą yra pigu: analizuojama struktūra, o ne turinys. Išlaidos skaičiuojamos vienam puslapiui ir jos patiriamos „tingiai“ – tuo momentu, kai puslapis priartėja slenkant. Peržiūros programa, kuri atsidaro akimirksniu ir slenka sklandžiai, apskritai neatlieka mažiau darbo; ji paskirsto darbą per faktinį vartotojo skaitymo kelią ir atmeta tai, kas lieka už nugaros. Praktinė pasekmė yra ta, kad jūs beveik niekada nenorite priverstinai atvaizduoti puslapių anksčiau nei vartotojas. Leiskite peržiūros programai nuspręsti, kas yra matoma
Pritaikykite puslapių dydį pagal plotį, tada palikite mastelį ramybėje
Skaitymo stulpelyje puslapiai turi būti pritaikyti prie skydelio pločio, o ne pririšti prie absoliutaus mastelio. FitMode tai padaro ir toliau tai daro, kai keičiasi lango dydis
PdfView.FitMode := pfmFitWidth; // each page fills the column width; height follows
Naudojant pfmFitWidth, komponentas perskaičiuoja mastelį kaskart pakeitus rodinio dydį, todėl stulpelis visada užpildo turimą plotį, o puslapių aukščiai ir atitinkamai slinkties dydis iš to išplaukia. Yra viena spąstų vieta, į kurią papuola žmonės: tiesioginis Zoom priskyrimas grąžina FitMode atgal į pfmNone. Tai daroma sąmoningai, nes rankinis mastelis ir automatinis pritaikymas yra prieštaringi ketinimai, tačiau tai reiškia, kad koks nors atsitiktinis PdfView.Zoom := 1.0 jūsų kode tyliai išjungia pritaikymą prie pločio, ir kito dydžio keitimo metu turinys nebebus performuojamas. Jei siūlote ir mastelio valdiklį, ir pritaikymo mygtuką, traktuokite juos kaip režimo jungiklį: vieno nustatymas išvalo kitą, ir jūs nusprendžiate, kuris iš jų laimi
Absoliutiems mastelio valdikliams, kurie atrodo natūraliai, rodinys atskleidžia pritaikymo mastelius kaip reikšmes, kurias galite pritaikyti arba rodyti: PageWidthZoom[PageNumber] grąžina mastelį, kuris pritaikytų tą puslapį prie pločio, o atitinkamas PageZoom sutalpina visą puslapį. Nuskaitant šias reikšmes, galite užpildyti meniu „Pritaikyti prie pločio“ (Fit Width) / „Sutalpinti puslapį“ (Fit Page), kietai nekoduodami magiškų procentų, kurie netinkamai veikia su horizontalaus ar nestandartinio dydžio puslapiais
Išlaikykite greito slinkimo reagavimą naudodami progresyvų atvaizdavimą
Numatytasis atvaizdavimo kelias nupiešia puslapį iki galo, prieš jam grįžtant. Vienam puslapiui tai yra gerai. Tačiau greitai slenkant per tankų dokumentą – ne: kiekvienas prašvilpiantis puslapis pradeda visą rastravimą, ir jei vartotojas slenka greičiau nei puslapiai gali būti atvaizduoti, tie atvaizdavimai susikaupia, o skydelis pradeda strigti, nes darbas atliekamas puslapiams, kurie baigus jau būna už ekrano ribų. Sprendimas yra padaryti atvaizdavimą atšaukiamą ir atsisakyti jo tuo momentu, kai vartotojas slenka toliau
RenderPageProgressive atvaizduoja gabalėliais ir kiekvieno gabalėlio riboje tikrina atšaukimo žetoną (token), todėl vykstantis puslapio, kuris ką tik buvo nuslinktas, atvaizdavimas gali būti nutrauktas, o ne vykdomas iki galo
type
TFormMain = class(TForm)
// ...
private
FRenderCancel: IPdfCancellationTokenSource;
procedure RenderPageToBitmap(PageNo: Integer; Bmp: TBitmap);
end;
procedure TFormMain.RenderPageToBitmap(PageNo: Integer; Bmp: TBitmap);
var
Status: TPdfProgressiveStatus;
begin
// Cancel whatever was rendering; the old token is now signaled.
if Assigned(FRenderCancel) then
FRenderCancel.Cancel;
FRenderCancel := TPdfCancellationTokenSource.New;
Pdf.PageNumber := PageNo;
Status := Pdf.RenderPageProgressive(Bmp, 0, 0, Bmp.Width, Bmp.Height,
FRenderCancel.Token);
case Status of
prsDone: ; // bitmap is complete, paint it
prsCancelled: Exit; // superseded, discard this result
prsFailed: ShowMessage('Render failed for page ' + IntToStr(PageNo));
end;
end;
Svarbiausia forma yra grąžinama reikšmė. prsDone reiškia, kad rastrinis paveikslėlis yra visiškai nupieštas ir vertas perkėlimo į ekraną; prsCancelled reiškia, kad naujesnė slinkties padėtis pakeitė šį puslapį, todėl dalinį rezultatą išmetate, o ne rodote; prsFailed yra tikra to puslapio klaida. Atšaukimas tikrinamas gabalėlių ribose, o ne iš anksto, todėl tikėkitės dešimčių milisekundžių delsos tarp Cancel iškvietimo ir realaus atvaizdavimo sustabdymo. Tai vis tiek yra daug pigiau, nei leisti senam viso puslapio atvaizdavimui blokuoti eilę. Perduodant nil kaip žetoną, atvaizduojama tiesiai iki pabaigos, o tai yra teisingas pasirinkimas vienkartiniam atvaizdavimui, pvz., spausdinimo peržiūrai, kur nėra su kuo lyginti atšaukimo
Kai vietoj to iškviečiate funkcinę RenderPage formą, kuri grąžina naują TBitmap, atminkite, kad ją iškvietęs kodas tampa jos savininku ir privalo ją atlaisvinti (Free). Slinkties cikle, kuris priskiria po rastrinį paveikslėlį kiekvienam puslapiui, tai pamiršus atsiranda nutekėjimas, kuris didėja su kiekvienu vartotojo praeitu puslapiu, ir tai yra būtent ta neribotos atminties klaida, kurios nuolatinis dizainas turėjo išvengti. Kur galite, atvaizduokite į pakartotinai naudojamą rastrinį paveikslėlį
Kas jums lieka
Nuolatinio slinkimo skaitytuvo pateikimas didžiąja dalimi priklauso nuo komponento. Išdėstymui pasirenkate dmSingleContinuous, nustatote pfmFitWidth, kad stulpelis būtų performuojamas kartu su langu, ir patikrinate Pdf.Active, kad blogas failas garsiai praneštų apie klaidą. Vienintelė dalis, kurią verta parašyti patiems, yra atšaukiamas atvaizdavimas, nes skaitytuvas vertinamas pagal tai, jak jis elgiasi, kai kas nors nutempia slinkties juostą į ilgo dokumento apačią, ir skydelis arba spėja kartu, arba ne. Viskas po to – teksto žymėjimas skirtinguose puslapiuose, paieškos paryškinimas, žymelių medis – yra sąsajos darbas, esantis ant šio slinkimo paviršiaus, o ne jo viduje
Čia parodytos TPdfView, DisplayMode ir RenderPageProgressive API yra PDFium komponento skirto Delphi ir Lazarus dalis