PDFium Component otvara PDF koji se još skida kroz TPdfProgressiveDocument, TPdf podklasu koja omata PDFiumov availability API FPDFAvail_*. BeginProgressiveLoad pokreće sesiju, CheckDocumentAvailability javlja koje bajt raspone PDFium još treba, OpenProgressiveDocument otvara datoteku čim ima dovoljno bajtova, a CancelProgressiveLoad odustaje od prekinutog preuzimanja bez curenja nativnih handlea. Teško nije sretan put. Viewer na krhkoj vezi vidjet će korisnike kako zatvore tab na 25 posto, predomisle se i otvore isti link ponovno, i svaka od tih prekinutih sesija ima nativni availability handle, dva C callback zapisa, stream adapter i skup zahtjeva raspona u letu koje treba otpustiti točno pravim redoslijedom
Kako TPdfProgressiveDocument loada PDF koji se još skida?
TPdfProgressiveDocument drži PDFium availability providera živim dok se random-access stream puni, i pita providera prije svakog koraka parsiranja jesu li bajtovi koje hoće prisutni. BeginProgressiveLoad(AStream, AFileSize, AOwnsStream, AInitialAvailableByteCount) uzima podupirući stream plus logičku veličinu udaljene datoteke, užara IsDataAvail callback i AddSegment callback u dva zapisa i zove FPDFAvail_Create. Kad PDFium pita postoji li raspon, komponenta odgovara da ako raspon leži unutar kontinuiranog prefiksa opisanog s AvailableByteCount ili unutar raspona već dovršenog kroz RangeRequests planer, a OnDataAvailable event presudu može nadjačati za rijetke storeove. Svaki poziv CheckDocumentAvailability vraća jednu od tri vrijednosti TPdfDataAvailability (pdaAvailable, pdaNotAvailable, pdaError) i predaje rasponse koje je PDFium tražio kao sortirani, spojeni TPdfDownloadRanges niz, već u redu čekanja planera uz prioritet rrpImmediate
// FetchRange je Vaš transport (HTTP Range GET, socket, blob reader):
// zapisuje Size bajtova na Offset u Store i vraća koliko je stiglo
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;
// Hintovi su već u redu čekanja; najprije zapišite bajtove, pa dovršite
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;
Dva detalja u toj petlji nose teret. Strop krugova važan je jer mrtvi link tjera CheckDocumentAvailability da traži iste raspone dovijeka, a beskonačna petlja mrežnu grešku pretvara u objeseno sučelje. Redoslijed je važan jer planer svoje stanje serializira kritičnom sekcijom ali ništa ne radi za TStream.Position podupirućeg storea: transportna nit mora odgovorne bajtove zapisati u stream prije poziva CompleteRequest, jer od trenutka kad se dovršetak objavi PDFium može taj raspon čitati, a paralelni pisci trebaju pozicionirani I/O ili vlastiti lokot
Zašto AvailableByteCount odbija ići unatrag?
AvailableByteCount samo raste, a setter podiže EPdfError s "Available byte count cannot move backwards" kad pokušate smanjiti. Jednom kad je IsDataAvail callback rekao PDFiumu da raspon postoji, parser je možda već iz njega pročitao i keširao objekte, pa bi povlačenje tih bajtova poslije odgovore o dostupnosti učinilo nekonzistentnima s onim što je PDFium već progutao. Isti setter odbija vrijednosti veće od LogicalFileSize i izvan sesije podiže "No progressive load is active", pa bajtovi koje već držite prije početka loada pripadaju argumentu AInitialAvailableByteCount u BeginProgressiveLoad, a ne dodjeli svojstva učinjenoj prerano. Ako se Vaš download store puni izvan reda, nemojte to uopće pokušavati izraziti kroz prefiks: dovršite raspone kroz planer ili odgovorite kroz OnDataAvailable
Kad se djelomično skinuti PDF stvarno može otvoriti?
Samo linearizirani PDF (ISO 32000-1 Aneks F, "Fast Web View" raspored) otvara se prije nego stigne cijela datoteka; nelinearizirani PDF i dalje treba svaki bajt. OpenProgressiveDocument provjerava svojstvo Linearization (plnUnknown, plnNotLinearized, plnLinearized) i rutira sukladno tome: linearizirana datoteka otvara se kroz FPDFAvail_GetDocument čim su prisutne sekcija prve stranice i tablice hintova, dok se nelinearizirana otvara kroz FPDF_LoadCustomDocument na istom zapisu pristupa datoteci i tretira čitljivom samo kao cjelina. Rutiranje postoji iz konkretnog razloga. Poziv FPDFAvail_GetDocument na nelineariziranoj datoteci može vratiti ne-nil handle čiji je broj stranica nula, dokument koji izgleda otvoren a prazan je. U vlastitom test suiteu komponente 51-stranični linearizirani fixture doseže pdaAvailable i otvara se s punim stablom stranica dok rijetki download store još ne pokriva datoteku
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 je sada aktivna stranica
pdaError:
Exit(False);
pdaNotAvailable:
while Pdf.RangeRequests.TryDequeue(Request) do
Pdf.RangeRequests.CompleteRequest(Request,
FetchRange(Store, Request.Offset, Request.Size));
end;
end;
LoadAvailablePage uzima broj stranice od 1 i nameće redoslijed koji PDFium očekuje: prije prve provjere stranice izvodi CheckFormAvailability, koji omata FPDFAvail_IsFormAvail, i tek zatim zove FPDFAvail_IsPageAvail. Rezultat pfaNotPresent normalan je odgovor za dokument bez AcroForma i ništa ne blokira. Kad je stranica spremna, LoadAvailablePage čini je aktivnom stranicom, pa viewer može prikazati stranicu 1 lineariziranog brošure dok su ostale stranice još u tranzitu; FirstAvailablePageNumber govori koju stranicu rječnik linearizacije označava prvom, već pretvoreno iz PDFiumova indeksa od nule
Što CancelProgressiveLoad otpušta i u kojem redoslijedu?
CancelProgressiveLoad rastavlja sesiju u četiri koraka koja se ne mogu presložiti: otkazati raspon planer, zatvoriti dokument, uništiti availability handle s FPDFAvail_Destroy, pa otpustiti callback zapise i osloboditi stream adapter. Otkazivanje planera prvo podiže njegov brojač generacije, baca svaki na čekanju i u letu zahtjev i okida OnCancelRequest za svaki u letu, pa transportni dovršetak koji slijeti kasnije nosi staru generaciju i CompleteRequest vraća False a da ništa ne dira. Dokument se mora zatvoriti prije nego availability handle i adapter nestanu jer PDFium može zvati natrag u provider pristupa datoteci dok zatvara dokument, i ako je adapter već nestao, taj callback čita oslobođenu memoriju
procedure TDownloadForm.FormCreate(Sender: TObject);
begin
FPdf := TPdfProgressiveDocument.Create(nil);
// Planer živi dok i FPdf, pa ga užarite jednom
FPdf.RangeRequests.OnCancelRequest := RangeCancelled;
end;
procedure TDownloadForm.RangeCancelled(Sender: TObject; RequestId: UInt64;
Attempt: Cardinal);
begin
FTransport.Abort(RequestId); // Vaš kod: zatvorite taj socket ili zahtjev
end;
procedure TDownloadForm.CancelButtonClick(Sender: TObject);
begin
FPdf.CancelProgressiveLoad;
// ProgressiveLoading = False, Active = False, AvailableByteCount = 0
end;
Metoda je idempotentna i jedina je staza čišćenja za tri situacije: BeginProgressiveLoad koji padne na pola konstrukcije, eksplicitno korisničko otkazivanje i destruktor. BeginProgressiveLoad nju zove i prije početka, pa ponovno pokretanje istog objekta na novom URL-u sigurno je bez eksplicitnog otkazivanja. Jedna odluka o vlasništvu je na Vama da je pogodite: ako radna nit piše u podupirući stream, pošaljite AOwnsStream = False i stream oslobodite sami nakon što se radna nit zaustavi, jer s predanim vlasništvom otkazivanje oslobađa stream dok kasni zapis može još biti na putu. Iznimke podignute unutar OnCancelRequest gutaju se po zahtjevu da jedna posrna transport ne blokira ostala otkazivanja
Kako lifecycle suite dokazuje da staza otkazivanja ne curi?
PDFium Component lifecycle stress suite izvodi prekinuto download-stil preuzimanje u svakom miješanom ciklusu. Svaki ciklus pokreće progressive load čiji store drži samo četvrtinu bajtova fixturu, zahtjeva pdaNotAvailable s nepraznim popisom hintova, zove CancelProgressiveLoad i tvrdi da objekt ne javlja ni ProgressiveLoading ni Active; zatim izvodi istu streaming stazu do kraja s punom dostupnošću, OpenProgressiveDocument, jednim renderom i zatvaranjem. Zadani miješani rad pokriva 100 mjerenih ciklusa sa 600 otvaranja, 2300 rendera i 100 progressive otkazivanja, a uzorkovana privatna memorija narasla je 8,21 MiB naspram budžeta od 32 MiB. Suite progressive otkazivanja broji odvojeno od render-callback otkazivanja, jer su prekinuto preuzimanje i render petlja koja staje rano različiti događaji s različitim kriterijima prihvaćanja
Gdje progressive put prestaje pomoći
Nekoliko granica vrijedi znati prije nego na ovome gradite viewer. Značajkama kojima trebaju izvorni bajtovi datoteke nepotpuni progressive izvor odbijaju umjesto da nagađaju: ReadXmpPacket pada eksplicitno, a validacija potpisa javlja Indeterminate dok cijela datoteka ne stigne. Zadani test dostupnosti pretpostavlja kontinuirani prefiks, pa transport koji dohvaća raspone izvan reda mora ih dovršavati kroz RangeRequests ili odgovarati kroz OnDataAvailable, ili će PDFium i dalje tražiti bajtove koje već držite. Nelinearizirana datoteka ne dobiva ništa u vremenu do prve stranice, pa ako je brzi prvi prikaz bitan, linearizirajte datoteku na strani servera. A CancelProgressiveLoad sam ne zatvara Vaše sockete; OnCancelRequest je kuka gdje se to događa
Za obični put stream adaptera koji loada kompletnu lokalnu datoteku na zahtjev, pogledajte streamanje velikih PDF-ova na zahtjev uz PDFium; za otvaranje PDF-a koji sjedi unutar većeg buffera, pogledajte byte range loadanje ugrađenih PDF-ova. Otkazivanje sporo rendera stranice koja je već učitana zaseban je mehanizam, obrađen u prekidivom progresivnom renderanju stranica. TPdfProgressiveDocument i njegov raspon planer isporučuju se s PDFium Componentom za Delphi i C++Builder