PDFium Component åbner en PDF, der stadig downloader, gennem TPdfProgressiveDocument, en TPdf-subklasse, der wrapper PDFiums FPDFAvail_*-availability-API. BeginProgressiveLoad starter sessionen, CheckDocumentAvailability rapporterer, hvilke byte-ranges PDFium stadig mangler, OpenProgressiveDocument åbner filen, så snart der er nok bytes, og CancelProgressiveLoad opgiver en afbrudt download uden at lække native handles. Det svære er ikke glad-vejen. En viewer på en ustabil forbindelse oplever brugere, der lukker fanen ved 25 procent, ændrer mening og åbner samme link igen, og hver eneste af de afbrudte sessioner har et native availability-handle, to C-callback-records, en stream-adapter og et sæt in-flight range requests, der skal frigives i præcis den rigtige rækkefølge
Hvordan loader TPdfProgressiveDocument en PDF, der stadig downloader?
TPdfProgressiveDocument holder en PDFium availability-provider i live, mens en random-access-stream fyldes, og spørger provideren før hvert parse-trin, om de bytes, den vil have, er til stede. BeginProgressiveLoad(AStream, AFileSize, AOwnsStream, AInitialAvailableByteCount) tager backing-streamen plus den logiske størrelse af fjernfilen, kobler en IsDataAvail-callback og en AddSegment-callback ind i to records og kalder FPDFAvail_Create. Når PDFium spørger, om et range er til stede, svarer komponenten ja, hvis range'et ligger inden for det kontinuerte prefix, som AvailableByteCount beskriver, eller inden for et range, der allerede er fuldført gennem RangeRequests-scheduleren, og OnDataAvailable-eventet kan overlade kendelsen for sparse stores. Hvert kald til CheckDocumentAvailability returnerer én af tre TPdfDataAvailability-værdier (pdaAvailable, pdaNotAvailable, pdaError) og giver de ranges, PDFium bad om, tilbage som et sorteret, fusioneret TPdfDownloadRanges-array, allerede sat i kø på scheduleren med rrpImmediate-prioritet
// FetchRange er din transport (HTTP Range GET, socket, blob reader):
// den skriver Size bytes ved Offset ind i Store og returnerer, hvor mange kom frem
function FetchRange(Store: TStream; Offset, Size: UInt64): UInt64; forward;
procedure OpenWhileDownloading(Pdf: TPdfProgressiveDocument; Store: TStream;
RemoteSize: UInt64);
const
MaxRounds = 64;
var
Hints: TPdfDownloadRanges;
State: TPdfDataAvailability;
Request: TPdfRangeRequest;
Round: Integer;
begin
Pdf.BeginProgressiveLoad(Store, RemoteSize, False);
State := pdaNotAvailable;
for Round := 1 to MaxRounds do
begin
State := Pdf.CheckDocumentAvailability(Hints);
if State <> pdaNotAvailable then
Break;
// Hintsene er allerede i kø; skriv bytesene først, fuldfør derefter
while Pdf.RangeRequests.TryDequeue(Request) do
Pdf.RangeRequests.CompleteRequest(Request,
FetchRange(Store, Request.Offset, Request.Size));
end;
if State <> pdaAvailable then
raise EPdfError.Create('The document could not be discovered');
Pdf.OpenProgressiveDocument;
end;
To detaljer i den løkke er load-bærende. Runde-loftet betyder noget, fordi et dødt link får CheckDocumentAvailability til at bede om de samme ranges for evigt, og en ubegrænset løkke gør en netværksfejl til en hængende UI. Rækkefølgen betyder noget, fordi scheduleren serialiserer sin egen tilstand med en critical section, men ikke gør noget for TStream.Position på backing store: en transporttråd skal skrive svarbytesene ind i streamen, før den kalder CompleteRequest, for i det øjeblik en fuldførelse publiceres, kan PDFium læse det range, og samtidige writere behøver positioneret I/O eller en lås af deres egen
Hvorfor nægter AvailableByteCount at gå baglæns?
AvailableByteCount vokser kun, og setteren rejser en EPdfError med "Available byte count cannot move backwards", når du forsøger at krympe den. Når IsDataAvail-callbacken har fortalt PDFium, at et range findes, kan parseren allerede have læst og cachet objekter fra det, så at trække de bytes tilbage bagefter ville gøre availability-svarene inkonsistente med det, PDFium allerede har indtaget. Samme setter afviser værdier større end LogicalFileSize og rejser "No progressive load is active" uden for en session, hvilket er derfor, bytes, du allerede holder, før loadet starter, hører hjemme i AInitialAvailableByteCount-argumentet til BeginProgressiveLoad frem for i en egenskabstildeling lavet for tidligt. Fylder din download-store ude af rækkefølge, så forsøg ikke at udtrykke det gennem prefixet: fuldfør ranges gennem scheduleren, eller svar gennem OnDataAvailable
Hvornår kan en delvist downloadet PDF reelt åbnes?
Kun en lineariseret PDF (ISO 32000-1 Annex F, "Fast Web View"-layoutet) åbner, før hele filen er ankommet; en ikke-lineariseret PDF behøver stadig hver byte. OpenProgressiveDocument tjekker Linearization-egenskaben (plnUnknown, plnNotLinearized, plnLinearized) og ruter derefter: en lineariseret fil åbnes gennem FPDFAvail_GetDocument, så snart first-page-sektionen og hint-tabellerne er til stede, mens en ikke-lineariseret fil åbnes gennem FPDF_LoadCustomDocument på samme file-access-record og behandles som læsbar kun som helhed. Routingen findes af en konkret grund. At kalde FPDFAvail_GetDocument på en ikke-lineariseret fil kan returnere et non-null handle, hvis sideantal er nul — et dokument, der ser åbent ud og er tomt. I komponentens egen test-suite når en 51-siders lineariseret fixture pdaAvailable og åbner med sit fulde paginetræ, mens den sparse download-store stadig ikke dækker filen
function WaitForPage(Pdf: TPdfProgressiveDocument; Store: TStream;
PageNumber: Integer): Boolean;
var
Hints: TPdfDownloadRanges;
Request: TPdfRangeRequest;
Round: Integer;
begin
Result := False;
for Round := 1 to 64 do
case Pdf.LoadAvailablePage(PageNumber, Hints) of
pdaAvailable:
Exit(True); // PageNumber er nu den aktive side
pdaError:
Exit(False);
pdaNotAvailable:
while Pdf.RangeRequests.TryDequeue(Request) do
Pdf.RangeRequests.CompleteRequest(Request,
FetchRange(Store, Request.Offset, Request.Size));
end;
end;
LoadAvailablePage tager et 1-baseret sidetal og håndhæver rækkefølgen, PDFium forventer: før det første side-tjek kører den CheckFormAvailability, som wrapper FPDFAvail_IsFormAvail, og først derefter kalder den FPDFAvail_IsPageAvail. Et resultat af pfaNotPresent er det normale svar for et dokument uden AcroForm og blokerer ingenting. Når siden er klar, gør LoadAvailablePage den til den aktive side, så en viewer kan rendere side 1 i en lineariseret brochure, mens de resterende sider stadig er undervejs; FirstAvailablePageNumber fortæller, hvilken side lineariseringsdictionaryen udpeger som den første, allerede konverteret fra PDFiums nul-baserede indeks
Hvad frigiver CancelProgressiveLoad, og i hvilken rækkefølge?
CancelProgressiveLoad river en session ned i fire trin, der ikke kan byttes om: annullér range-scheduleren, luk dokumentet, destrúr availability-handleet med FPDFAvail_Destroy, og disponér derefter callback-recordene og frigiv stream-adapteren. At annullere scheduleren først bumper dens generationstæller, dropper hvert pending- og in-flight-request og fyre OnCancelRequest for hvert in-flight-request, så en transportfuldførelse, der lander senere, bærer den gamle generation, og CompleteRequest returnerer False uden at røre noget. Dokumentet skal lukkes, før availability-handleet og adapteren forsvinder, fordi PDFium kan calle tilbage ind i file-access-provideren, mens det lukker et dokument, og hvis adapteren allerede er væk, læser callback frigjort hukommelse
procedure TDownloadForm.FormCreate(Sender: TObject);
begin
FPdf := TPdfProgressiveDocument.Create(nil);
// Scheduleren lever så længe FPdf, så kobl den én gang
FPdf.RangeRequests.OnCancelRequest := RangeCancelled;
end;
procedure TDownloadForm.RangeCancelled(Sender: TObject; RequestId: UInt64;
Attempt: Cardinal);
begin
FTransport.Abort(RequestId); // din kode: luk den socket eller request
end;
procedure TDownloadForm.CancelButtonClick(Sender: TObject);
begin
FPdf.CancelProgressiveLoad;
// ProgressiveLoading = False, Active = False, AvailableByteCount = 0
end;
Metoden er idempotent og er den eneste cleanup-vej for tre situationer: en BeginProgressiveLoad, der fejler halvvejs gennem konstruktionen, en eksplicit bruger-cancel og destruktoren. BeginProgressiveLoad kalder den også, før den starter, så genstart af samme objekt på en ny URL er sikker uden en eksplicit cancel. Én ownership-beslutning er din at få rigtigt: skriver en worker-tråd ind i backing-streamen, så giv AOwnsStream = False og frigiv streamen selv, efter workeren er stoppet, for med ownership overdraget frigør cancel'en streamen, mens en sen skriv måske stadig er på vej. Exceptions rejst inde i OnCancelRequest opsluges pr. request, så én fejlende transport ikke kan blokere de resterende annulleringer
Hvordan beviser lifecycle-suiten, at cancel-vejen ikke lækker?
PDFium Components lifecycle stress-suite øver en afbrudt netværksagtig download på hver blandet cyklus. Hver cyklus starter et progressivt load, hvis store kun holder en fjerdedel af fixture-bytes, kræver pdaNotAvailable med en ikke-tom hint-liste, kalder CancelProgressiveLoad og assert, at objektet hverken rapporterer ProgressiveLoading eller Active; derefter kører den samme streaming-vej til fuldførelse med fuld availability, OpenProgressiveDocument, en rendering og en lukning. Det default blandede run dækker 100 målte cyklusser med 600 åbninger, 2300 renderinger og 100 progressive annulleringer, og samplet privat hukommelse voksede med 8,21 MiB mod et 32 MiB-budget. Suiten tæller progressive annulleringer separat fra render-callback-annulleringer, for en afbrudt download og en render-løkke, der stopper tidligt, er forskellige events med forskellige acceptkriterier
Hvor den progressive vej holder op med at hjælpe
Et par grænser er værd at kende, før du bygger en viewer oven på dette. Features, der behøver de originale file-bytes, afviser en ufuldstændig progressiv kilde i stedet for at gætte: ReadXmpPacket fejler eksplicit, og signaturvalidering rapporterer Indeterminate, til hele filen er til stede. Availability-testen antager et kontinuert prefix, så en transport, der henter ranges ude af rækkefølge, skal fuldføre dem gennem RangeRequests eller svare gennem OnDataAvailable, ellers bliver PDFium ved med at bede om bytes, du allerede holder. En ikke-lineariseret fil vinder ingenting i tid til første side, så hvis hurtig første painting betyder noget, linearisér filen på serversiden. Og CancelProgressiveLoad lukker ikke dine sockets af sig selv; OnCancelRequest er hooken, hvor det sker
For den almindelige stream-adapter-vej, der loader en komplet lokal fil on demand, se streaming large PDFs on demand with PDFium; for at åbne en PDF, der ligger inde i en større buffer, se byte range loading for embedded PDFs. At annullere en langsom rendering af en allerede loadet side er en separat mekanisme, gennemgået i cancellable progressive page rendering. TPdfProgressiveDocument og dets range-scheduler skibes med PDFium Component for Delphi and C++Builder