PDFium Component avaa vielä latautuessa olevan PDF:n TPdfProgressiveDocumentin kautta, TPdfin aliluokka joka käärii PDFiumin FPDFAvail_*-saatavuus-API:n. BeginProgressiveLoad käynnistää istunnon, CheckDocumentAvailability raportoi mitkä tavualueet PDFium vielä tarvitsee, OpenProgressiveDocument avaa tiedoston kun tarpeeksi tavuja on olemassa, ja CancelProgressiveLoad hylkää keskeytetyn latauksen vuotamatta natiiveja handlexia. Vaikea osa ei ole onnistumisen polku. Huonolla yhteydellä oleva katselin näkee käyttäjät sulkemassa välilehden 25 prosentissa, muuttamassa mielensä ja avaamassa saman linkin uudelleen, ja jokaisella kyseisistä keskeytetyistä istunnoista on natiivi saatavuushandle, kaksi C-callback-tietuetta, stream-sovitin ja joukko lennossa olevia aluepyyntöjä jotka on vapautettava täsmälleen oikeassa järjestyksessä
Miten TPdfProgressiveDocument lataa vielä latautuessa olevan PDF:n?
TPdfProgressiveDocument pitää PDFiumin saatavuustarjoajan hengissä kun satunnaislukustreamia täytetään, ja kysyy kyseiseltä tarjoajalta ennen jokaista jäsennysaskelta ovatko sen haluamat tavut läsnä. BeginProgressiveLoad(AStream, AFileSize, AOwnsStream, AInitialAvailableByteCount) ottaa taustalle streamin plus etätiedoston loogisen koon, kytkee IsDataAvail-callbackin ja AddSegment-callbackin kahteen tietueeseen ja kutsuu FPDFAvail_Createia. Kun PDFium kysyy onko alue läsnä, komponentti vastaa kyllä jos alue sijaitsee AvailableByteCountin kuvaaman yhtenäisen etuliitteen sisällä tai alueen sisällä joka on jo viimeistelty RangeRequests-ajastimen kautta, ja OnDataAvailable-eventti voi kumota tuomion harvoille varastoille. Jokainen CheckDocumentAvailability-kutsu palauttaa yhden kolmesta TPdfDataAvailability-arvosta (pdaAvailable, pdaNotAvailable, pdaError) ja luovuttaa PDFiumin pyytämät alueet lajiteltuna, yhdistettynä TPdfDownloadRanges-taulukkona, jo jonossa ajastimella rrpImmediate-prioriteetilla
// FetchRange on sinun kuljetuksesi (HTTP Range GET, socket, blob-lukija):
// se kirjoittaa Size tavua Offsettiin Storeen ja palauttaa kuinka monta saapui
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;
// Vihjeet ovat jo jonossa; kirjoita tavut ensin, viimeistele sitten
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;
Kaksi yksityiskohtaa kyseisessä silmukassa kantaa kuormaa. Kierroskatto merkitsee koska kuollut linkki saa CheckDocumentAvailabilityin kysymään samoja alueita ikuisesti, ja rajaton silmukka kääntää verkkovirheen jumiutuneeksi käyttöliittymäksi. Järjestys merkitsee koska ajastin serialisoi oman tilansa critical sectionilla mutta ei tee mitään TStream.Positionille taustavarastossa: kuljetussäikeen on kirjoitettava vastauksen tavut streamiin ennen CompleteRequestin kutsumista, sillä sillä hetkellä kun viimeistely julkaistaan PDFium voi lukea kyseisen alueen, ja rinnakkaiset kirjoittajat tarvitsevat positionoitua I/O:a tai oman lukon
Miksi AvailableByteCount kieltäytyy liikkumasta taaksepäin?
AvailableByteCount vain kasvaa, ja setteri nostaa EPdfErrorin viestillä "Available byte count cannot move backwards" kun yrität kutistaa sitä. Kun IsDataAvail-callback on kertonut PDFiumille että alue on olemassa, jäsentin voi jo lukea ja välimuistittaa objekteja siitä, joten niiden tavujen peruuttaminen jälkikäteen tekisi saatavusvastauksista epäjohdonmukaisia siihen nähden mitä PDFium on jo kuluttanut. Sama setteri hylkää arvot jotka ovat suurempia kuin LogicalFileSize ja nostaa "No progressive load is active" -viestin istunnon ulkopuolella, mistä syystä tavut jotka jo pidät ennen latauksen alkamista kuuluvat BeginProgressiveLoadin AInitialAvailableByteCount-argumenttiin eikä liian aikaiseen tehtyyn property-sijoitukseen. Jos latausvarastosi täyttyy epäjärjestyksessä, älä yritä ilmaista sitä etuliitteen kautta lainkaan: viimeistele alueet ajastimen kautta tai vastaa OnDataAvailablein kautta
Milloin osittain ladattu PDF oikeasti avautuu?
Vain linearisoitu PDF (ISO 32000-1 Annex F, "Fast Web View" -asettelu) avautuu ennen kuin koko tiedosto on saapunut; ei-linearisoitu PDF tarvitsee yhä jokaisen tavun. OpenProgressiveDocument tarkistaa Linearization-propertyn (plnUnknown, plnNotLinearized, plnLinearized) ja reitittää sen mukaan: linearisoitu tiedosto avautuu FPDFAvail_GetDocumentin kautta heti kun ensimmäisen sivun osio ja vihjetaulukot ovat läsnä, kun taas ei-linearisoitu tiedosto avataan FPDF_LoadCustomDocumentilla saman tiedostokäsittelytietueen yli ja kohdellaan luettavana vain kokonaisuutena. Reititys on olemassa konkreettisesta syystä. FPDFAvail_GetDocumentin kutsuminen ei-linearisolulle tiedostolle voi palauttaa ei-null handlein jonka sivumäärä on nolla, dokumentti joka näyttää avautuneen ja on tyhjä. Komponentin omassa testisarjassa 51-sivuinen linearisoitu fixtuuri saavuttaa pdaAvailablein ja avautuu täydellisellä sivupuullaan kun harva latausvarasto ei yhä kata tiedostoa
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 on nyt aktiivinen sivu
pdaError:
Exit(False);
pdaNotAvailable:
while Pdf.RangeRequests.TryDequeue(Request) do
Pdf.RangeRequests.CompleteRequest(Request,
FetchRange(Store, Request.Offset, Request.Size));
end;
end;
LoadAvailablePage ottaa 1-pohjaisen sivunumeron ja valvoo järjestystä jota PDFium odottaa: ennen ensimmäistä sivutarkistusta se ajaa CheckFormAvailabilityin, joka käärii FPDFAvail_IsFormAvailin, ja vasta sen jälkeen se kutsuu FPDFAvail_IsPageAvailia. Tulos pfaNotPresent on normaali vastaus dokumentille ilman AcroFormia eikä estä mitään. Kun sivu on valmis, LoadAvailablePage tekee siitä aktiivisen sivun, joten katselin voi renderöidä linearisoidun esitteen sivun 1 kun taas loput sivut ovat yhä matkalla; FirstAvailablePageNumber kertoo minkä sivun linearisaatiosanakirja nimeää ensimmäiseksi, jo konvertoituna PDFiumin nollapohjaisesta indeksistä
Mitä CancelProgressiveLoad vapauttaa, ja missä järjestyksessä?
CancelProgressiveLoad purkaa istunnon neljässä askeleessa joita ei voi järjestää uudelleen: peruuta alueajastin, sulje dokumentti, tuhoa saatavuushandle funktiolla FPDFAvail_Destroy, sitten hävitä callback-tietueet ja vapauta stream-sovitin. Ajastimen peruuttaminen ensimmäisenä nostaa sen generatiolaskuria, pudottaa jokaisen jonossa olevan ja lennossa olevan pyynnön, ja laukaisee OnCancelRequestin jokaiselle lennossa olevalle, joten myöhemmin laskeutuva kuljetusviimeistely kantaa vanhaa generatiota ja CompleteRequest palauttaa arvon False koskematta mihinkään. Dokumentin on suljettava ennen kuin saatavuushandle ja sovitin katoavat, koska PDFium voi kutsua takaisin tiedostokäsittelytietueeseen sulkiessaan dokumenttia, ja jos sovitin on jo poissa kyseinen callback lukee vapautettua muistia
procedure TDownloadForm.FormCreate(Sender: TObject);
begin
FPdf := TPdfProgressiveDocument.Create(nil);
// Ajastin elää yhtä kauan kuin FPdf, joten kytkä se kerran
FPdf.RangeRequests.OnCancelRequest := RangeCancelled;
end;
procedure TDownloadForm.RangeCancelled(Sender: TObject; RequestId: UInt64;
Attempt: Cardinal);
begin
FTransport.Abort(RequestId); // sinun koodisi: sulje kyseinen socket tai pyyntö
end;
procedure TDownloadForm.CancelButtonClick(Sender: TObject);
begin
FPdf.CancelProgressiveLoad;
// ProgressiveLoading = False, Active = False, AvailableByteCount = 0
end;
Metodi on idempotentti ja on yksi siivouspolku kolmelle tilanteelle: BeginProgressiveLoad joka epäonnistuu rakentamisen puolivälissä, eksplisiittinen käyttäjän peruutus ja destruktori. BeginProgressiveLoad kutsuu sitä myös ennen aloitusta, joten saman objektin uudelleenkäynnistäminen uudelle URLille on turvallista ilman eksplisiittistä peruutusta. Yksi omistajuuspäätös on sinun saatava oikein: jos työskerrosäie kirjoittaa taustalle streamiin, välitä AOwnsStream = False ja vapauta stream itse työskerrosäikeen pysähdyttyä, koska omistajuuden luovuttamisella peruutus vapauttaa streamin kun myöhäinen kirjoitus voi yhä olla matkalla. OnCancelRequestin sisällä nostetut poikkeukset niellään pyyntöä kohden joten yksi epäonnistuva kuljetus ei voi estää loput peruutukset
Miten lifecycle-testisarja todistaa ettei perumispolku vuoda?
PDFium Componentin lifecycle-stressisarja harjoittelee keskeytettyä verkkotyyppistä latausta jokaisella sekoitetulla kierroksella. Jokainen kierros käynnistää progressiivisen latauksen jonka varasto kantaa vain neljäsosan fixtuurin tavuista, vaatii pdaNotAvailablein ei-tyhjällä vihjelistalla, kutsuu CancelProgressiveLoadia ja assertoi että objekti raportoi ei ProgressiveLoadingia eikä Activea; se ajaa sitten saman streamauspolun loppuun täydellisellä saatavuudella, OpenProgressiveDocumentilla, renderöinnillä ja sulkemisella. Oletussekoitettu ajo kattaa 100 mitattua kierrosta 600 avauksella, 2300 renderöinnillä ja 100 progressiivisella peruutuksella, ja näytteistetty yksityinen muisti kasvoi 8,21 MiB 32 MiB:n budjettia vasten. Testisarja laskee progressiiviset peruutukset erikseen render-callback-peruutuksista, koska keskeytetty lataus ja varhain pysähtyvä renderöintisilmukka ovat eri tapahtumia erilaisilla hyväksymiskriteereillä
Missä progressiivinen polku lakkaa auttamasta
Muutama raja kannattaa tietää ennen kuin rakennat katselinta tämän päälle. Ominaisuudet jotka tarvitsevat alkuperäisen tiedoston tavut kieltäytyvät puutteellisesta progressiivisesta lähteestä sen sijaan että arvaisivat: ReadXmpPacket epäonnistuu eksplisiittisesti ja allekirjoitusvalidointi raportoi Indeterminaten kunnes koko tiedosto on läsnä. Oletussaatavuustesti olettaa yhtenäisen etuliitteen, joten kuljetus joka hakee alueita epäjärjestyksessä on viimeisteltävä ne RangeRequestsin kautta tai vastattava OnDataAvailablein kautta, tai PDFium kysyy yhä tavuja jotka jo pidät. Ei-linearisoitu tiedosto ei saa mitään etua ensimmäisen sivun aikaan, joten jos nopea ensimmäinen maalaus merkitsee, linearisoi tiedosto palvelinpuolella. Ja CancelProgressiveLoad ei sulje sockettejasi itse; OnCancelRequest on koukku jossa se tapahtuu
Tavalliselle stream-sovitinpolulle joka lataa kokonaisen paikallisen tiedoston tarpeen mukaan katso artikkeli suurten PDF:ien streamauksesta tarpeen mukaan PDFiumilla; isommassa puskurissa istuvan PDF:n avaamisesta katso artikkeli tavualueiden lataus upotetuille PDF:eille. Jo ladatun sivun hitaan renderöinnin peruuttaminen on erillinen mekanismi, jota käsittelee artikkeli perutettavissa oleva progressiivinen sivun renderöinti. TPdfProgressiveDocument ja sen alueajastin toimitetaan mukana PDFium Component for Delphi and C++Builder -paketissa