Dua keluhan selalu menyertai setiap viewer PDF yang disematkan: dokumen besar harus merender ulang dari awal setiap kali dibuka, dan teks menjadi kabur saat jendela diseret ke monitor dengan DPI tinggi. losLab PDF Library menjawab kedua keluhan tersebut dalam kontrol TPDFlibViewer miliknya: properti DiskCacheFolder mengaktifkan cache halaman disk lintas sesi yang menggunakan kunci sidik jari dokumen, dan properti ScreenDPI, yang didukung oleh GetDpiForWindow dan handler WM_DPICHANGED, memberikan kontrol tersebut kesadaran DPI per-monitor yang sesungguhnya
Kedua masalah ini perlu ditangani dengan serius karena pengguna dapat langsung menyadarinya. Kontrak pindaian sebanyak 400 halaman yang membutuhkan waktu delapan detik untuk dapat digulirkan kemarin, membutuhkan delapan detik lagi hari ini, meskipun tidak ada perubahan dalam berkas tersebut. Dan pada desktop dengan DPI campuran, viewer yang merender pada DPI monitor utama sebesar 96 terlihat baik-baik saja di sana tetapi tampak buram di panel 4K di sebelahnya. Artikel ini membahas bagaimana TPDFlibViewer menyelesaikan masing-masing masalah tersebut dan, yang lebih berguna, mengapa desainnya dipilih demikian. Jika Anda baru mengenal kontrol ini, mulailah dengan ringkasan kontrol viewer PDF interaktif dan kembali lagi untuk mempelajari lapisan kinerjanya
Bagaimana cara menyimpan cache halaman PDF yang dirender lintas sesi di Delphi?
Tetapkan folder ke TPDFlibViewer.DiskCacheFolder dan kontrol akan menyimpan bitmap halaman yang dirender ke disk, sehingga dokumen yang baru saja dilihat akan terbuka secara instan alih-alih merender ulang. Struktur folder di disk adalah <Folder>\PDFlibPas-PageCache\<document fingerprint>\p<page>_z<zoomkey>.bin: satu subfolder per dokumen, satu berkas per kombinasi halaman dan zoom. Sisanya berjalan otomatis; tidak ada API cache yang perlu dipanggil dan tidak ada yang perlu dibersihkan
uses
PDFlibViewer;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FViewer := TPDFlibViewer.Create(Self);
FViewer.Parent := Self;
FViewer.Align := alClient;
// Satu baris untuk mengaktifkan cache halaman lintas sesi
FViewer.DiskCacheFolder := 'C:\ProgramData\MyApp\PageCache';
FViewer.LoadFromFile('C:\Contracts\master-agreement.pdf', '');
end;
Properti DiskCacheHits melaporkan berapa banyak proses rendering halaman yang berhasil dihemat oleh cache disk pada sesi saat ini, membuat keuntungan performa dapat diukur alih-alih sekadar desas-desus. Pada dokumen yang Anda tutup dan buka kembali, setiap halaman yang muncul di layar tanpa proses rendering akan menambah nilai properti ini
Mengapa kunci cache menggunakan sidik jari, bukan nomor versi
Sidik jari dokumen adalah hash FNV-1a 64-bit dari string path|size|last write time, diformat sebagai enam belas digit heksadesimal. Ketiga data tersebut merupakan strategi pembatalan cache (invalidation strategy) seutuhnya: ketika berkas diedit, ukuran atau waktu tulisnya berubah, hash berubah, dan viewer tinggal membuka subfolder cache yang berbeda. Folder yang usang tidak akan pernah diakses lagi dan pada akhirnya akan dihapus oleh pemangkasan LRU. Tidak ada protokol tag versi yang perlu dikelola, tidak ada logika perbandingan stempel waktu yang dapat menyimpang, dan tidak ada kemungkinan bitmap lama disajikan untuk dokumen yang telah dimodifikasi
Ini adalah gagasan "identitas konten sebagai kunci cache" yang sama seperti yang digunakan oleh sistem kompilasi (build system), dan sangat berguna karena menghilangkan kemungkinan mode kegagalan. Cache yang menyimpan berkas dengan nama biasa dokumen harus mendeteksi pengeditan secara eksplisit, dan setiap pemeriksaan eksplisit adalah bug yang rentan terhadap kasus tepi: berkas yang dipulihkan dari cadangan dengan stempel waktu lama, penyimpanan dokumen yang mempertahankan ukuran yang sama, atau jalur berkas yang dibandingkan secara sensitif huruf (case-sensitively) di satu mesin tetapi tidak di mesin lainnya. Melakukan hash pada ketiga sinyal tersebut menjadi nama folder membuat keusangan menjadi tidak mungkin terjadi secara struktural, alih-alih diperiksa secara prosedural
Mengapa rotasi sengaja diabaikan dari kunci disk
Nama berkas cache mengodekan nomor halaman dan zoom, dan tidak ada yang lain — sudut rotasi tampilan sengaja ditiadakan. Cache disk selalu menyimpan byte rendering yang tidak diputar; ketika halaman yang di-cache dimuat saat tampilan diputar, piksel-piksel tersebut akan diorientasikan ulang dalam memori setelah didekode. Jika rotasi menjadi bagian dari kunci disk, pengguna yang menelusuri empat sudut tampilan akan menulis halaman yang sama ke disk sebanyak empat kali, yang akan melipatgandakan pertumbuhan cache untuk nol informasi tambahan, karena rotasi hanyalah pengacakan piksel yang murah dibandingkan dengan proses rendering penuh
Cache bitmap dalam memori menggunakan kunci komposit, Round(Zoom * 1000) * 4 + Rotation div 90, karena di sana bitmap yang diputar adalah apa yang dibutuhkan oleh handler pengecatan. Utas prefetch latar belakang mengikuti aturan yang sama: utas tersebut selalu merender halaman yang tidak diputar dan melaporkan kunci zoom biasa, dan utas utama menerapkan rotasi saat mengambil hasilnya — aturan utas utama memiliki cache (main-thread-owns-the-cache) yang sama seperti yang dibahas dalam rendering halaman paralel dan keamanan utas. Menjaga agar rotasi tetap berada di luar apa pun di bawah lapisan memori berarti satu rendering kanonis per halaman dan zoom, di mana pun penyimpanan itu mahal dilakukan
Bagaimana pemangkasan LRU menjaga agar folder cache tetap terbatas?
Cache mempertahankan hingga sepuluh folder dokumen dan memangkas folder yang paling jarang digunakan (least recently used) saat dokumen baru tiba. Kebaruan dilacak oleh berkas last-used.marker di setiap folder dokumen yang stempel waktunya diperbarui baik saat menulis cache maupun membaca cache, sehingga dokumen yang hanya Anda baca ulang tetap hangat tanpa perlu ditulis ulang. Folder milik dokumen yang sedang terbuka dikecualikan dari penghapusan, sehingga pemangkasan tidak akan pernah menghapus halaman dari bawah tampilan aktif
Setiap operasi disk dalam cache — pembuatan sidik jari, pembuatan folder, pembaruan penanda, pembacaan dan penulisan halaman, pemangkasan — menelan pengecualiannya sendiri. Ini adalah sikap yang sengaja diambil, bukan kecerobohan: cache adalah akselerator dengan upaya terbaik, dan disk yang penuh, folder baca-saja, atau penguncian antivirus hanya boleh menurunkan performa viewer menjadi "merender seperti sebelumnya", tidak boleh "gagal menampilkan halaman". Hasil terburuk yang dapat dihasilkan oleh kesalahan I/O cache hanyalah kegagalan cache (missed hit)
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// Berapa banyak proses render yang dihemat oleh cache disk sesi ini?
Log(Format('Disk cache hits: %d', [FViewer.DiskCacheHits]));
end;
Bagaimana cara membuat viewer PDF sadar DPI (DPI-aware) di Delphi?
TPDFlibViewer merutekan setiap kalkulasi yang bergantung pada DPI melalui satu bidang saja, dan desain titik perubahan tunggal (single-change-point) inilah yang membuat dukungan per-monitor dapat dikelola. Kode tata letak, prefetch, dan render semuanya membaca satu nilai FScreenDPI value; pada zoom 100%, satu titik PDF memetakan ke piksel sebesar ScreenDPI / 72. Setter properti publik ScreenDPI membersihkan cache bitmap, membangun ulang tata letak, memperbarui rentang gulir, dan membatalkan validasi jendela, sehingga mengubah DPI hanyalah satu tugas dengan setiap konsumen mengikuti secara otomatis. Matematika piksel yang sama penting saat Anda merender untuk printer alih-alih monitor, topik yang dibahas dalam rendering pratinjau cetak dan konteks perangkat
Deteksi terjadi dalam override CreateWnd alih-alih konstruktor, karena GetDpiForWindow memerlukan handel jendela yang valid dan konstruktor berjalan jauh sebelum handel jendela tersebut ada. Hingga CreateWnd dipicu, kontrol menahan nilai default bawaan 96 agar matematika tata letak tetap waras. GetDpiForWindow sendiri adalah API Windows 10 1607+, sehingga kontrol mengikatnya secara dinamis dengan GetProcAddress dan beralih ke GetDeviceCaps(LOGPIXELSY) pada sistem yang lebih lama — unit tetap dapat dimuat di mana saja, dan Windows yang lebih lama hanya akan melaporkan DPI sistem
Apa yang terjadi saat jendela berpindah ke monitor 4K?
Windows mengirimkan WM_DPICHANGED ketika jendela yang sadar per-monitor berpindah ke monitor dengan faktor skala yang berbeda, dan TPDFlibViewer menangani pesan tersebut secara langsung. Handler membaca DPI baru dari low word dari wParam, menetapkannya melalui SetScreenDPI — yang membuang cache bitmap yang sekarang salah dan menata ulang halaman — kemudian menerima saran persegi panjang jendela yang dilewatkan Windows dalam lParam melalui SetWindowPos, yang menjaga ukuran klien logis tetap stabil selama perpindahan. Pengguna melihat dokumen dirender ulang dengan tajam pada skala baru alih-alih melihat bitmap yang diregangkan oleh OS
// Biasanya Anda tidak perlu menyentuh ScreenDPI: CreateWnd mendeteksi monitor host
// dan WM_DPICHANGED melacak gerakan. Lakukan override hanya untuk target khusus,
// misal merender tata letak seolah-olah untuk tampilan 150% display:
FViewer.ScreenDPI := 144; // membersihkan cache bitmap dan menata ulang tata letak
Batasan yang perlu diketahui sebelum Anda mendistribusikannya
Tiga batasan praktis layak masuk dalam tinjauan desain Anda. Pertama, WM_DPICHANGED hanya tiba jika proses memilih untuk ikut serta (opt in): manifes aplikasi Anda harus menyatakan kesadaran DPI per-monitor (per-monitor v2 pada Windows saat ini), yang dalam Delphi IDE adalah pengaturan kesadaran DPI di bawah opsi Aplikasi. Proses yang hanya sadar DPI sistem tidak akan pernah menerima pesan tersebut, dan kontrol kemudian merender pada DPI yang dideteksinya saat jendela dibuat. Kedua, cache disk menukar ruang disk untuk kecepatan — bitmap halaman untuk sepuluh dokumen di beberapa tingkat zoom membutuhkan penyimpanan nyata, jadi arahkan DiskCacheFolder ke lokasi yang nyaman bagi Anda untuk menampung pertumbuhan data, bukan ke profil roaming. Ketiga, folder cache memerlukan izin tulis bagi pengguna yang menjalankan aplikasi; tanpanya viewer tetap berfungsi, secara diam-diam, dengan nol hit cache — periksa DiskCacheHits saat pengujian jika Anda mencurigai cache tidak berfungsi
Secara bersama-sama, kedua fitur ini mengubah persepsi kualitas aplikasi yang kaya dokumen lebih baik daripada sebagian besar optimasi rendering: dokumen yang dikunjungi kembali oleh pengguna terbuka secara instan, dan tampilannya tetap tajam di monitor mana pun ia berada. Keduanya disertakan sebagai properti biasa pada TPDFlibViewer di losLab PDF Library, sehingga mengadopsinya hanyalah pekerjaan menghubungkan properti dalam satu siang alih-alih proyek mesin rendering