HotPDFs THPDFBackgroundRenderer-klasse er en TThread-etterkommer som gjengir innlastede PDF-sider til bitkart på en arbeidertråd, slik at en Delphi-fremviser kan fortsette å rulle og tegne på nytt mens en side fortsatt rasteriseres i bakgrunnen. THPDFBackgroundRenderer.RequestPage setter en sideindeks i kø for den arbeidertråden, CancelAll forkaster alt som fortsatt venter, og GetCachedBitmap overleverer et ferdig bitkart som kalleren eier og må frigjøre. Å rulle gjennom en to hundre siders skannet kontrakt i utskriftsoppløsning på UI-tråden alene fører til at hver sideomslag stanser vinduet inntil GDI er ferdig med å tegne den, og det er nøyaktig den hakkingen THPDFBackgroundRenderer finnes for å fjerne
Hvorfor i det hele tatt gjengi PDF-sider på en bakgrunnstråd?
En bakgrunnstråd rettferdiggjør kompleksiteten sin fordi HotPDFs sidefremviser er en ekte innholdsstrøm-tolker, ikke en billig bitkart-kopi som returnerer før noen merker det: den går gjennom PDF-operatorer, holder en grafikktilstands-stakk, og rasteriserer baner, bilder og glyfer gjennom GDI, den samme motoren dekket i å gjengi innlastede PDF-sider til et TBitmap. Kjør det arbeidet synkront inne i en rulle- eller male-håndterer, og meldingsløkken slutter å pumpe inntil kallet returnerer, og det er nøyaktig det et fastfrosset vindu faktisk er. Å legge inn Application.ProcessMessages inne i gjengivelseskallet løser ikke dette: det lar meldingskøen tømmes, men selve gjengivelsen eier fortsatt den kallende tråden, slik at vinduet tegner utdatert innhold raskere mens det reelle arbeidet ikke har flyttet seg noe sted. Den eneste måten å holde en fremviser responsiv under en genuint treg gjengivelse på, er å kjøre den gjengivelsen et annet sted, noe som er grunnen til at THPDFBackgroundRenderer finnes som en TThread-underklasse i stedet for en callback eller en timer
Å sette opp en forespørselskø for en rullende fremviser
THPDFBackgroundRenderer.Create tar den innlastede THotPDF-instansen og en DPI som forblir fast gjennom hele gjengiverens levetid, slik at hver side satt i kø gjennom én instans gjengis ved én oppløsning; en fremviser som støtter zoom, trenger en ny gjengiver, ikke en ny DPI-egenskap, hver gang zoom-nivået endres. RequestPage legger til en sideindeks i en intern kø og returnerer umiddelbart: den gjør ingen gjengivelse selv og rører aldri UI-tråden. Execute, det nedarvede TThread-inngangspunktet HotPDF kjører når man kaller Start, trekker én indeks av gangen fra fronten av den køen, gjengir den gjennom dokumentets sidebuffer, og lagrer en kopi indeksert på side, slik at GetCachedBitmap kan overlevere den senere
type
TViewerForm = class(TForm)
RenderPollTimer: TTimer;
procedure RenderPollTimerTimer(Sender: TObject);
private
FDoc: THotPDF;
FRenderer: THPDFBackgroundRenderer;
FPendingPage: Integer;
procedure RequestPageWindow(CenterPage: Integer);
end;
procedure TViewerForm.RequestPageWindow(CenterPage: Integer);
var
I: Integer;
begin
if FRenderer <> nil then
begin
FRenderer.CancelAll;
FRenderer.Free;
end;
FRenderer := THPDFBackgroundRenderer.Create(FDoc, 150);
for I := CenterPage - 1 to CenterPage + 1 do
if (I >= 0) and (I < FDoc.LoadedPageCount) then
FRenderer.RequestPage(I);
FPendingPage := CenterPage;
FRenderer.Start;
end;
procedure TViewerForm.RenderPollTimerTimer(Sender: TObject);
var
Bmp: TBitmap;
begin
if FRenderer = nil then Exit;
Bmp := FRenderer.GetCachedBitmap(FPendingPage);
if Bmp <> nil then
begin
PageImage.Picture.Bitmap.Assign(Bmp);
Bmp.Free;
end;
end;
GetCachedBitmap returnerer nil inntil den sidens kopi er klar, så et poll-på-en-timer-mønster som det ovenfor er nok; det finnes ingen separat klar-hendelse å koble opp, HotPDF løser dette med en enkel nil-sjekk i stedet for et større varslings-API. Neste avsnitt dekker hva CancelAll og det Free-kallet faktisk gjør, fordi begge betyr noe når sider begynner å gjengis i feil rekkefølge, eller en rulling skjer raskere enn køen kan tømmes
Ett-kalls-snarveien for én enkelt side
THotPDF.RenderLoadedPageToBitmapAsync finnes for det vanlige tilfellet med å sende av gårde nøyaktig én side uten å røre THPDFBackgroundRenderer direkte: den konstruerer gjengiveren internt, kaller RequestPage én gang, starter tråden, og returnerer TThread-referansen til kalleren, som eier den og er ansvarlig for å frigjøre den. Å hente resultatet går gjennom THotPDF.GetLoadedCachedRenderedBitmap i stedet for gjengiverens egen GetCachedBitmap, fordi GetLoadedCachedRenderedBitmap leser dokumentets delte buffer, nøkkelsatt på sideindeks og DPI, den samme bufferen RenderLoadedPageToBitmapCached og den innebygde prefetcheren allerede fyller — en side en annen del av fremviseren allerede har gjengitt ved den DPI-en, kan komme tilbake umiddelbart, før bakgrunnstråden som nettopp ble startet, i det hele tatt har blitt planlagt av OS-et
// A simpler alternative to the queue above, for one page at a time.
procedure TViewerForm.RequestSinglePage(PageIndex: Integer);
begin
if FAsyncWorker <> nil then
FAsyncWorker.Free; // waits if a prior page is still rendering
FAsyncWorker := Pdf.RenderLoadedPageToBitmapAsync(PageIndex, 150);
FPendingPage := PageIndex;
end;
procedure TViewerForm.AsyncPollTimerTimer(Sender: TObject);
var
Bmp: TBitmap;
begin
Bmp := Pdf.GetLoadedCachedRenderedBitmap(FPendingPage, 150);
if Bmp <> nil then
begin
PageImage.Picture.Bitmap.Assign(Bmp);
Bmp.Free;
end;
end;
Kan man avbryte en side som allerede er satt i kø?
CancelAll fjerner bare jobber som fortsatt ligger i køen; en side HotPDF allerede har trukket av fronten og overlevert til gjengivelseskallet sitt, fortsetter til fullføring, fordi THPDFBackgroundRenderer ikke har noen mekanisme for å avbryte arbeid som allerede er i gang. Det er en rimelig avveining i praksis — en enkelt sidegjengivelse er sjelden lang nok til å gjøre forgripelse verdt den ekstra kompleksiteten — men en rask rulling som utløser CancelAll ved hver rullehendelse, betaler likevel for hvilken som helst side som var midt i gjengivelse i det øyeblikket av hver avbrytelse. Den offisielle referansen er direkte om dette: allerede-kjørende gjengivelse kan fullføre før tråden avsluttes
Execute har en andre, lett-å-overse, oppførsel: løkken avslutter så snart den finner køen tom, den ligger ikke og venter på at mer arbeid skal komme. En THPDFBackgroundRenderer-instans er derfor en engangs-batch-arbeider, ikke en vedvarende bakgrunnstjeneste — sett en håndfull sider i kø, kall Start, og så snart den siste sideoppgaven i køen har gjengitt, avslutter den underliggende OS-tråden av seg selv. Å kalle RequestPage igjen på den samme instansen etter at Execute allerede har tømt køen, starter den ikke på nytt, noe som er nøyaktig grunnen til at RequestPageWindow ovenfor erstatter gjengiverinstansen ved hvert kall i stedet for å prøve å fortsette å mate ett langlevd objekt
Er det trygt å røre et TBitmap fra en bakgrunnstråd i Delphi?
Å røre et TBitmap fra en bakgrunnstråd er trygt i HotPDFs design så lenge bare én tråd noensinne opererer på en gitt bitkart-instans om gangen, og THPDFBackgroundRenderer håndhever den grensen i stedet for å overlate den til kalleren. Execute gjengir hver side inne i dokumentets egen gjengivelseslås, den samme kritiske seksjonen hvert RenderLoadedPageToBitmapCached-kall og den innebygde PrefetchLoadedPages-prefetcheren allerede deler, slik at selve GDI-tegningen for en gitt side skjer på nøyaktig én tråd om gangen og aldri overlapper en annen gjengivelse av det dokumentet. Det resulterende bitkartet er et objekt eid av arbeidertråden som THPDFBackgroundRenderer aldri publiserer direkte til en kaller
GetCachedBitmap allokerer i stedet et helt nytt TBitmap og kaller Assign på det under gjengiverens egen separate lås, slik at kopieringen alltid skjer mens Execute er blokkert fra å erstatte den bufferplassen under den — den kallende tråden får pikseldata, aldri det opprinnelige håndtaket. Den separasjonen er også grunnen til å motstå fristelsen til å bygge en egen gjengivelsestråd som kaller HotPDFs gjengivelsesfunksjoner direkte uten å gå gjennom THPDFBackgroundRenderer eller PrefetchLoadedPages: to gjengivelser som kappløper mot det samme innlastede dokumentets delte buffere og objektgraf, er nøyaktig det scenarioet HotPDFs interne låsing finnes for å forhindre, og bakgrunnsgjengiver-klassen gir deg den låsingen gratis i stedet for å måtte reimplementere den
Hvordan skiller dette seg fra HotPDFs innebygde sideforhåndslasting?
PrefetchLoadedPages og THPDFBackgroundRenderer løser beslektede, men forskjellige problemer: PrefetchLoadedPages, gitt et sideområde, gjengir hele det nabolaget inn i den delte dokumentbufferen automatisk på sin egen arbeidertråd, uten noe kø-objekt kalleren må opprette eller administrere. THPDFBackgroundRenderer bytter den automatikken mot kontroll — kalleren bestemmer nøyaktig hvilke sideindekser som betyr noe og i hvilken rekkefølge, og kan avbryte de som fortsatt er i kø uten å røre hvilket område den innebygde prefetcheren varmer opp et annet sted. Begge kanaliseres gjennom den samme gjengivelseslåsen, slik at en fremviser kan kjøre PrefetchLoadedPages for det vanlige neste-få-sider-tilfellet og gripe til THPDFBackgroundRenderer bare når noe utenfor det mønsteret dukker opp, slik som en miniatyrbilde-stripe som hopper rett til en side brukeren nettopp klikket på
begin
// PrefetchLoadedPages takes a 1-based "start-end" range string, while
// RequestPage below stays 0-based like every other loaded-page index.
Pdf.PrefetchLoadedPages(Format('%d-%d', [CenterPage + 1, CenterPage + 5]), 150);
// Reach for THPDFBackgroundRenderer only for a page outside that
// window, such as a thumbnail the user just clicked.
FRenderer := THPDFBackgroundRenderer.Create(Pdf, 150);
FRenderer.RequestPage(ClickedThumbnailPage);
FRenderer.Start;
end;
To livssyklusdetaljer er verdt å ta med seg inn i produksjonskode. Den dokumentomfattende bufferen bak RenderLoadedPageToBitmapCached er begrenset av RenderCacheCapacity, åtte sider som standard, og kaster ut den minst nylig brukte oppføringen når den er full, men en THPDFBackgroundRenderer-instans' egen resultatliste har ingen slik grense — den beholder ett bitkart per distinkt sideindeks noensinne forespurt gjennom den instansen inntil selve instansen frigjøres, så en gjengiver holdt i live gjennom en hel rulleøkt ved høy DPI, vil villig akkumulere ett fullopplyst bitkart per side rullet forbi. HotPDF avbryter heller ikke en kaller-opprettet gjengiver automatisk slik den avbryter sin egen prefetcher før et dokument lastes inn eller ødelegger seg selv, ettersom en THPDFBackgroundRenderer-instans aldri registreres på THotPDF-objektet den peker på — så den kallende koden må avbryte og frigjøre hver gjengiver bygget mot et dokument før det dokumentet lastes inn på nytt eller frigjøres, den samme rekkefølgedisiplinen HotPDF anvender på PrefetchLoadedPages internt
THPDFBackgroundRenderer er én bit av den innlastede-dokument-fasaden bak HotPDFs MVC-fremviserarkitektur, og den kombineres naturlig med filnivå-arbeidsflytene i Direct File API for store PDF-er når dokumentet som rulles gjennom, i seg selv er for stort til å laste inn uanstrengt i utgangspunktet. Bakgrunnsgjengivelse, forespørselskøer, og gjengivelsesbufferen beskrevet her er alle en del av den standard HotPDF-komponenten for Delphi og C++Builder