Vsak vgrajeni pregledovalnik PDF spremljata dve pogosti pritožbi: veliki dokumenti se ob vsakem zagonu znova upodabljajo iz nič, besedilo pa postane motno tisti trenutek, ko okno povlečete na monitor z visoko ločljivostjo (high-DPI). Knjižnica losLab PDF Library odgovarja na oboje v svojem kontrolniku TPDFlibViewer: lastnost DiskCacheFolder omogoča medsejni predpomnilnik strani na disku, ključan s prstnim odtisom dokumenta, lastnost ScreenDPI, podprta z GetDpiForWindow in rokovnikom WM_DPICHANGED, pa prinaša kontrolniku pravo zavedanje DPI-ja za posamezne monitorje
Obe težavi je vredno vzeti resno, saj ju uporabniki takoj opazijo. 400-stranska skenirana pogodba, ki je včeraj potrebovala osem sekund, da je postala pomična, danes spet potrebuje osem sekund, čeprav se v datoteki ni nič spremenilo. In na namizju z mešanimi vrednostmi DPI je pregledovalnik, ki upodablja pri 96 DPI primarnega monitorja, tam videti dobro, na sosednjem 4K zaslonu pa opazno zamegljeno. Ta članek opisuje, kako TPDFlibViewer rešuje vsako od teh težav in, kar je še bolj koristno, zakaj se je zasnova končala tam, kjer se je. Če ste novi pri samem kontrolniku, začnite s pregledom interaktivnega kontrolnika pregledovalnika PDF in se nato vrnite na to zmogljivostno plast
Kako v Delphiju predpomniti upodobljene strani PDF med sejami?
Dodelite mapo lastnosti TPDFlibViewer.DiskCacheFolder in kontrolnik bo shranil upodobljene bitne slike strani na disk, tako da se pred kratkim ogledani dokument odpre takoj, namesto da bi se znova upodabljal. Na disku je struktura naslednja: <Folder>\PDFlibPas-PageCache\<document fingerprint>\p<page>_z<zoomkey>.bin — ena podmapa na dokument in ena datoteka na kombinacijo strani in povečave. Vse ostalo je samodejno; ni API-ja za predpomnjenje, ki bi ga morali klicati, in ničesar ni treba čistiti
uses
PDFlibViewer;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FViewer := TPDFlibViewer.Create(Self);
FViewer.Parent := Self;
FViewer.Align := alClient;
// Ena vrstica vklopi medsejni predpomnilnik strani
FViewer.DiskCacheFolder := 'C:\ProgramData\MyApp\PageCache';
FViewer.LoadFromFile('C:\Contracts\master-agreement.pdf', '');
end;
Ševec DiskCacheHits poroča o tem, koliko upodobitev strani je predpomnilnik na disku prihranil v trenutni seji, zaradi česar je pridobitev merljiva in ne le anekdotična. Pri dokumentu, ki ste ga zaprli in znova odprli, se števec poveča z vsako stranjo, ki se pomakne v pogled brez prehoda za upodabljanje
Zakaj je ključ predpomnilnika prstni odtis in ne številka različice
Prstni odtis dokumenta je 64-bitna zgostitev (hash) FNV-1a nad nizom pot|velikost|čas zadnjega pisanja, oblikovana kot šestnajst šestnajstiških števk. Ta trojica predstavlja celotno strategijo razveljavitve: ko se datoteka uredi, se spremeni njena velikost ali čas pisanja, spremeni se zgostitev, pregledovalnik pa preprosto odpre drugo podmapo predpomnilnika. Zastarela mapa se nikoli več ne uporabi in sčasoma podleže LRU čiščenju. Ni protokola o oznakah različic, ki bi ga bilo treba vzdrževati, ni logike za primerjavo časovnih žigov, ki bi lahko odstopala, in ni možnosti, da bi se stara bitna slika uporabila za spremenjen dokument
To je ista ideja "identitete vsebine kot ključa predpomnilnika", ki jo uporabljajo gradbeni sistemi, in se izkaže z odpravljanjem načinov odpovedi. Predpomnilnik, ki shranjuje datoteke pod navadnim imenom dokumenta, mora urejanja zaznavati eksplicitno, vsak ekspliciten preizkus pa je hrošč, ki čaka na mejni primer: datoteka, obnovljena iz varnostne kopije s starim časovnim žigom, shranjevanje, ki ohrani velikost, ali pot, ki se na enem računalniku primerja z razlikovanjem velikih in malih črk, na drugem pa ne. Zgostitev vseh treh signalov v ime mape naredi zastarelost strukturno nemogočo in ne le proceduralno preverjeno
Zakaj je rotacija namerno izpuščena iz ključa na disku
Ime datoteke predpomnilnika kodira le številko strani in povečavo — rotacijski kot pogleda pa je namerno odsoten. Predpomnilnik na disku vedno shranjuje nerotirane bajte upodobitve; ko se predpomnjena stran naloži, medtem ko je pogled rotiran, se piksli po dekodiranju ponovno usmerijo v pomnilniku. Če bi bila rotacija del ključa na disku, bi uporabnik, ki kroži skozi štiri kote pogleda, isto stran zapisal na disk štirikrat, kar bi štirikratno povečalo rast predpomnilnika za nič dodatnih informacij, saj je rotacija le poceni prerazporeditev pikslov v primerjavi s celotnim prehodom upodabljanja
Predpomnilnik bitnih slik v pomnilniku sicer uporablja sestavljeni ključ, Round(Zoom * 1000) * 4 + Rotation div 90, saj je tam rotirana bitna slika natanko to, kar rokovnik za risanje potrebuje. Tisto v ozadju, ki izvaja vnaprejšnje nalaganje (prefetch thread), sledi isti disciplini: vedno upodablja nerotirane strani in sporoča preproste ključe povečave, glavna nit pa uporabi rotacijo ob prevzemu rezultatov — to je isto pravilo "glavna nit je lastnik predpomnilnika", o katerem govori članek o vzporednem upodabljanju strani in nitni varnosti. Ohranjanje rotacije izven vsega, kar je pod pomnilniško plastjo, pomeni eno kanonično upodobitev na stran in povečavo, povsod, kjer je shranjevanje drago
Kako LRU čiščenje ohranja omejeno velikost mape predpomnilnika?
Predpomnilnik obdrži do deset map dokumentov in ob prihodu novih dokumentov očisti tiste, ki so bili najmanj pred kratkim uporabljeni (least recently used - LRU). Aktualnost se spremlja prek datoteke last-used.marker v vsaki mapi dokumenta, katere časovni žig se osveži tako pri pisanju kot branju iz predpomnilnika, tako da dokument, ki ga le ponovno berete, ostane topel, ne da bi se znova zapisoval. Mapa, ki pripada trenutno odprtemu dokumentu, je izvzeta iz brisanja, zato čiščenje nikoli ne more odstraniti strani izpod aktivnega pogleda
Vsaka diskovna operacija v predpomnilniku — ustvarjanje prstnih odtisov, ustvarjanje map, posodabljanje označevalcev, branje in pisanje strani, čiščenje — utiša lastne izjeme. To je namerna drža in ne malomarnost: predpomnilnik je pospeševalnik po principu najboljšega truda (best-effort) in poln disk, mapa le za branje ali protivirusna zaklenitev morajo delovanje pregledovalnika le degradirati na raven "upodablja kot prej", nikoli pa povzročiti, da "strani ne bi prikazal". Najslabši rezultat, ki ga lahko povzroči napaka I/O predpomnilnika, je le zgrešen zadetek (missed hit)
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// Koliko prehodov upodabljanja je predpomnilnik na disku prihranil v tej seji?
Log(Format('Disk cache hits: %d', [FViewer.DiskCacheHits]));
end;
Kako narediti pregledovalnik PDF zaveden DPI-ja v Delphiju?
TPDFlibViewer usmerja vsak izračun, odvisen od DPI, skozi eno samo polje, ta zasnova z eno točko spremembe pa je tista, ki omogoča enostavno podporo za posamezne monitorje. Koda za postavitev, vnaprejšnje nalaganje in upodabljanje bere isto vrednost FScreenDPI; pri 100-odstotni povečavi se ena točka PDF preslika v ScreenDPI / 72 slikovnih pik. Nastavitvena metoda javne lastnosti ScreenDPI počisti predpomnilnik bitnih slik, znova zgradi postavitev, posodobi obsege pomikanja in razveljavi okno, zato je sprememba DPI-ja le ena dodelitev, ki ji vsak porabnik samodejno sledi. Ista slikovna matematika je pomembna, ko upodabljate za tiskalnik namesto za monitor, kar je tema članka o predogledu tiskanja in upodabljanju konteksta naprave
Zaznavanje se zgodi v prepisu metode CreateWnd in ne v konstruktorju, saj funkcija GetDpiForWindow potrebuje veljaven ročaj okna, konstruktor pa se zažene dolgo preden ta obstaja. Dokler se CreateWnd ne sproži, kontrolnik drži staro privzeto vrednost 96, da matematika postavitve ostane smiselna. Sama funkcija GetDpiForWindow je API za Windows 10 različice 1607+, zato jo kontrolnik dinamično poveže s GetProcAddress in na starejših sistemih uporabi rezervno možnost GetDeviceCaps(LOGPIXELSY) — enota se še vedno naloži povsod, starejši Windows pa preprosto sporoči sistemski DPI
Kaj se zgodi, ko se okno premakne na 4K monitor?
Windows pošlje sporočilo WM_DPICHANGED, ko okno, ki se zaveda posameznih monitorjev, preide na monitor z drugačnim faktorjem merila, TPDFlibViewer pa ga obravnava neposredno. Rokovnik prebere nov DPI iz spodnje besede wParam, ga dodeli prek SetScreenDPI — kar počisti zdaj napačen predpomnilnik bitnih slik in znova razporedi strani — ter nato sprejme predlagani pravokotnik okna, ki ga Windows posreduje v lParam prek SetWindowPos, kar ohranja logično velikost odjemalca stabilno med premikom. Uporabnik vidi, da se dokument znova upodobi ostro pri novem merilu, namesto da bi videl bitno sliko, ki jo raztegne operacijski sistem
// Običajno se nikoli ne dotikate ScreenDPI: CreateWnd zazna gostiteljski monitor
// WM_DPICHANGED pa spremlja premike. Prepišite jo le za posebne cilje,
// npr. upodabljanje postavitve, kot bi šlo za 150% zaslon:
// počisti predpomnilnik bitnih slik in znova zgradi postavitev
FViewer.ScreenDPI := 144;
Omejitve, ki jih je dobro poznati pred izdajo izdelka
Tri praktične omejitve si zaslužijo mesto v vašem oblikovalskem pregledu. Prvič, sporočilo WM_DPICHANGED prispe le, če se proces vanj prijavi: vaš manifest aplikacije mora deklarirati zavedanje DPI za posamezne monitorje (per-monitor v2 na trenutnem sistemu Windows), kar je v Delphi IDE nastavitev zavedanja DPI pod možnostmi Application. Proces, ki se zaveda le sistemskega DPI-ja, nikoli ne prejme sporočila, kontrolnik pa nato upodablja pri vrednosti DPI, zaznani ob ustvarjanju okna. Drugič, predpomnilnik na disku menja prostor na disku za hitrost — bitne slike strani za deset dokumentov pri več stopnjah povečave predstavljajo dejansko shrambo, zato lastnost DiskCacheFolder usmerite na lokacijo, za katero ste pripravljeni, da raste, in ne v gostujoči profil (roaming profile). Tretjič, mapa predpomnilnika potrebuje dovoljenje za pisanje za tekočega uporabnika; brez tega pregledovalnik še vedno deluje, tiho, z nič zadetki — med testiranjem preverite DiskCacheHits, če sumite, da se predpomnilnik ne vklopi
Skupaj ti dve funkciji spremenita zaznano kakovost aplikacije z velikim številom dokumentov bolj kot večina optimizacij upodabljanja: dokumenti, ki jih vaši uporabniki ponovno obiščejo, se odprejo takoj, pogled pa ostane oster na katerem koli monitorju, kjer se nahaja. Obe sta na voljo kot običajni lastnosti kontrolnika TPDFlibViewer v knjižnici losLab PDF Library, zato je njun sprejem stvar enega popoldneva povezovanja in ne projekt upodabljalnega pogona