PDFium Component membuka PDF yang masih terunduh lewat TPdfProgressiveDocument, subclass TPdf yang membungkus API availability FPDFAvail_* milik PDFium. BeginProgressiveLoad memulai sesinya, CheckDocumentAvailability melaporkan rentang byte mana yang masih dibutuhkan PDFium, OpenProgressiveDocument membuka berkasnya begitu byte yang cukup ada, dan CancelProgressiveLoad meninggalkan unduhan yang terputus tanpa membocorkan handle native. Bagian sulitnya bukan happy path-nya. Viewer di koneksi yang berkelit akan melihat pengguna menutup tab di 25 persen, berubah pikiran, lalu membuka link yang sama lagi, dan setiap sesi yang terputus itu punya satu handle availability native, dua record callback C, satu stream adapter, dan satu set permintaan rentang yang sedang berjalan, semuanya harus dilepas dalam urutan yang benar persis
Bagaimana TPdfProgressiveDocument memuat PDF yang masih terunduh?
TPdfProgressiveDocument menjaga provider availability PDFium tetap hidup selama stream random-access diisi, dan bertanya ke provider itu sebelum setiap langkah parse apakah byte yang ia inginkan sudah ada. BeginProgressiveLoad(AStream, AFileSize, AOwnsStream, AInitialAvailableByteCount) menerima stream pendukung plus ukuran logis berkas jarak jauh, menyambungkan callback IsDataAvail dan callback AddSegment ke dua record, lalu memanggil FPDFAvail_Create. Saat PDFium bertanya apakah sebuah rentang sudah ada, komponen menjawab ya kalau rentang itu berada di dalam prefiks kontigu yang dideskripsikan AvailableByteCount atau di dalam rentang yang sudah selesai lewat scheduler RangeRequests, dan event OnDataAvailable bisa meng-override vonis itu untuk store yang sparse. Tiap panggilan CheckDocumentAvailability mengembalikan satu dari tiga nilai TPdfDataAvailability (pdaAvailable, pdaNotAvailable, pdaError) dan menyerahkan rentang yang diminta PDFium sebagai array TPdfDownloadRanges yang terurut dan tergabung, sudah mengantre di scheduler dengan prioritas rrpImmediate
// FetchRange adalah transport Anda (HTTP Range GET, socket, pembaca blob):
// ia menulis Size byte di Offset ke Store dan mengembalikan berapa yang tiba
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;
// Hint-nya sudah mengantre; tulis byte-nya dulu, baru lengkapi
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;
Dua detail di loop itu menopang beban. Cap ronde penting karena link mati membuat CheckDocumentAvailability meminta rentang yang sama selamanya, dan loop tanpa batas mengubah kegagalan jaringan jadi UI yang menggantung. Urutannya penting karena scheduler menserialisasi state-nya sendiri dengan critical section tapi tidak melakukan apa pun untuk TStream.Position di store pendukung: thread transport harus menulis byte respons ke stream sebelum memanggil CompleteRequest, karena begitu sebuah completion terbit, PDFium bisa langsung membaca rentang itu, dan penulis yang bersamaan butuh I/O berposisi atau lock milik sendiri
Mengapa AvailableByteCount menolak bergerak mundur?
AvailableByteCount hanya bertambah, dan setter-nya melempar EPdfError dengan "Available byte count cannot move backwards" saat Anda mencoba mengecilnya. Begitu callback IsDataAvail sudah memberi tahu PDFium bahwa sebuah rentang ada, parser bisa jadi sudah membaca dan mencache objek dari sana, jadi menarik byte-byte itu kemudian akan membuat jawaban availability tidak konsisten dengan yang sudah PDFium konsumsi. Setter yang sama menolak nilai yang lebih besar dari LogicalFileSize dan melempar "No progressive load is active" di luar sesi, itulah sebabnya byte yang sudah Anda pegang sebelum load dimulai sebaiknya lewat argumen AInitialAvailableByteCount milik BeginProgressiveLoad, bukan lewat assignment properti yang dibuat terlalu dini. Kalau store unduhan Anda terisi tak berurutan, jangan coba ungkapkannya lewat prefiks sama sekali: lengkapi rentangnya lewat scheduler atau jawab lewat OnDataAvailable
Kapan PDF yang terunduh sebagian benar-benar bisa terbuka?
Hanya PDF linearized (ISO 32000-1 Annex F, tata letak "Fast Web View") yang terbuka sebelum seluruh berkas tiba; PDF non-linearized masih butuh setiap byte. OpenProgressiveDocument memeriksa properti Linearization (plnUnknown, plnNotLinearized, plnLinearized) dan mengarahkan sesuai itu: berkas linearized terbuka lewat FPDFAvail_GetDocument begitu bagian halaman pertama dan tabel hint hadir, sementara berkas non-linearized dibuka lewat FPDF_LoadCustomDocument pada record akses berkas yang sama dan dianggap terbaca hanya sebagai satu kesatuan. Pengarahannya ada dengan alasan konkret. Memanggil FPDFAvail_GetDocument pada berkas non-linearized bisa mengembalikan handle non-null yang jumlah halamannya nol, dokumen yang tampak terbuka dan kosong. Di test suite milik komponen sendiri, fixture linearized 51 halaman mencapai pdaAvailable dan terbuka dengan page tree penuhnya sementara store unduhan yang sparse belum menutupi berkasnya
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 kini jadi halaman aktif
pdaError:
Exit(False);
pdaNotAvailable:
while Pdf.RangeRequests.TryDequeue(Request) do
Pdf.RangeRequests.CompleteRequest(Request,
FetchRange(Store, Request.Offset, Request.Size));
end;
end;
LoadAvailablePage menerima nomor halaman berbasis 1 dan menegakkan urutan yang diharapkan PDFium: sebelum pemeriksaan halaman pertama ia menjalankan CheckFormAvailability, yang membungkus FPDFAvail_IsFormAvail, dan hanya setelah itu ia memanggil FPDFAvail_IsPageAvail. Hasil pfaNotPresent adalah jawaban normal untuk dokumen tanpa AcroForm dan tidak memblokir apa pun. Saat halamannya siap, LoadAvailablePage menjadikannya halaman aktif, jadi viewer bisa merender halaman 1 dari brosur linearized sementara halaman-halaman sisanya masih di jalan; FirstAvailablePageNumber memberi tahu halaman mana yang ditunjuk dictionary linearization sebagai yang pertama, sudah dikonversi dari indeks berbasis nol milik PDFium
Apa yang dilepas CancelProgressiveLoad, dan dalam urutan apa?
CancelProgressiveLoad membongkar sesi dalam empat langkah yang tak bisa diubah urutannya: batalkan scheduler rentang, tutup dokumen, hancurkan handle availability dengan FPDFAvail_Destroy, lalu buang record callback dan bebaskan stream adapter. Membatalkan scheduler lebih dulu menaikkan counter generation-nya, menjatuhkan setiap permintaan yang mengantre dan sedang berjalan, dan menembakkan OnCancelRequest untuk tiap yang sedang berjalan, jadi completion transport yang mendarat belakangan membawa generation lama dan CompleteRequest mengembalikan False tanpa menyentuh apa pun. Dokumen harus tertutup sebelum handle availability dan adapter pergi karena PDFium bisa callback ke provider akses berkas saat ia menutup dokumen, dan kalau adapternya sudah tiada, callback itu membaca memori yang sudah dibebaskan
procedure TDownloadForm.FormCreate(Sender: TObject);
begin
FPdf := TPdfProgressiveDocument.Create(nil);
// Scheduler hidup selama FPdf, jadi sambungkan sekali saja
FPdf.RangeRequests.OnCancelRequest := RangeCancelled;
end;
procedure TDownloadForm.RangeCancelled(Sender: TObject; RequestId: UInt64;
Attempt: Cardinal);
begin
FTransport.Abort(RequestId); // kode Anda: tutup socket atau request itu
end;
procedure TDownloadForm.CancelButtonClick(Sender: TObject);
begin
FPdf.CancelProgressiveLoad;
// ProgressiveLoading = False, Active = False, AvailableByteCount = 0
end;
Metodenya idempotent dan menjadi satu-satunya jalur pembersihan untuk tiga situasi: BeginProgressiveLoad yang gagal di tengah konstruksi, cancel eksplisit oleh pengguna, dan destructor. BeginProgressiveLoad juga memanggilnya sebelum mulai, jadi menyalakan ulang objek yang sama di URL baru aman tanpa cancel eksplisit. Satu keputusan kepemilikan yang harus Anda benarkan sendiri: kalau worker thread menulis ke stream pendukung, kirim AOwnsStream = False dan bebaskan streamnya sendiri setelah worker berhenti, karena dengan kepemilikan yang diserahterimakan, cancel membebaskan stream saat sebuah write yang terlambat mungkin masih di jalan. Exception yang dilempar di dalam OnCancelRequest ditelan per permintaan supaya satu transport yang gagal tak bisa memblokir pembatalan sisanya
Bagaimana suite lifecycle membuktikan jalur cancel tidak bocor?
Suite stress lifecycle PDFium Component mengolah unduhan bergaya jaringan yang terputus pada tiap siklus campurannya. Tiap siklus memulai progressive load yang store-nya hanya memegang seperempat byte fixture, mensyaratkan pdaNotAvailable dengan daftar hint yang tak kosong, memanggil CancelProgressiveLoad, dan meng-assert bahwa objek melaporkan bukan ProgressiveLoading maupun Active; lalu ia menjalankan jalur streaming yang sama sampai tuntas dengan availability penuh, OpenProgressiveDocument, satu render, dan satu close. Run campur bawaan mencakup 100 siklus terukur dengan 600 open, 2300 render, dan 100 cancel progressive, dan memori privat sampelannya tumbuh 8,21 MiB terhadap anggaran 32 MiB. Suite menghitung cancel progressive terpisah dari cancel render-callback, karena unduhan yang terputus dan loop render yang berhenti lebih awal adalah dua kejadian berbeda dengan kriteria penerimaan berbeda
Di mana jalur progressive berhenti menolong
Beberapa batas layak diketahui sebelum Anda membangun viewer di atasnya. Fitur yang butuh byte berkas asli menolak sumber progressive yang belum lengkap alih-alih menebak: ReadXmpPacket gagal secara eksplisit dan validasi signature melaporkan Indeterminate sampai seluruh berkas hadir. Uji availability bawaan mengasumsikan prefiks kontigu, jadi transport yang mengambil rentang tak berurutan harus melengkapinya lewat RangeRequests atau menjawab lewat OnDataAvailable, kalau tidak PDFium akan terus meminta byte yang sudah Anda pegang. Berkas non-linearized tak mendapat apa pun soal waktu ke halaman pertama, jadi kalau first paint yang cepat penting, linearized-kan berkasnya di sisi server. Dan CancelProgressiveLoad tidak menutup socket Anda sendirian; OnCancelRequest adalah hook tempat itu terjadi
Untuk jalur stream-adapter polos yang memuat berkas lokal lengkap sesuai permintaan, lihat streaming PDF besar sesuai permintaan dengan PDFium; untuk membuka PDF yang berada di dalam buffer yang lebih besar, lihat byte range loading untuk PDF tertanam. Membatalkan render yang lambat milik halaman yang sudah termuat adalah mekanisme terpisah, dibahas di rendering halaman progressive yang bisa dibatalkan. TPdfProgressiveDocument dan scheduler rentangnya dikirim bersama PDFium Component for Delphi and C++Builder