Jedna stránka A4 vykreslená pri pohodlnom čítacom priblížení má niekoľko megabajtov 32-bitovej bitmapy. Vynásobte to 400-stránkovou zmluvou a aritmetika prestane byť abstraktná: vykresliť všetky stránky vopred znamená požiadavku na Windows o viac ako gigabajt bitmáp, na ktoré sa používateľ pozrie vedno po obrazovke. Aplikácia buď vyčerpá adresový priestor pri 32-bitovom zostavení alebo trávi prvé niekoľko sekúnd zmrazená, kým GPU a parser stránok spracovávajú stránky, na ktoré nikto nescrolloval. Čitateľ s nepretržitým posunom musí pôsobiť ako jeden vysoký pás stránok, ale v skutočnosti ich nemôže všetky naraz držať v pamäti.
Toto napätie je celým problémom tu. PDFium VCL ho rieši vo vnútri TPdfView, takže väčšina práce spočíva vo výbere správneho zobrazovacieho režimu a pochopení toho, čo komponent robí za vás. Časti, ktoré nerobí za vás, nastavenie veľkosti stránok pre tok čítania a udržiavanie plynulosti rýchleho posunu, sú miesto, kde trochu kódu stojí za to. Ak stále zostavujete okolie (panel nástrojov, náhľady, vyhľadávacie pole), walkthrough prehliadača s bohatými funkciami pokrýva túto oblasť; tu je predmetom samotné posúvanie.
Rozloženie je zobrazovací režim, nie panel bitmáp
Intuícia z práce s formulármi VCL je siahnuť po scroll boxe a stohovať dovnútra ovládacie prvky obrázkov, jeden na stránku. Odolajte tomu. Tento návrh vás núti vlastniť pozicionovanie stránok, matematiku posunu a otázku pamäte naraz, a všetky ich znovu vynájdete zle. TPdfView už modeluje dokument ako nepretržitý beh stránok a vystavuje rozloženie prostredníctvom svojej vlastnosti DisplayMode.
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');
To je celé nastavenie nepretržitého posunu. dmSingleContinuous umiestni stránky do jedného vertikálneho stĺpca s medzerami medzi nimi spravovanými interne a pohľad sa posúva cez tento stĺpec ako jeden povrch. Neexistuje ovládací prvok na stránku, ktorý by bolo treba zapojiť, a žiadny obsluhovač posunu, ktorý by bolo treba napísať pre bežnú navigáciu. Poznamenajme kontrolu Pdf.Active po priradení: otvorenie dokumentu nikdy nevyvolá výnimku, takže poškodený alebo heslom chránený súbor nechá Active sedieť na False bez výnimky na zachytenie, a prehliadač, ktorý túto kontrolu preskakuje, zobrazí prázdny panel.
Rovnaká vlastnosť nesie režimy dvojstránky. dmTwoPageContinuous umiestni stránky vedľa seba, dve na riadok, pre čítanie v knižnom štýle, ktoré niektoré dokumenty vyžadujú; dmTwoPageContinuousWithCover robí to isté, ale nechá stránku jedna stáť osamote ako obal, aby zvyšok rozsahov padal na prirodzenú párno-nepárnu hranicu. Všetky tri sa posúvajú nepretržite. Prepínanie medzi nimi je jedno priradenie, čo umožňuje jednoducho neskôr pridať rozbaľovací zoznam zobrazovacieho režimu.
Rasterizácia sa vykonáva iba pre viditeľné stránky
Dôvod, prečo to funguje pri 400-stránkovom súbore, je ten, že stĺpec je virtuálny. TPdfView pozná výšku každej stránky zo stromu stránok dokumentu, takže môže vypočítať celkový rozsah posunu a polohu každej stránky bez rasterizácie čohokoľvek. Rasterizácia, nákladný krok, ktorý premieňa obsahový tok stránky na pixely, prebieha iba pre stránky, ktoré sa práve pretínajú s náhľadom, plus malý okraj, aby stránka bola pripravená, keď sa posunie do zobrazenia. Keď scrollujete nadol, stránky vstupujúce do viewportu sa vykreslia a stránky opúšťajúce ho majú ich bitmapy uvoľnené. Pamäť ostáva proporcionálna k tomu, čo sa zmestí na obrazovku, nie k dĺžke dokumentu.
Toto stojí za internalizovanie, pretože mení spôsob, ako uvažujete o nákladoch. Otvoriť 400-stránkový dokument je lacné: parsuje štruktúru, nie obsah. Náklad je na stránku a platí sa lenivo, v okamihu, keď sa stránka priblíži pri scrollovaní. Prehliadač, ktorý sa otvára okamžite a scrolluje plynule, nerobí celkovo menej práce, rozširuje prácu cez skutočnú čítaciu cestu používateľa a zahadzuje to, čo zostalo pozadu. Praktickým dôsledkom je, že takmer nikdy nechcete silovo vykresľovať stránky pred používateľom. Nechajte pohľad rozhodnúť, čo je viditeľné.
Nastavte veľkosť stránok na šírku a potom ponechajte zoom na pokoji
Čítací stĺpec vyžaduje stránky nastavené na šírku panela, nie pripojené k absolútnemu priblíženiu. FitMode to robí a pokračuje v tom, keď sa zmení veľkosť okna.
PdfView.FitMode := pfmFitWidth; // each page fills the column width; height follows
Pri pfmFitWidth komponent prepočíta zoom vždy, keď sa veľkosť pohľadu zmení, takže stĺpec vždy vypĺňa dostupnú šírku a výšky stránok, a teda rozsah posunu, z toho vyplývajú. Jedna pasca, ktorá ľudí chytí: priamo priradiť Zoom resetuje FitMode späť na pfmNone. To je zámerné, pretože manuálny zoom a automatické prispôsobenie sú protichodné zámery, ale znamená to, že blúdivé PdfView.Zoom := 1.0 niekde vo vašom kóde ticho vypne prispôsobenie na šírku a nasledujúce zmeny veľkosti prestanú pretekat. Ak ponúkate ovládanie priblíženia aj tlačidlo prispôsobenia, zaobchádzajte s nimi ako s prepínačom režimu: nastavenie jedného vymaže druhý, a vy rozhodnete, ktorý zvíťazí.
Pre absolútne ovládacie prvky priblíženia, ktoré sa prirodzene čítajú, pohľad vystavuje prispôsobené zoomy ako hodnoty, ktoré môžete aplikovať alebo zobrazovať: PageWidthZoom[PageNumber] vráti zoom, ktorý by prispôsobil tú stránku na šírku, a zodpovedajúci PageZoom prispôsobí celú stránku. Čítanie týchto hodnôt je spôsob, ako napĺňať ponuku "Prispôsobiť šírku" alebo "Prispôsobiť stránku" bez natvrdo zakódovaných percent, ktoré sú nesprávne pri krajinnom alebo nadrozmernom stránkach.
Udržte rýchle posúvanie responzívnym s progresívnym vykresľovaním
Predvolená cesta vykresľovania vykreslí stránku do dokončenia pred návratom. Pre jednu stránku je to v poriadku. Počas šmyknutia cez hustý dokument nie je: každá stránka, ktorá blikne okolo, spustí plnú rasterizáciu, a ak používateľ scrolluje rýchlejšie, ako sa stránky môžu vykresliť, tieto vykreslenia sa nahromadia a panel trhá, pretože sa práca robí pre stránky, ktoré sú už mimo obrazovky v čase jej dokončenia. Riešením je robiť vykresľovanie zrušiteľné a opustiť ho v okamihu, keď sa používateľ presunul.
RenderPageProgressive vykresľuje v častiach a kontroluje token zrušenia na každej hranici časti, takže prebehajúci render stránky, ktorá práve odscrollovala, môže byť zahodený namiesto spustenia do konca.
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;
Tvar, ktorý je dôležitý, je návratová hodnota. prsDone znamená, že bitmapa je plne vykreslená a stojí za prenesenie na obrazovku; prsCancelled znamená, že novšia poloha posunu nahradila túto stránku, takže čiastočný výsledok zahoďte namiesto zobrazenia; prsFailed je skutočná chyba na danej stránke. Zrušenie sa poll-uje na hraniciach časti, nie preventívne, takže očakávajte desiatky milisekúnd latencie medzi volaním Cancel a skutočným zastavením vykresľovania. To je stále oveľa lacnejšie ako nechanie zastaralého full-page vykresľovania blokovať frontu. Odovzdanie nil ako token vykreslí priamo až do dokončenia, čo je správna voľba pre jednorazové vykresľovanie, ako je náhľad tlače, kde nie je nič na zrušenie.
Keď namiesto toho zavoláte formu RenderPage, tú, ktorá vráti novú TBitmap, pamätajte, že volajúci je vlastníkom a musí ju uvoľniť pomocou Free. V scrollovacej slučke, ktorá alokuje bitmapu na stránku, zabudnutie na toto je únik, ktorý rastie s každou stránkou, ktorú používateľ presáhne, čo je presne chyba neobmedzenej pamäte, ktorej sa mal nepretržitý návrh vyhnúť. Vykresľujte do znovu použitej bitmapy, kde môžete.
Čo vám zostane
Čitateľ s nepretržitým posunom je prevažne doménou komponentu. Vyberiete dmSingleContinuous pre rozloženie, nastavíte pfmFitWidth, aby sa stĺpec pretekal s oknom, a skontrolujete Pdf.Active, aby zlý súbor zlyhal výrazne. Jedna časť, ktorú stojí za to napísať sami, je zrušiteľné vykresľovanie, pretože prehliadač sa posudzuje podľa toho, ako sa správa, keď niekto pretasí scrollbar na koniec dlhého dokumentu a panel buď drží krok, alebo nie. Všetko za tým, výber textu na stránkach, zvýraznenie vyhľadávania, strom záložiek, je práca rozhrania, ktorá sedí na vrchu tohto posúvacieho povrchu, nie vo vnútri neho.
API TPdfView, DisplayMode a RenderPageProgressive ukazované tu sú súčasťou PDFium VCL Komponentu pre Delphi a Lazarus.