HotPDF RenderCacheFolder gjør den minnebaserte rendret-side-cachen til HotPDF Delphi-komponenten om til en vedvarende diskbasert sidebuffer: rendrete sider skrives som PNG-filer under en mappe du velger, og neste gang samme PDF-kilde åpnes, leser RenderLoadedPageToBitmapCached dem tilbake i stedet for å rasterisere igjen. Oppslagsrekkefølgen er minne, så disk, så rendereren
Disk-laget har vært i API-en siden v2.416.0, men frem til v2.770.140 serverte det aldri faktisk en side for et vanlig LoadFromFile- eller LoadFromStream-kall. Fiksen tvang frem et spørsmål enhver vedvarende buffer må svare på: hvordan vet du at filen du åpnet i dag, er dokumentet du rendret i går, og hva skjer med de cachete sidene når det ikke er det? Nedenfor er svarene HotPDF landet på, inkludert der den med vilje nekter å cache
Hvordan virker HotPDF disk render cache?
HotPDF disk render cache er et andre lag bak minnebasert raster-buffer, og det deltar bare når RenderCacheFolder er en ikke-tom sti. Et kall til RenderLoadedPageToBitmapCached(PageIndex, DPI) skanner først minneoppføringene, nøklet etter sideindeks, DPI og en renderinnstillings-variant. Ved et bom spør den disk-laget; et disk-treff dekoder PNG-en, promoterer den tilbake i minnet og returnerer en kalleid kopi. Først når begge lag bommer går siden gjennom content stream-tolkeren beskrevet i å rendere en lastet PDF-side til en TBitmap, og den ferske bitmapen skrives da til disk også
På disk er layouten med vilje kjedelig. Hvert dokument får en undermappe navngitt fra en dokumentnøkkel på 16 heks-tegn pluss en render-variant på 16 heks-tegn, hver side lagres som <page>@<dpi>.png, og en index.txt ved roten holder dokumentene i mest-sist-brukt-rekkefølge bak et schema-merke. Et schema-avvik tømmer mappen ved første bruk. Skrivinger går først til en midlertidig fil og byttes på plass med en atomær erstatning, så et krasj midt i en skriving etterlater enten den gamle siden eller ingenting, aldri en halv PNG. En PNG som feiler å dekode, slettes og telles som et bom
Tre grenser avgrenser mappen:
RenderCacheMaxDocuments(standard 20) setter tak for antallet dokumentundermapper; den minst nylig brukte mappen kastes førstRenderCacheMaxBytes(standard 524288000, som er 500 MB) setter tak for total størrelse av alle PNG-filer under roten- Hver dokumentmappe beholder høyst 200 sidebilder; den per-dokument-grensen er fastsatt av THotPDF og er ikke en publisert egenskap
RenderCacheCapacity (standard 8) er en separat knott: den setter hvor mange rendrete sider minnelaget beholder, og den har ingenting med diskavtrykket å gjøre
uses
SysUtils, Graphics, HPDFDoc;
procedure WarmThumbnails(const FileName: string);
var
Pdf: THotPDF;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
// Konfigurer disk-laget før den første cachete renderen:
// mappen og begge grensene leses når laget brukes første gang
Pdf.RenderCacheFolder := IncludeTrailingPathDelimiter(
GetEnvironmentVariable('LOCALAPPDATA')) + 'MyViewer\PageCache';
Pdf.RenderCacheMaxDocuments := 50;
Pdf.RenderCacheMaxBytes := Int64(1024) * 1024 * 1024; // 1 GiB
Pdf.RenderCacheCapacity := 16; // sider i minnet
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
// Gi kopien til thumbnail-stripen her
finally
Bmp.Free; // det cachete kallet returnerer alltid en kalleid kopi
end;
end;
finally
Pdf.Free; // siden v2.770.140 sletter ikke lenger dette disk-oppføringene
end;
end;
Kjør samme prosedyre to ganger, og andre kjøring rasteriserer aldri en side som fikk plass i bufferen. Disk-bufferobjektet opprettes dovent ved den første cachete renderingen og lever til THotPDF-instansen frigjøres, så å endre RenderCacheFolder, RenderCacheMaxDocuments eller RenderCacheMaxBytes etter det punktet flytter eller endrer ikke størrelsen på en allerede åpen buffer. Sider for store for minne-admisjonspolicyen (som standard kan ikke en enkelt oppføring overstige 64 MiB av 32-bits piksler) vedvares heller ikke, og disk-laget konsulteres bare mens RenderFallbackPolicy beholder sin standard rfpIgnore, for fallback-diagnostikk lagres ikke sammen med PNG-en
Hvorfor virket aldri RenderCacheFolder før v2.770.140?
RenderCacheFolder hadde ingen effekt før v2.770.140 fordi disk-laget nøklet dokumenter på en hash av kildebyter som vanlige lastinger aldri beholdt. Dokumentnøkkelen kom fra en SHA-256 over en intern kopi av de rå PDF-bytene, men LoadFromFile og LoadFromStream parser kilden in place og beholder ikke en slik kopi; feltet ble bare fylt midlertidig på en kryptert-gjenopprettingssti og tømt igjen rett etter. Uten bytes var nøkkelen alltid tom, og en tom nøkkel betyr at disk-laget omgås. Ingen feil, ingen advarsel, bare en mappe som forble tom
Å gjøre nøkkelen ikke-tom avdekket en andre bug som hadde gjemt seg bak den første. Den gamle InvalidateRenderedPageCache slettet dokumentets disk-mappe, og InvalidateRenderedPageCache kjører ved starten av hver lasting, ved hver redigering og inne i Free. Så i det øyeblikket nøkkelen virket, ville hver viewer-økt ha ødelagt sin egen buffer ved avslutning, og neste økt ville ha startet kald uansett. Verre, nøkkelen ble beregnet på nytt fra samme kilde etter en redigering, så renderinger av det redigerte dokumentet ville ha blitt lagret under nøkkelen til originalfilen og servert til neste økt som åpnet den umodifiserte PDF-en. v2.770.140 fikser identiteten og ugjøringen sammen; å fikse bare én av dem ville ha skipet enten en død buffer eller en som løy
Hvordan HotPDF identifiserer en PDF uten å lese hele filen
HotPDF identifiserer en PDF lastet fra en lokal fil med et fingeravtrykk av størrelsen, dens last-write-tid og dens første og siste 64 KiB, og identifiserer en strøm- eller random access-kilde med en SHA-256 av hele innholdet. Begge fanges én gang, når en lasting lykkes, og de første 16 heks-tegnene i SHA-256-digesten (64 bit) blir dokumentnøkkelen
| Kilde | Identitet | Kostnad | Fanget når |
|---|---|---|---|
LoadFromFile | Størrelse + LastWriteTime + ledende og hale 64 KiB, hasjet med SHA-256 | Høyst 128 KiB lest, uavhengig av filstørrelse | Hver vellykket lasting, selv om RenderCacheFolder settes senere |
LoadFromStream | SHA-256 av hele strømmen | Ett fullt pass over kilden | Bare hvis RenderCacheFolder var satt før lasting |
LoadFromRandomAccessSource | SHA-256 av hele kilden | Ett fullt pass over kilden | Bare hvis mappen ble satt først og hele området er tilgjengelig |
Enhver kilde med en /Encrypt-oppføring | Ingen | Ingen | Aldri; disk-laget omgås |
Filfingeravtrykket er en bevisst avveining. Å hasje et 400 MB skannet arkiv i sin helhet ved hver åpning kan koste mer enn å rendere de to sidene en bruker faktisk ser på. De samplede regionene er ikke vilkårlige: headeren ligger ved filens start, og traileren og siste kryssreferanseseksjon ligger ved slutten (ISO 32000-1 §7.5). En inkrementell oppdatering legger til en ny kropp, kryssreferanseseksjon og trailer (§7.5.6), så den endrer størrelsen og halen på én gang. En full omskriving av ethvert normalt verktøy endrer last-write-tiden. For filer opptil 128 KiB dekker de to samplene hver byte, så små dokumenter hasjes i praksis i sin helhet
Restrisikoen er en like stor, in place-endring midt i en stor fil hvis skriver så gjenoppretter originaltidsstempelet. Det krever et verktøy som bevisst bevarer modifikasjonstider mens det redigerer innhold, noe som er sjeldent men ikke umulig, og i så fall serverer bufferen foreldede sider. Den andre siden er godartet: å kopiere en fil på Windows bevarer normalt last-write-tiden, så en kopi av et dokument allerede i bufferen treffer de samme oppføringene, noe som er korrekt fordi bytene er identiske
Strømmer har ingen modifikasjonstid i det hele tatt, så den eneste ærlige identiteten er innholdet. HotPDF betaler bare for det fulle SHA-256-passet når du har bedt om en disk-buffer før lasting; enhver annen kaller av LoadFromStream ser ingen ekstra kostnad. Det gjør egenskapstilordningsrekkefølgen bærende:
procedure OpenDownloadedPdf(Pdf: THotPDF; Data: TStream;
const CacheRoot: string);
begin
// Feil rekkefølge for strømmer: innholdshasjen beregnes bare når
// mappen allerede er satt, så dette dokumentet ville omgått disk-laget
// Pdf.LoadFromStream(Data);
// Pdf.RenderCacheFolder := CacheRoot;
Pdf.RenderCacheFolder := CacheRoot; // satt først
Data.Position := 0;
if Pdf.LoadFromStream(Data) <= 0 then
raise Exception.Create('The stream is not a loadable PDF');
end;
En random access-kilde som fortsatt laster ned (noen områder ikke tilgjengelige ennå) får ingen identitet i stedet for en hash av delvis innhold, og hvis beregning av identiteten feiler av hvilken som helst grunn, lykkes lasting likevel; dokumentet rendrer bare uten disk-laget
Hva ugjør en HotPDF disk cache-oppføring?
En HotPDF disk cache-oppføring ugjøres aldri ved å slettes ved redigering; i stedet slipper redigering av det lastede dokumentet dokumentidentiteten, så disk-laget omgås for resten av den lastingen, og de lagrede sidene forblir gyldige for den umodifiserte kilden. Oppføringer forlater disken bare gjennom LRU- og byte-grensene, en korrupt PNG, eller et schema-endring
Nøkkelen beskriver en kilde på disk, ikke objektgrafen i minnet. Så snart du stampler en side eller endrer en annotering, matcher dokumentet ikke lenger den kilden, så verken lesing eller skriving under dens nøkkel ville vært korrekt. Siden v2.770.140 tømmer både dokumentnivå- og sidenivå-ugjøring identiteten i stedet for å røre mappen, og det finnes en andre vakt for redigeringer som ikke kalte InvalidateRenderedPageCache: før bruk av disk-laget sjekker THotPDF om noe lastet objekt er skittent og behandler et skittent dokument som uten identitet
Renderinnstillinger virker motsatt vei. Å bytte PageRenderBackend (eller kalle UseNativeGDIRenderBackend), og å kalle ConfigureRenderICCWorkflow eller ClearRenderICCWorkflow, tømmer minnesidene men beholder identiteten, for dokumentet matcher fortsatt kilden sin. De innstillingene endrer pikslene uten å være del av minnevarianten, så disknøkkelen foldes inn med backend-navnet, black-point compensation-flagget og SHA-256-digester av ICC-proof- og output-profilene. Varianten selv dekker allerede fargeintensjonen, output-dithering, overprint-preview, luminosity mask-modus, fallback-policy og synligheten til hver valgfri innholdsgruppe, så å slå et lag av og på rendrer inn i en annen mappe i stedet for å overskrive standardvisningen
For å få et redigert dokument tilbake på disk-laget, gi det en ny kildeidentitet ved å lagre det og laste resultatet:
procedure CommitEditsAndRekey(Pdf: THotPDF; const EditedFile: string);
begin
// Etter redigering av det lastede dokumentet: oppfrisk minnesidene.
// Kildeidentiteten er allerede borte, så ingenting leses fra eller
// skrives til originaldokumentets disk-mappe
Pdf.InvalidateRenderedPageCache;
// En lagret fil har ny størrelse og last-write-tid, og dermed en ny
// identitet; renderinger etter denne lasting caches under den nye nøkkelen
Pdf.SaveLoadedDocument(EditedFile);
if Pdf.LoadFromFile(EditedFile) <= 0 then
raise Exception.Create('Could not reload the edited document');
end;
Originaldokumentets mappe blir latt i fred og eldes ut gjennom RenderCacheMaxDocuments og RenderCacheMaxBytes som enhver annen oppføring. Åpner brukeren den uredigerte originalen på nytt, er sidene der fortsatt
Sikkerhetsgrenser: krypterte kilder og koblede mapper
HotPDF disk render cache nekter to typer input med vilje: den skriver aldri sider av en kryptert PDF til disk, og den følger aldri en dokumentundermappe som er en junction eller et annet reparse-punkt. Begge regler bytter cache-treff mot ikke å lekke data eller slette feil filer
Krypterte PDF-er caches aldri på disk
En rendret side er dekryptert innhold. Å skrive den som en vanlig PNG inn i en cache-mappe ville etterlate en lesbar kopi av et passordbeskyttet dokument på disk, utenfor beskyttelsen forfatteren valgte (ISO 32000-1 §7.6). HotPDF fanger derfor ingen identitet for noen kilde hvis trailer bærer en /Encrypt-oppføring, inkludert filer åpnet med et passord eller med et tomt brukerpassord. De dokumentene bruker fortsatt minnelaget, som dør med prosessen
Junction-undermapper avvises siden v2.770.173
Bufferroten er ditt valg, og å peke den mot en junction er tillatt. Dokumentundermappene under den er en annen sak: bufferen oppretter, leser, berører og sletter dem på egen hånd, under oppstarts-gjenoppretting (som fjerner igjenblitt midlertidige filer), oppslag (som oppdaterer tidsstempler), lagring, ugjøring og de tre eviction-grensene. Ersetter noen med skrivetilgang til bufferroten en dokumentmappe med en junction til en annen katalog, ville hver av de stiene fulgt den, og eviction ville slettet filer et sted bufferen aldri eide. Siden v2.770.173 sjekker hver av de inngangspunktene reparse-punkt-attributten og hopper over en koblet dokumentmappe: et oppslag teller et bom, en lagring teller en skrivefeil, og eviction lar den være i fred
Unicode-stier og delte røtter
To relaterte fikser betyr noe hvis du deployerer til brukerprofiler. Før v2.770.135 var RenderCacheFolder en AnsiString, så en mappe utenfor systemets kodepage (et kinesisk brukernavn på en engelsk Windows-installasjon, for eksempel) ble konvertert med tap før bufferen så den; egenskapen er nå en Unicode string, og den atomære erstatningen bruker det vide Windows API. Siden v2.770.52 deler flere THotPDF-instanser i én prosess som peker på samme rot (etter stiutvidelse, sammenlignet case-insensitivt) en enkelt referansetalt indeks og lås. Tidligere overskrev hver instans index.txt med sin egen kopi og håndhevet grensene mot sitt delvise syn, så mappen kunne vokse flere ganger forbi budsjettet sitt
Den delingen stopper ved prosessgrensen. To separate prosesser på samme rot holder fortsatt separate minneindekser, så gi hver samtidig kjørende applikasjon sin egen bufferrot. Viewere som rendrer på worker-tråder har det fint innenfor én prosess: PrefetchLoadedPages og køen dekket i bakgrunnsrendering med en request-kø går begge gjennom samme cachete sti og samme lås
Hurtigreferanse: RenderCacheFolder-sjekkliste
- Sett
RenderCacheFolder,RenderCacheMaxDocumentsogRenderCacheMaxBytesfør det første kallet tilRenderLoadedPageToBitmapCached; for strøm- og random access-lastinger, sett mappen før lasting - Oppgrader til v2.770.140 eller senere hvis du er avhengig av disk-laget; tidligere versjoner godtar egenskapen men serverer aldri en side fra disk for vanlige lastinger
- Forvent ingen disk-caching for krypterte PDF-er, for dokumenter redigert etter lasting, eller mens
RenderFallbackPolicyikke errfpIgnore - Frigjør THotPDF-instansen normalt; siden v2.770.140 sletter verken
FreeellerInvalidateRenderedPageCachedisk-oppføringer - Å endre
PageRenderBackendeller ICC-arbeidsflyten beholder dokumentet på disk-laget under en annen nøkkel - Bruk én bufferrot per kjørende applikasjon; instanser innenfor én prosess deler indeksen siden v2.770.52
- Hold bufferroten på et per-bruker-sted; dokumentundermapper som er junctions hoppes over siden v2.770.173
En vedvarende sidebuffer betaler seg best i en viewer som åpner de samme dokumentene på nytt hele dagen, noe som er nøyaktig formen på arkitekturen for custom PDF viewer i Delphi beskrevet andre steder på denne bloggen. RenderCacheFolder, minnebasert raster-buffer og siderendereren følger med HotPDF Delphi PDF-komponenten for Delphi og C++Builder