HotPDF RenderCacheFolder atmintinį HotPDF Delphi komponento atvaizduotų puslapių podėlį paverčia patvariu diskiniu puslapių podėliu: atvaizduoti puslapiai rašomi PNG failais jūsų pasirinktame aplanke, ir kitą kartą atsidarius tą patį PDF šaltinį RenderLoadedPageToBitmapCached juos perskaito vietoj pakartotinio rastravimo. Paieškos tvarka: atmintis, tada diskas, tada atvaizduoklis
Diskinė pakopa API yra nuo v2.416.0, bet iki v2.770.140 ji niekada iš tikrųjų neatidavė puslapio įprastam LoadFromFile ar LoadFromStream kvietimui. Taisymas privertė atsakyti į klausimą, į kurį privalo atsakyti kiekvienas patvarus podėlis: iš ko žinote, kad šiandien atidarytas failas yra vakar atvaizduotas dokumentas, ir kas nutinka podėliuotiems puslapiams, kai jis toks nėra? Žemiau – atsakymai, prie kurių priėjo HotPDF, įskaitant vietas, kur jis tyčia atsisako podėliuoti
Kaip veikia HotPDF diskinis atvaizdavimo podėlis?
HotPDF diskinis atvaizdavimo podėlis – antroji pakopa už atmintinį rastrinį podėlį, ir ji dalyvauja tik tada, kai RenderCacheFolder yra netuščias kelias. Kvietimas RenderLoadedPageToBitmapCached(PageIndex, DPI) pirmiausia peržiūri atminties įrašus, raktus vedančius pagal puslapio indeksą, DPI ir atvaizdavimo nustatymų variantą. Nepataikius klausiama diskinės pakopos; diskas pataikęs iškoduoją PNG, grąžina jį į atmintį ir atiduoda kvietėjui priklausančią kopiją. Tik kai abi pakopos nepataiko, puslapis keliauja pro turinio srauto interpretatorių, aprašytą įkelto PDF puslapio atvaizdavime į TBitmap, o šviežias bitmapas tada irgi rašomas į diską
Disko išdėstymas sąmoningai nuobodus. Kiekvienas dokumentas gauna poaplankį, pavadintą iš 16 šešioliktainių simbolių dokumento rakto plius 16 šešioliktainių simbolių atvaizdavimo varianto, kiekvienas puslapis saugomas kaip <page>@<dpi>.png, o index.txt šaknyje laiko dokumentus naujausiai naudotų tvarka už schemos žymos. Schemos nesutapimas pirmu naudojimu išvalo aplanką. Rašymai pirmiausia keliauja į laikinąjį failą ir į vietą perkeliami atominiu pakeitimu, tad avarija rašymo viduryje palieka arba seną puslapį, arba nieko – niekada ne pusę PNG. PNG, kurio nepavyksta iškoduoti, ištrinamas ir skaitomas nepataikymu
Trys ribos apriboja aplanką:
RenderCacheMaxDocuments(numatytoji 20) riboja dokumento poaplankių skaičių; seniausiai naudotas aplankas iškeliaujamas pirmasRenderCacheMaxBytes(numatytoji 524288000, tai 500 MB) riboja bendrą visų PNG failų dydį po šaknimi- Kiekvienas dokumento aplankas laiko daugiausiai 200 puslapių atvaizdų; ta riba dokumentui fiksuota THotPDF ir nėra skelbiama savybė
RenderCacheCapacity (numatytoji 8) – atskira rankenėlė: ji nustato, kiek atvaizduotų puslapių laiko atminties pakopa, ir su disku užimta vieta niekaip nesusijusi
uses
SysUtils, Graphics, HPDFDoc;
procedure WarmThumbnails(const FileName: string);
var
Pdf: THotPDF;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
// Sukonfigūruokite diskinę pakopą prieš pirmąjį podėliuotą atvaizdavimą:
// aplankas ir abi ribos skaitomos, kai pakopa pirmą kartą naudojama
Pdf.RenderCacheFolder := IncludeTrailingPathDelimiter(
GetEnvironmentVariable('LOCALAPPDATA')) + 'MyViewer\PageCache';
Pdf.RenderCacheMaxDocuments := 50;
Pdf.RenderCacheMaxBytes := Int64(1024) * 1024 * 1024; // 1 GiB
Pdf.RenderCacheCapacity := 16; // puslapiai atmintyje
if Pdf.LoadFromFile(FileName) > 0 then
for I := 0 to Pdf.LoadedPageCount - 1 do
begin
Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 96);
if Bmp <> nil then
try
// Čia atiduokite kopiją miniatiūrų juostai
finally
Bmp.Free; // podėliuotas kvietimas visada grąžina kvietėjui priklausančią kopiją
end;
end;
finally
Pdf.Free; // nuo v2.770.140 tai daugiau netrina diskinių įrašų
end;
end;
Paleiskite tą pačią procedūrą dukart, ir antras paleidimas nė karto nerastruos puslapio, tilpusio į podėlį. Diskinis podėlio objektas kuriamas tingiai pirmojo podėliuoto atvaizdavimo metu ir gyvuoja, kol atlaisvinama THotPDF instancija, tad RenderCacheFolder, RenderCacheMaxDocuments ar RenderCacheMaxBytes keitimas po to negali perkelti ar pakeisti dydžio jau atidarytam podėliui. Puslapiai, per dideli atminties priėmimo politikai (pagal nutylėjimą vienas įrašas negali viršyti 64 MiB 32 bitų pikselių), taip pat neišsaugomi, o diskinė pakopa klausiama tik kol RenderFallbackPolicy laiko savąją numatytąją rfpIgnore, nes atsarginės diagnostikos šalia PNG nesaugoma
Kodėl RenderCacheFolder iki v2.770.140 niekada neveikė?
RenderCacheFolder iki v2.770.140 jokio poveikio neturėjo, nes diskinė pakopa dokumentus raktdavo ženklu iš šaltinio baitų, kurių įprasti įkėlimai niekada nesaugodavo. Dokumento raktas eidavo iš SHA-256 ant vidinės žalių PDF baitų kopijos, bet LoadFromFile ir LoadFromStream šaltinį analizuoja vietoje ir tokios kopijos nesilaiko; laukas buvo užpildomas tik laikinai šifruoto atkūrimo kelyje ir tuoj pat vėl išvalomas. Be baitų raktas visada būdavo tuščias, o tuščias raktas reiškia, kad diskinė pakopa praleidžiama. Jokios klaidos, jokio įspėjimo – tik aplankas, likęs tuščias
Rakto padarymas netuščiu atvėrė antrą klaidą, slėpusią už pirmosios. Senasis InvalidateRenderedPageCache ištrindavo dokumento diskinį aplanką, o InvalidateRenderedPageCache paleidžiamas kiekvieno įkėlimo pradžioje, kiekvieno redagavimo metu ir Free viduje. Taigi vos raktas suveiktų, kiekviena peržiūros sesija išeidama sunaikintų savą podėlį, o kita sesija vis tiek pradėtų šalta. Blogiau – raktas po redagavimo būtų perskaičiuotas iš to paties šaltinio, tad redaguoto dokumento atvaizdavimai būtų saugomi po originalaus failo raktu ir atiduodami kitai sesijai, atsidariusiai nepakeistą PDF. v2.770.140 tapatumą ir nebegaliojimą taiso kartu; vieno jų taisymas būtų išsiuntęs arba negyvą podėlį, arba melagį
Kaip HotPDF atpažįsta PDF neskaitęs viso failo
HotPDF iš vietinio failo įkeltą PDF atpažįsta pagal jo dydžio, last-write laiko ir pirmųjų bei paskutinių 64 KiB piršto atspaudą, o srautą ar atsitiktinės prieigos šaltinį – pagal viso turinio SHA-256. Abu fiksujami kartą, kai įkėlimas pavyksta, ir pirmieji 16 šešioliktainių SHA-256 santraukos simbolių (64 bitai) tampa dokumento raktu
| Šaltinis | Tapatumas | Kaina | Kada fiksujama |
|---|---|---|---|
LoadFromFile | Dydis + LastWriteTime + pirmieji ir paskutiniai 64 KiB, hashuojama SHA-256 | Perskaitoma daugiausiai 128 KiB, nepriklausomai nuo failo dydžio | Kiekvienas sėkmingas įkėlimas, net jei RenderCacheFolder nustatoma vėliau |
LoadFromStream | Viso srauto SHA-256 | Vienas pilnas ėjimas per šaltinį | Tik jei RenderCacheFolder buvo nustatyta prieš įkėlimą |
LoadFromRandomAccessSource | Viso šaltinio SHA-256 | Vienas pilnas ėjimas per šaltinį | Tik jei aplankas nustatytas pirmiausia ir visas intervalas prieinamas |
Bet koks šaltinis su /Encrypt įrašu | Nėra | Nėra | Niekada; diskinė pakopa praleidžiama |
Failo atspaudas – sąmoningas kompromisas. 400 MB skenuoto archyvo pilnas hashavimas kiekvieno atsidarymo metu gali kainuoti daugiau nei dviejų puslapių, kuriuos naudotojas iš tiesų žiūri, atvaizdavimas. Imami gabalai ne atsitiktiniai: antraštė slypi failo pradžioje, o traileris ir paskutinė kryžminių nuorodų sekcija – gale (ISO 32000-1 §7.5). Inkrementinis atnaujinimas prideda naują pagrindą, kryžminių nuorodų sekciją ir trailerį (§7.5.6), tad vienu metu keičia ir dydį, ir uodegą. Pilnas perrašymas bet kokiu normaliu įrankiu keičia last-write laiką. Failams iki 128 KiB abu gabalai dengia kiekvieną baitą, tad maži dokumentai efektyviai hashuojami pilni
Likutinė rizika – to paties dydžio, vietoje atliktas didelio failo vidurio pakeitimas, po kurio rašytojas atstatė originalią laiko žymą. Tam reikia įrankio, kuris redaguodamas turinį sąmoningai išlaiko keitimo laikus – retai, bet ne neįmanoma, ir tada podėlis atiduoda senus puslapius. Kita pusė rami: failo kopijavimas Windows paprastai išlaiko last-write laiką, tad dokumento, jau esančio podėlyje, kopija pataiko į tuos pačius įrašus, kas teisinga, nes baitai identiški
Srautai iš viso neturi keitimo laiko, tad vienintelis sąžiningas tapatumas – turinys. HotPDF už tą pilną SHA-256 ėjimą moka tik tada, kai diskinio podėlio paprašėte iki įkėlimo; kitiems visiems LoadFromStream kvietėjams papildomos kainos nėra. Todėl savybių priskyrimo tvarka tampa lemiamu veiksniu:
procedure OpenDownloadedPdf(Pdf: THotPDF; Data: TStream;
const CacheRoot: string);
begin
// Netinkama tvarka srautams: turinio hash skaičiuojamas tik tada, kai
// aplankas jau nustatytas, tad šis dokumentas praleistų diskinę pakopą
// Pdf.LoadFromStream(Data);
// Pdf.RenderCacheFolder := CacheRoot;
Pdf.RenderCacheFolder := CacheRoot; // nustatykite pirmiausia
Data.Position := 0;
if Pdf.LoadFromStream(Data) <= 0 then
raise Exception.Create('The stream is not a loadable PDF');
end;
Atsitiktinės prieigos šaltinis, kuris vis tebesisiunčia (kai kurių intervalų dar nėra), tapatumo negauna – vietoj dalinio turinio hasho – o jei tapatumo apskaičiavimas dėl kokių priežasčių nepavyksta, įkėlimas vis tiek pavyksta; dokumentas tiesiog atvaizduojamas be diskinės pakopos
Kas panaikina HotPDF diskinio podėlio įrašo galiojimą?
HotPDF diskinio podėlio įrašas niekada nepanaikinamas ištrinant jį redagavimo metu; vietoj to įkelto dokumento redagavimas numeta dokumento tapatumą, tad diskinė pakopa likusiai to įkėlimo daliai praleidžiama, o saugomi puslapiai lieka teisėti nepakeistam šaltiniui. Iš disko įrašai keliauja tik pro LRU ir baitų ribas, sugadintą PNG arba schemos pakeitimą
Raktas aprašo šaltinį diske, o ne objektų grafą atmintyje. Kai tik užspaudžiate puslapį arba keičiate anotaciją, dokumentas daugiau neatitinka to šaltinio, tad nei skaitymas, nei rašymas po jo raktu nebūtų teisingas. Nuo v2.770.140 ir dokumento lygio, ir puslapio lygio nebegaliojimas išvalo tapatumą vietoj aplanko liečimo, o yra ir antra apsauga redagavimams, nekvietusiems InvalidateRenderedPageCache: prieš naudodama diskinę pakopą THotPDF patikrina, ar kuris įkeltas objektas pažymėtas dirty, ir tokį dokumentą traktuoja kaip neturintį tapatumo
Atvaizdavimo nustatymai veikia atvirkščiai. PageRenderBackend perjungimas (arba UseNativeGDIRenderBackend kvietimas), taip pat ConfigureRenderICCWorkflow arba ClearRenderICCWorkflow kvietimai atminties puslapius nuplauna, bet tapatumą palieka, nes dokumentas vis dar atitinka savąjį šaltinį. Tie nustatymai keičia pikselius, nedalyvaudami atminties variante, tad diskinis raktas įsipina backend pavadinimą, juodojo taško kompensacijos vėliavą ir ICC proof bei išvesties profilių SHA-256 santraukas. Pats variantas jau dengia spalvų ketinimą, išvesties ditheringą, overprint peržiūrą, šviesos kaukės režimą, atsarginę politiką ir kiekvienos pasirinktinio turinio grupės matomumą, tad sluoksnio įjungimas atvaizduoja į kitą aplanką, o neperrašo numatytojo vaizdo
Kad redaguotas dokumentas grįžtų ant diskinės pakopos, duokite jam naują šaltinio tapatumą – išsaugokite ir įkelkite rezultatą:
procedure CommitEditsAndRekey(Pdf: THotPDF; const EditedFile: string);
begin
// Po įkelto dokumento redagavimo: atnaujinkite atminties puslapius.
// Šaltinio tapatumas jau dingo, tad iš originalaus dokumento diskinio
// aplanko niekas neskaityta ir neįrašyta
Pdf.InvalidateRenderedPageCache;
// Išsaugotas failas turi naują dydį ir last-write laiką, vadinasi naują
// tapatumą; atvaizdavimai po šio įkėlimo podėliuojami nauju raktu
Pdf.SaveLoadedDocument(EditedFile);
if Pdf.LoadFromFile(EditedFile) <= 0 then
raise Exception.Create('Could not reload the edited document');
end;
Originalaus dokumento aplankas lieka ramybėje ir sensta pro RenderCacheMaxDocuments bei RenderCacheMaxBytes kaip ir bet kuris kitas įrašas. Jeigu naudotojas vėl atidaro neredaguotą originalą, jo puslapiai tebėra ten
Saugumo ribos: šifruoti šaltiniai ir susieti aplankai
HotPDF diskinis atvaizdavimo podėlis tyčia atsisako dviejų rūšių įvedimo: niekada nerašo šifruoto PDF puslapių į diską ir niekada neseka dokumento poaplankio, kuris yra jungtis (junction) ar kitas reparse taškas. Abi taisyklės podėlio pataikymus maino į duomenų nesunutekėjimą ir netinkamų failų netrinimą
Šifruoti PDF niekada nepodėliuojami diske
Atvaizduotas puslapis – iššifruotas turinys. Jo užrašymas kaip paprasto PNG į podėlio aplanką paliktų skaitomą slaptažodžiu apsaugoto dokumento kopiją diske, už autoriaus pasirinktos apsaugos ribų (ISO 32000-1 §7.6). Todėl HotPDF jokiam šaltiniui, kurio traileris neša /Encrypt įrašą – įskaitant failus, atidarytus su slaptažodžiu arba su tuščiu naudotojo slaptažodžiu – tapatumo nefiksuoja. Tie dokumentai vis dar naudoja atminties pakopą, kuri žūsta su procesu
Jungčių poaplankiai atmetami nuo v2.770.173
Podėlio šaknis – jūsų pasirinkimas, ir nukreipti ją į jungtį leidžiama. Dokumentų poaplankiai po ja – kitas reikalas: podėlis juos pats kuria, skaito, liečia ir trina – paleidimo atkūrimo metu (kuriuo pašalinami likę laikinieji failai), paieškos metu (kuri atnaujina laiko žymas), saugojimo, nebegaliojimo ir trijų iškėlimo ribų metu. Jeigu kas nors, turintis rašymo prieigą prie podėlio šaknies, dokumento aplanką pakeičia jungtimi į kitą katalogą, visi tie keliai ją sektų, o iškėlimas trintų failus ten, kur podėlis niekada nesavo. Nuo v2.770.173 kiekvienas tų įėjimo taškų tikrina reparse taško atributą ir praleidžia susietą dokumento aplanką: paieška skaičiuoja nepataikymą, saugojimas – rašymo nesėkmę, o iškėlimas jo neliečia
Unicode keliai ir bendros šaknys
Dvi susijusios pataisos svarbios, jei diegiate į naudotojų profilius. Iki v2.770.135 RenderCacheFolder buvo AnsiString, tad aplankas už sistemos koduotės ribų (pvz., kinų naudotojo vardas angliškoje Windows instaliacijoje) būdavo konvertuojamas su nuostoliais, kol podėlis jį pamatydavo; savybė dabar – Unicode string, o atominis pakeitimas naudoja plačiąją Windows API. Nuo v2.770.52 kelios THotPDF instancijos viename procese, rodančios į tą pačią šaknį (po kelio išplėtimo, lyginant neatsižvelgiant į raidžių registrą), dalijasi viena skaičiuojamų nuorodų rodykle ir rakta. Anksčiau kiekviena instancija index.txt perrašydavo savą kopija ir ribas taikydavo savai dalinei peržiūrai, tad aplankas galėdavo išaugti keletą kartų virš savo biudžeto
Tas dalijimasis sustoja ties proceso riba. Du atskiri procesai toje pačioje šaknyje vis dar laiko atskirus atminties indeksus, tad kiekvienai lygiagrečiai besileidžiančiai programai duokite savą podėlio šaknį. Peržiūros, atvaizduojančios darbo gijose, viename procese jaučiasi gerai: PrefetchLoadedPages ir eilė, aprašyta foniniame atvaizdavime su užklausų eile, abu eina ta pačia podėliuota kelia ir ta pačia rakta
Trumpa atmintinė: RenderCacheFolder patikros sąrašas
- Nustatykite
RenderCacheFolder,RenderCacheMaxDocumentsirRenderCacheMaxBytesprieš pirmąjįRenderLoadedPageToBitmapCachedkvietimą; srauto ir atsitiktinės prieigos įkėlimams aplanką nustatykite prieš įkeliant - Atnaujinkite iki v2.770.140 ar vėlesnės, jeigu remiatės diskine pakopa; ankstesnės versijos savybę priima, bet įprastiems įkėlimams puslapio iš disko niekada neatiduoda
- Nesitikėkite diskinio podėliavimo šifruotiems PDF, dokumentams, redaguotiems po įkėlimo, arba kol
RenderFallbackPolicynėrarfpIgnore - THotPDF instanciją atlaisvinkite įprastai; nuo v2.770.140 nei
Free, neiInvalidateRenderedPageCachediskinių įrašų netrina PageRenderBackendarba ICC darbo eigos keitimas dokumentą diskinėje pakopoje palieka po kitokiu raktu- Naudokite po vieną podėlio šaknį kiekvienai besileidžiančiai programai; instancijos viename procese nuo v2.770.52 dalijasi rodykle
- Podėlio šaknį laikykite kiekvienam naudotojui skirtoje vietoje; dokumentų poaplankiai, kurie yra jungtys, nuo v2.770.173 praleidžiami
Patvarus puslapių podėlis atsipirčiausiai ten, kur peržiūra visą dieną vėl atidarinėja tuos pačius dokumentus – būtent tokios formos yra pasirinktinė PDF peržiūros architektūra Delphi, aprašyta kitur šiame tinklaraštyje. RenderCacheFolder, atmintinis rastrinis podėlis ir puslapių atvaizduoklis keliauja su HotPDF Delphi PDF komponentu, skirtu Delphi ir C++Builder