HotPDF RenderCacheFolder mengubah cache halaman hasil render di memori milik komponen Delphi HotPDF menjadi page cache disk yang persisten: halaman hasil render ditulis sebagai file PNG di bawah folder pilihan Anda, dan saat PDF sumber yang sama dibuka lagi, RenderLoadedPageToBitmapCached membacanya kembali alih-alih merasterisasi ulang. Urutan lookup-nya memori, lalu disk, lalu renderer
Tier disk sudah ada di API sejak v2.416.0, tapi sampai v2.770.140 ia tak pernah benar-benar menyajikan satu halaman pun untuk panggilan LoadFromFile atau LoadFromStream normal. Perbaikannya memaksa satu pertanyaan yang harus dijawab setiap cache persisten: bagaimana Anda tahu bahwa file yang Anda buka hari ini adalah dokumen yang Anda render kemarin, dan apa yang terjadi pada halaman ter-cache ketika bukan? Di bawah ini jawaban-jawaban yang dipilih HotPDF, termasuk di mana ia dengan sengaja menolak men-cache
Bagaimana disk render cache HotPDF bekerja?
Disk render cache HotPDF adalah tier kedua di belakang raster cache di memori, dan ia hanya berpartisipasi ketika RenderCacheFolder adalah path non-kosong. Panggilan RenderLoadedPageToBitmapCached(PageIndex, DPI) lebih dulu memindai entri di memori, yang di-key oleh indeks halaman, DPI, dan varian render-settings. Kalau miss, ia bertanya ke tier disk; hit disk men-decode PNG-nya, mem-promote-nya kembali ke memori, dan mengembalikan salinan milik caller. Hanya ketika kedua tier miss halaman melalui content-stream interpreter yang dijelaskan di merender halaman PDF loaded menjadi TBitmap, dan bitmap barunya juga ditulis ke disk
Di disk, layoutnya sengaja membosankan. Tiap dokumen mendapat subfolder yang dinamai dari key dokumen 16 karakter hex plus varian render 16 karakter hex, tiap halaman disimpan sebagai <page>@<dpi>.png, dan index.txt di root menjaga dokumen dalam urutan most-recently-used di balik satu schema tag. Ketidakcocokan schema membersihkan folder itu pada pemakaian pertama. Penulisan menuju file sementara lebih dulu lalu ditukar ke tempatnya dengan atomic replace, sehingga crash di tengah penulisan menyisakan halaman lama atau tidak sama sekali, tak pernah setengah PNG. PNG yang gagal di-decode dihapus dan dihitung sebagai miss
Tiga limit membatasi folder itu:
RenderCacheMaxDocuments(default 20) membatasi jumlah subfolder dokumen; folder least recently used di-evict lebih duluRenderCacheMaxBytes(default 524288000, yaitu 500 MB) membatasi ukuran total semua file PNG di bawah root- Tiap folder dokumen menyimpan paling banyak 200 image halaman; batas per dokumen itu tetap oleh THotPDF dan bukan property yang dipublikasikan
RenderCacheCapacity (default 8) adalah kenop terpisah: ia menyetel berapa halaman hasil render yang disimpan tier memori, dan tak ada hubungannya dengan jejak disk
uses
SysUtils, Graphics, HPDFDoc;
procedure WarmThumbnails(const FileName: string);
var
Pdf: THotPDF;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
// Konfigurasi disk tier sebelum render cache pertama:
// folder dan kedua limit dibaca saat tier pertama kali dipakai
Pdf.RenderCacheFolder := IncludeTrailingPathDelimiter(
GetEnvironmentVariable('LOCALAPPDATA')) + 'MyViewer\PageCache';
Pdf.RenderCacheMaxDocuments := 50;
Pdf.RenderCacheMaxBytes := Int64(1024) * 1024 * 1024; // 1 GiB
Pdf.RenderCacheCapacity := 16; // halaman di memori
if Pdf.LoadFromFile(FileName) > 0 then
for I := 0 to Pdf.LoadedPageCount - 1 do
begin
Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 96);
if Bmp <> nil then
try
// Serahkan salinannya ke thumbnail strip di sini
finally
Bmp.Free; // panggilan cache selalu mengembalikan salinan milik caller
end;
end;
finally
Pdf.Free; // sejak v2.770.140 ini tak lagi menghapus entri disk
end;
end;
Jalankan prosedur yang sama dua kali dan run kedua tak pernah merasterisasi halaman yang muat di cache. Objek disk cache dibuat lazily pada render cache pertama dan hidup sampai instance THotPDF di-free, jadi mengubah RenderCacheFolder, RenderCacheMaxDocuments, atau RenderCacheMaxBytes setelah titik itu tak memindahkan atau mengubah ukuran cache yang sudah terbuka. Halaman yang terlalu besar untuk admission policy memori (secara default satu entri tak boleh melebihi 64 MiB piksel 32-bit) juga tak dipersistenkan, dan tier disk hanya dikonsultasikan selama RenderFallbackPolicy menjaga default rfpIgnore-nya, karena diagnostik fallback tidak disimpan bersama PNG
Kenapa RenderCacheFolder tak pernah bekerja sebelum v2.770.140?
RenderCacheFolder tak berpengaruh sebelum v2.770.140 karena tier disk meng-key dokumen pada hash byte sumber yang tidak pernah disimpan oleh load biasa. Key dokumen datang dari SHA-256 atas salinan internal byte PDF mentah, tapi LoadFromFile dan LoadFromStream mem-parse sumbernya di tempat dan tidak menyimpan salinan semacam itu; field itu hanya terisi sementara di jalur encrypted recovery dan dikosongkan lagi tepat setelahnya. Tanpa byte, key selalu kosong, dan key kosong berarti tier disk dilewati. Tanpa error, tanpa warning, cuma folder yang tetap kosong
Membuat key itu non-kosong membuka bug kedua yang sembunyi di balik yang pertama. InvalidateRenderedPageCache yang lama menghapus folder disk dokumen, dan InvalidateRenderedPageCache jalan di awal setiap load, pada setiap edit, dan di dalam Free. Jadi begitu key-nya bekerja, setiap sesi viewer akan menghancurkan cache-nya sendiri saat keluar, dan sesi berikutnya tetap mulai dingin. Lebih buruk, key dihitung ulang dari sumber yang sama setelah edit, sehingga render dokumen yang sudah diedit akan tersimpan di bawah key file asli dan disajikan ke sesi berikutnya yang membuka PDF yang belum dimodifikasi. v2.770.140 memperbaiki identitas dan invalidasi sekaligus; memperbaiki hanya salah satunya akan mengirim cache yang mati atau yang bohong
Bagaimana HotPDF mengidentifikasi PDF tanpa membaca seluruh file
HotPDF mengidentifikasi PDF yang dimuat dari file lokal lewat fingerprint dari ukurannya, last-write time-nya, serta 64 KiB pertama dan terakhirnya, dan mengidentifikasi stream atau sumber random-access lewat SHA-256 atas seluruh kontennya. Keduanya ditangkap sekali, saat load sukses, dan 16 karakter hex pertama dari digest SHA-256 (64 bit) menjadi key dokumennya
| Sumber | Identitas | Biaya | Ditangkap kapan |
|---|---|---|---|
LoadFromFile | Size + LastWriteTime + 64 KiB awal dan akhir, di-hash dengan SHA-256 | Paling banyak baca 128 KiB, independen dari ukuran file | Setiap load yang sukses, meski RenderCacheFolder diset belakangan |
LoadFromStream | SHA-256 atas seluruh stream | Satu pass penuh atas sumber | Hanya jika RenderCacheFolder diset sebelum load |
LoadFromRandomAccessSource | SHA-256 atas seluruh sumber | Satu pass penuh atas sumber | Hanya jika folder diset lebih dulu dan seluruh range tersedia |
Sumber apa pun dengan entri /Encrypt | Tidak ada | Tidak ada | Tak pernah; tier disk dilewati |
Fingerprint file adalah trade-off yang disengaja. Meng-hash arsip hasil scan 400 MB secara penuh di setiap pembukaan bisa lebih mahal daripada merender dua halaman yang benar-benar dilihat pengguna. Region yang disampelkan tak arbitrary: header berada di awal file, dan trailer serta section cross-reference terakhir berada di ujungnya (ISO 32000-1 §7.5). Incremental update menambahkan body baru, section cross-reference, dan trailer baru (§7.5.6), jadi ia mengubah ukuran dan ekor sekaligus. Rewrite penuh oleh tool normal mana pun mengubah last-write time. Untuk file sampai 128 KiB, kedua sampel menutupi setiap byte, jadi dokumen kecil efektifnya ter-hash penuh
Risiko residunya adalah perubahan di tempat berukuran sama di tengah file besar yang writernya lalu mengembalikan timestamp asli. Itu butuh tool yang sengaja melestarikan modification time sambil mengedit konten, jarang tapi tak mustahil, dan dalam kasus itu cache menyajikan halaman basi. Sisi baiknya: menyalin file di Windows biasanya melestarikan last-write time, jadi salinan dokumen yang sudah di cache meng-hit entri yang sama, dan itu benar karena byte-nya identik
Stream tak punya modification time sama sekali, jadi satu-satunya identitas yang jujur adalah kontennya. HotPDF hanya membayar pass SHA-256 penuh itu ketika Anda meminta disk cache sebelum load; caller LoadFromStream lainnya tak melihat biaya ekstra. Itu membuat urutan assignment property menjadi load-bearing:
procedure OpenDownloadedPdf(Pdf: THotPDF; Data: TStream;
const CacheRoot: string);
begin
// Urutan salah untuk stream: hash konten hanya dihitung ketika
// foldernya sudah diset, jadi dokumen ini akan melewati disk tier
// Pdf.LoadFromStream(Data);
// Pdf.RenderCacheFolder := CacheRoot;
Pdf.RenderCacheFolder := CacheRoot; // set lebih dulu
Data.Position := 0;
if Pdf.LoadFromStream(Data) <= 0 then
raise Exception.Create('The stream is not a loadable PDF');
end;
Sumber random-access yang masih mengunduh (beberapa range belum tersedia) tak mendapat identitas alih-alih hash atas konten parsial, dan kalau penghitungan identitas gagal karena alasan apa pun, load-nya tetap sukses; dokumennya cukup dirender tanpa tier disk
Apa yang meng-invalidasi entri disk cache HotPDF?
Entri disk cache HotPDF tak pernah di-invalidasi dengan menghapusnya saat edit; sebagai gantinya, mengedit dokumen yang dimuat membuang identitas dokumen itu, sehingga tier disk dilewati untuk sisa load tersebut dan halaman tersimpan tetap valid untuk sumber yang belum dimodifikasi. Entri meninggalkan disk hanya lewat limit LRU dan byte, PNG korup, atau perubahan schema
Key itu mendeskripsikan sumber di disk, bukan object graph di memori. Begitu Anda men-stamp sebuah halaman atau mengubah annotation, dokumen tak lagi cocok dengan sumber itu, jadi membaca maupun menulis di bawah key-nya sama-sama tak benar. Sejak v2.770.140, invalidasi level dokumen maupun level halaman mengosongkan identitas alih-alih menyentuh folder, dan ada pengaman kedua untuk edit yang tak memanggil InvalidateRenderedPageCache: sebelum memakai tier disk, THotPDF mengecek apakah ada objek yang dimuat berstatus dirty dan memperlakukan dokumen dirty sebagai tanpa identitas
Setting render bekerja sebaliknya. Mengganti PageRenderBackend (atau memanggil UseNativeGDIRenderBackend), serta memanggil ConfigureRenderICCWorkflow atau ClearRenderICCWorkflow, menyiram halaman di memori tapi menjaga identitas, karena dokumen masih cocok dengan sumbernya. Setting-setting itu mengubah piksel tanpa menjadi bagian varian di memori, jadi key disk melipat masuk nama backend, flag black-point compensation, dan digest SHA-256 dari profil ICC proof dan output. Varian itu sendiri sudah mencakup color intent, output dithering, overprint preview, luminosity mask mode, fallback policy, dan visibilitas setiap optional content group, jadi men-toggle layer merender ke folder berbeda alih-alih menimpa view default
Untuk mengembalikan dokumen hasil edit ke tier disk, berikan identitas sumber baru dengan menyimpannya lalu memuat hasilnya:
procedure CommitEditsAndRekey(Pdf: THotPDF; const EditedFile: string);
begin
// Setelah mengedit dokumen yang dimuat: refresh halaman di memori.
// Identitas sumber sudah hilang, jadi tak ada yang dibaca dari atau
// ditulis ke folder disk dokumen aslinya
Pdf.InvalidateRenderedPageCache;
// File tersimpan punya ukuran dan last-write time baru, karenanya
// identitas baru; render setelah load ini di-cache di bawah key baru
Pdf.SaveLoadedDocument(EditedFile);
if Pdf.LoadFromFile(EditedFile) <= 0 then
raise Exception.Create('Could not reload the edited document');
end;
Folder dokumen aslinya dibiarkan dan menua lewat RenderCacheMaxDocuments dan RenderCacheMaxBytes seperti entri lainnya. Kalau pengguna membuka ulang asli yang belum diedit, halaman-halamannya masih di sana
Batas keamanan: sumber terenkripsi dan folder terhubung
Disk render cache HotPDF menolak dua jenis input dengan sengaja: ia tak pernah menulis halaman PDF terenkripsi ke disk, dan tak pernah mengikuti subfolder dokumen yang berupa junction atau reparse point lain. Kedua aturan itu menukar cache hit dengan tak bocornya data dan tak terhapusnya file yang salah
PDF terenkripsi tak pernah di-cache di disk
Halaman hasil render adalah konten yang sudah di-decrypt. Menulisnya sebagai PNG polos ke folder cache akan menyisakan salinan yang bisa dibaca dari dokumen terlindungi kata sandi di disk, di luar perlindungan yang dipilih penulisnya (ISO 32000-1 §7.6). HotPDF karenanya tak menangkap identitas untuk sumber apa pun yang trailernya membawa entri /Encrypt, termasuk file yang dibuka dengan password atau dengan user password kosong. Dokumen-dokumen itu tetap memakai tier memori, yang mati bersama prosesnya
Subfolder junction ditolak sejak v2.770.173
Root cache adalah pilihan Anda, dan mengarahkannya ke junction diizinkan. Subfolder dokumen di bawahnya urusan berbeda: cache menciptakan, membaca, menyentuh, dan menghapusnya sendiri, selama recovery saat startup (yang menghapus file sementara tersisa), lookup (yang memperbarui timestamp), store, invalidasi, dan ketiga limit eviction. Kalau seseorang dengan akses tulis ke root cache mengganti folder dokumen dengan junction ke direktori lain, setiap jalur itu akan mengikutinya, dan eviction akan menghapus file di tempat yang tak pernah dimiliki cache. Sejak v2.770.173 tiap entry point itu mengecek atribut reparse-point dan melewatkan folder dokumen yang terhubung: lookup menghitung miss, store menghitung kegagalan tulis, dan eviction membiarkannya
Path Unicode dan root bersama
Dua perbaikan terkait penting kalau Anda deploy ke profil pengguna. Sebelum v2.770.135, RenderCacheFolder adalah AnsiString, sehingga folder di luar system code page (nama pengguna Tionghoa di instalasi Windows English misalnya) terkonversi secara lossy sebelum cache melihatnya; property itu kini string Unicode, dan atomic replace-nya memakai Windows API versi wide. Sejak v2.770.52, beberapa instance THotPDF dalam satu proses yang menunjuk root yang sama (setelah ekspansi path, dibandingkan case-insensitive) berbagi satu index dan lock yang reference-counted. Sebelumnya tiap instance menimpa index.txt dengan salinannya sendiri dan menegakkan limit terhadap view parsialnya, sehingga folder bisa tumbuh beberapa kali melewati anggarannya
Pembagian itu berhenti di batas proses. Dua proses terpisah di root yang sama tetap memegang index di memori yang terpisah, jadi berikan tiap aplikasi yang jalan bersamaan root cache miliknya sendiri. Viewer yang merender di worker thread aman dalam satu proses: PrefetchLoadedPages dan antrean yang dibahas di rendering latar belakang dengan request queue sama-sama lewat jalur cache yang sama dan lock yang sama
Referensi cepat: checklist RenderCacheFolder
- Set
RenderCacheFolder,RenderCacheMaxDocuments, danRenderCacheMaxBytessebelum panggilan pertama keRenderLoadedPageToBitmapCached; untuk load stream dan random-access, set foldernya sebelum load - Upgrade ke v2.770.140 atau lebih baru jika Anda mengandalkan tier disk; versi lebih lama menerima property itu tapi tak pernah menyajikan satu halaman pun dari disk untuk load normal
- Jangan berharap disk cache untuk PDF terenkripsi, untuk dokumen yang diedit setelah load, atau selama
RenderFallbackPolicybukanrfpIgnore - Free instance THotPDF secara normal; sejak v2.770.140 tidak ada
FreemaupunInvalidateRenderedPageCacheyang menghapus entri disk - Mengganti
PageRenderBackendatau ICC workflow menjaga dokumen di tier disk di bawah key yang berbeda - Pakai satu root cache per aplikasi yang berjalan; instance di dalam satu proses berbagi index sejak v2.770.52
- Simpan root cache di lokasi per pengguna; subfolder dokumen yang berupa junction dilewati sejak v2.770.173
Page cache persisten paling terasa di viewer yang membuka ulang dokumen yang sama sepanjang hari, yang persis bentuk arsitektur PDF viewer custom di Delphi yang dibahas di tempat lain di blog ini. RenderCacheFolder, raster cache di memori, dan page renderer dikirim bersama komponen PDF Delphi HotPDF untuk Delphi dan C++Builder