PDFium Component odpre PDF, ki se še vedno prenaša, skozi TPdfProgressiveDocument, podrazred TPdf, ki ovija razpoložljivostni API FPDFAvail_* PDFium. BeginProgressiveLoad zažene sejo, CheckDocumentAvailability poroča, katerih bajtnih obsegov še potrebuje PDFium, OpenProgressiveDocument odpre datoteko, ko obstaja dovolj bajtov, CancelProgressiveLoad pa opusti prekinjen prenos brez puščanja izvornih ročajev. Težaven del ni srečna pot. Gledalnik na nestabilni povezavi bo videl uporabnike, ki zaprejo zavihek pri 25 odstotkih, si premislijo in znova odprejo isto povezavo, vsaka od teh opuščenih sej pa ima izvorni ročaj razpoložljivosti, dva zapisa klicev nazaj C, adapter toka in nabor zahtev obsegov v letu, ki jih je treba sprostiti v točno pravem vrstem redu
Kako TPdfProgressiveDocument naloži PDF, ki se še vedno prenaša?
TPdfProgressiveDocument obdrži ponudnika razpoložljivosti PDFium pri življenju, medtem ko se polni tok s prostim dostopom, in pred vsakim korakom razčlenjevanja tega ponudnika vpraša, ali so bajti, ki jih hoče, prisotni. BeginProgressiveLoad(AStream, AFileSize, AOwnsStream, AInitialAvailableByteCount) vzame podporni tok plus logično velikost oddaljene datoteke, poveže klic nazaj IsDataAvail in klic nazaj AddSegment v dva zapisa ter pokliče FPDFAvail_Create. Ko PDFium vpraša, ali je obseg prisoten, komponenta odgovori ja, če leži obseg znotraj zvezne predpone, ki jo opisuje AvailableByteCount, ali znotraj obsega, že dokončanega skozi razporejevalnik RangeRequests, dogodek OnDataAvailable pa lahko sodbo preglasi za redke shrambe. Vsak klic CheckDocumentAvailability vrne eno od treh vrednosti TPdfDataAvailability (pdaAvailable, pdaNotAvailable, pdaError) in poda nazaj obsege, ki jih je zahteval PDFium, kot urejeno, spojeno tabelo TPdfDownloadRanges, že v vrsti na razporejevalniku s prednostjo rrpImmediate
// FetchRange je vaš transport (HTTP Range GET, vtičnica, bralec blobov):
// zapiše Size bajtov pri Offset v Store in vrne, koliko jih je prispljalo
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;
// Namigi so že v vrsti; najprej zapišite bajte, nato dokončajte
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;
Dve podrobnosti v tej zanki nosita obremenitev. Strop krogov je pomemben, ker mrtva povezava spravi CheckDocumentAvailability k vprašanju po istih obsegih za vedno, neomejena zanka pa iz omrežne napake naredi obeseni vmesnik. Vrstni red je pomemben, ker razporejevalnik svoje stanje serializira s kritičnim odsekom, za TStream.Position na podporni shrambi pa ne naredi nič: nit transporta mora bajte odgovora zapisati v tok, preden pokliče CompleteRequest, saj se v trenutku, ko je dokončanje objavljeno, PDFium lahko loti brati ta obseg, sočasni pisatelji pa potrebujejo pozicionirani V/I ali svojo ključavnico
Zakaj AvailableByteCount odkloni premik nazaj?
AvailableByteCount le raste, nastavljavnik pa sproži EPdfError z »štetje razpoložljivih bajtov se ne sme premikati nazaj«, kadar poskusite, da bi ga skrčili. Ko je klic nazaj IsDataAvail povedal PDFium, da obseg obstaja, je razčlenjevalnik morda že prebral in predpomnil objekte iz njega, tako da bi umik teh bajtov pozneje naredil odgovore o razpoložljivosti neskladne s tem, kar je PDFium že porabil. Isti nastavljavnik zavrne vrednosti, večje od LogicalFileSize, in sproži »nobeno postopno nalaganje ni dejavno« izven seje, kar je razlog, da bajti, ki jih že imate, preden se nalaganje začne, pripadajo argumentu AInitialAvailableByteCount pri BeginProgressiveLoad, ne pa dodelitvi lastnosti, narejeni prezgodaj. Če se vaša shramba prenosa polni izven vrstnega reda, tega sploh ne poskušajte izraziti skozi predpono: obsege dokončajte skozi razporejevalnik ali odgovorite skozi OnDataAvailable
Kdaj se delno prenesen PDF dejansko lahko odpre?
Le lineariziran PDF (priloga F ISO 32000-1, razporeditev »Fast Web View«) se odpre, preden prispe cela datoteka; nelineariziran PDF še vedno potrebuje vsak bajt. OpenProgressiveDocument preveri lastnost Linearization (plnUnknown, plnNotLinearized, plnLinearized) in usmerja ustrezno: linearizirana datoteka se odpre skozi FPDFAvail_GetDocument, takoj ko so prisotni odsek prve strani in namigovne tabele, nelinearizirana datoteka pa se odpre skozi FPDF_LoadCustomDocument na istem zapisu dostopa do datoteke in se obravnava kot berljiva le kot celota. Usmerjanje obstaja iz konkretnega razloga. Klic FPDFAvail_GetDocument na nelinearizirani datoteki lahko vrne ročaj, ki ni ničelni, katerega štetje strani je nič — dokument, ki zgleda odprt in je prazen. V lastni testni zbirki komponente lineariziran fixture s 51 stranmi doseže pdaAvailable in se odpre s svojim polnim drevesom strani, medtem ko redka shramba prenosa še vedno ne pokrije datoteke
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 zdaj dejavna stran
pdaError:
Exit(False);
pdaNotAvailable:
while Pdf.RangeRequests.TryDequeue(Request) do
Pdf.RangeRequests.CompleteRequest(Request,
FetchRange(Store, Request.Offset, Request.Size));
end;
end;
LoadAvailablePage vzame številko strani z osnovo 1 in vsili vrstni red, ki ga pričakuje PDFium: pred preizkusom prve strani požene CheckFormAvailability, ki ovija FPDFAvail_IsFormAvail, šele nato pa pokliče FPDFAvail_IsPageAvail. Rezultat pfaNotPresent je običajen odgovor za dokument brez AcroForm in ničesar ne blokira. Ko je stran pripravljena, jo LoadAvailablePage naredi za dejavno stran, tako da gledalnik lahko izriše stran 1 linearizirane brošure, medtem ko so preostale strani še vedno v prenosu; FirstAvailablePageNumber pove, katero stran slovar linearizacije imenuje za prvo, že pretvorjeno iz ničelno osnovanega indeksa PDFium
Kaj sprosti CancelProgressiveLoad in v kakšnem vrstem redu?
CancelProgressiveLoad razstavi sejo v štirih korakih, ki jih ni mogoče preurediti: prekliče razporejevalnik obsegov, zapre dokument, uniči ročaj razpoložljivosti s FPDFAvail_Destroy, nato pa odstrani zapise klicev nazaj in sprosti adapter toka. Preklic razporejevalnika najprej poveča njegov števec generacij, spusti vsako čakajočo in v letu obstoječo zahtevo ter sproži OnCancelRequest za vsako v letu, tako da transportno dokončanje, ki pristne pozneje, nosi staro generacijo in CompleteRequest vrne False, brez da bi se česa dotaknil. Dokument se mora zapreti, preden odideta ročaj razpoložljivosti in adapter, ker se lahko PDFium pokliče nazaj v ponudnika dostopa do datoteke, medtem ko zapira dokument, če pa je adapter že odšel, ta klic nazaj bere sproščen spomin
procedure TDownloadForm.FormCreate(Sender: TObject);
begin
FPdf := TPdfProgressiveDocument.Create(nil);
// Razporejevalnik živi, dokler živi FPdf, zato ga povežite enkrat
FPdf.RangeRequests.OnCancelRequest := RangeCancelled;
end;
procedure TDownloadForm.RangeCancelled(Sender: TObject; RequestId: UInt64;
Attempt: Cardinal);
begin
FTransport.Abort(RequestId); // vaša koda: zapri ta vtičnik ali zahtevo
end;
procedure TDownloadForm.CancelButtonClick(Sender: TObject);
begin
FPdf.CancelProgressiveLoad;
// ProgressiveLoading = False, Active = False, AvailableByteCount = 0
end;
Metoda je idempotentna in je ena sama pot čiščenja za tri situacije: BeginProgressiveLoad, ki spodleti na polovici gradnje, izrecen uporabniški preklic in destruktor. BeginProgressiveLoad jo pokliče tudi pred začetkom, tako da je ponovni zagon istega objekta na novem URL-ju varen brez izrecnega preklica. Ena odločitev o lastnini je vaša, da jo zadete: če nit delavca zapisuje v podporni tok, podajte AOwnsStream = False in tok sprostite sami, potem ko se je delavec ustavil, ker s predano lastnino preklic sprosti tok, medtem ko je lahko pozni zapis še vedno na poti. Izjeme, sprožene znotraj OnCancelRequest, so pogltljene na zahtevo, tako da en spodletel transport ne more blokirati preostalih preklicev
Kako zbirka življenjskega cikla dokaže, da pot preklica ne pušča?
Stresna zbirka življenjskega cikla PDFium Component vadi prekinjen prenos v omrežnem stilu na vsakem mešanem ciklu. Vsak cikel zažene postopno nalaganje, katerega shramba drži le četrtino bajtov fixture, zahteva pdaNotAvailable z nepraznim seznamom namigov, pokliče CancelProgressiveLoad in trdi, da objekt ne prijavlja niti ProgressiveLoading niti Active; nato požene isto pot pretakanja do konca s polno razpoložljivostjo, OpenProgressiveDocument, izrisom in zaprtjem. Privzeti mešani tek pokrije 100 merjenih ciklov s 600 odpiranji, 2300 izrisi in 100 postopnimi preklici, vzorčen zasebni spomin pa je zrasel za 8,21 MiB proti proračunu 32 MiB. Zbirka šteje postopne preklice ločeno od preklicev klicev nazaj izrisa, ker sta opuščen prenos in zanka izrisa, ki se ustavi zgodaj, različna dogodka z različnimi merili sprejemanja
Kje pot postopnosti preneha pomagati
Nekaj mej se splača poznati, preden na tem zgradite gledalnik. Zmožnosti, ki potrebujejo bajte izvirne datoteke, odklonijo nepopoln postopen vir, namesto da bi uganjale: ReadXmpPacket spodleti izrecno in validacija podpisov poroča Indeterminate, dokler ni prisotna cela datoteka. Privzeti preizkus razpoložljivosti privzame zvezno predpono, tako da transport, ki pridobiva obsege izven vrstnega reda, mora te dokončati skozi RangeRequests ali odgovoriti skozi OnDataAvailable, sicer bo PDFium še naprej spraševal po bajtih, ki jih že imate. Nelinearizirana datoteka ne dobi nič glede časa do prve strani, tako da, če je pomemben hiter prvi izris, linearizirajte datoteko na strani strežnika. CancelProgressiveLoad pa sam ne zapre vaših vtičnic; OnCancelRequest je kavelj, kjer se to zgodi
Za golo pot adapterja toka, ki na zahtevo naloži popolno krajevno datoteko, glej pretakanje velikih PDF-jev na zahtevo s PDFium; za odpiranje PDF-ja, ki sedi znotraj večjega medpomnilnika, glej nalaganje bajtnih obsegov za vgrajene PDF-je. Preklic počasnega izrisa strani, ki je že naložena, je ločen mehanizem, pokrit v preklicljivem postopnem izrisu strani. TPdfProgressiveDocument in njegov razporejevalnik obsegov odplujeta z PDFium Component za Delphi in C++Builder