Gömülü her PDF görüntüleyicinin peşinden iki şikayet gelir: Büyük belgelerin her başlatılışta sıfırdan yeniden işlenmesi ve pencere yüksek DPI'lı bir monitöre sürüklendiği anda metnin bulanıklaşması. losLab PDF Kütüphanesi, TPDFlibViewer kontrolünde her ikisine de yanıt verir: DiskCacheFolder özelliği, belge parmak iziyle anahtarlanan oturumlar arası bir disk sayfası önbelleğini etkinleştirir ve GetDpiForWindow ile bir WM_DPICHANGED işleyicisiyle desteklenen ScreenDPI özelliği, kontrole gerçek monitör başına DPI uyumluluğu kazandırır
Her iki sorun da ciddiye alınmaya değerdir çünkü kullanıcılar bunları hemen fark eder. Dün kaydırılabilir hale gelmesi sekiz saniye süren 400 sayfalık taranmış bir sözleşme, dosyadaki hiçbir şey değişmemesine rağmen bugün yine sekiz saniye sürer. Ve karma DPI'lı bir masaüstünde, birincil monitörün 96 DPI'ında işleme yapan bir görüntüleyici orada iyi görünürken yanındaki 4K panelde gözle görülür şekilde yumuşak (bulanık) görünür. Bu makale, TPDFlibViewer kontrolünün bunların her birini nasıl çözdüğünü ve daha da kullanışlısı, tasarımın neden buraya vardığını incelemektedir. Kontrolün kendisine yeniyseniz, önce etkileşimli PDF görüntüleyici kontrolü genel bakışıyla başlayın ve performans katmanı için buraya geri dönün
Delphi'de işlenmiş (rendered) PDF sayfalarını oturumlar arasında nasıl diskte önbelleğe alırsınız?
TPDFlibViewer.DiskCacheFolder özelliğine bir klasör atadığınızda, kontrol işlenmiş sayfa bit eşlemlerini diske kalıcı olarak yazar; böylece son görüntülenen bir belge yeniden işlenmek yerine anında açılır. Diskteki düzen <Folder>\PDFlibPas-PageCache\<belge parmak izi>\p<sayfa>_z<yakınlaştırma anahtarı>.bin şeklindedir: Belge başına bir alt klasör, sayfa ve yakınlaştırma kombinasyonu başına bir dosya. Geri kalan her şey otomatiktir; çağrılacak bir önbellek API'si ve temizlenecek hiçbir şey yoktur
uses
PDFlibViewer;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FViewer := TPDFlibViewer.Create(Self);
FViewer.Parent := Self;
FViewer.Align := alClient;
// Tek bir satır oturumlar arası sayfa önbelleğini açar
FViewer.DiskCacheFolder := 'C:\ProgramData\MyApp\PageCache';
FViewer.LoadFromFile('C:\Contracts\master-agreement.pdf', '');
end;
DiskCacheHits sayacı, disk önbelleğinin geçerli oturumda kaç sayfa işlemeyi kurtardığını bildirir; bu da kazanımı anekdot olmaktan ziyade ölçülebilir kılar. Kapattığınız ve yeniden açtığınız bir belgede, bir işleme geçişi olmadan görünüme kayan her sayfa bunu artırır
Önbellek anahtarı neden sürüm numarası değil de parmak izidir?
Belge parmak izi, yol|boyut|son yazılma zamanı dizgesi üzerinde 16'lık onaltılık (hex) basamak olarak biçimlendirilmiş 64 bitlik bir FNV-1a karmasıdır (hash). Bu üçlü, geçersiz kılma (invalidation) stratejisinin tamamıdır: Dosya düzenlendiğinde boyutu veya yazılma zamanı değişir, karma değişir ve görüntüleyici sadece farklı bir önbellek alt klasörü açar. Bayat klasöre bir daha asla danışılmaz ve sonunda LRU budamasına uğrar. Sürdürülecek bir sürüm etiketi protokolü, kayabilecek bir zaman damgası karşılaştırma mantığı ve eski bir bit eşlemin değiştirilmiş bir belgeye karşı sunulma yolu yoktur
Bu, derleme sistemlerinin kullandığı "önbellek anahtarı olarak içerik kimliği" fikrinin aynısıdır ve ortadan kaldırdığı başarısızlık modlarıyla değerini kanıtlar. Dosyaları belgenin düz adı altında saklayan bir önbellek, düzenlemeleri açıkça tespit etmek zorundadır ve her açık kontrol bir uç durum (edge case) bekleyen bir hatadır: Eski bir zaman damgasıyla yedekten geri yüklenen bir dosya, boyutu koruyan bir kaydetme, bir makinede büyük-küçük harf duyarlı karşılaştırılan ve diğerinde karşılaştırılmayan bir yol. Her üç sinyali de klasör adına hash'lemek, bayatlığı prosedürel olarak kontrol etmek yerine yapısal olarak imkansız hale verir
Döndürme (rotation) neden kasıtlı olarak disk anahtarının dışında bırakılmıştır?
Önbellek dosya adı sayfa numarasını ve yakınlaştırmayı kodlar, başka hiçbir şeyi değil; görünüm döndürme (rotation) açısı kasıtlı olarak yoktur. Disk önbelleği her zaman döndürülmemiş işleme baytlarını saklar; görünüm döndürülmüş durumdayken önbelleğe alınmış bir sayfa yüklendiğinde, piksel yönü kod çözüldükten sonra bellekte yeniden yönlendirilir. Döndürme, disk üzerindeki anahtarın bir parçası olsaydı, dört görünüm açısı arasında geçiş yapan bir kullanıcı aynı sayfayı diske dört kez yazarak, tam bir işleme geçişine kıyasla döndürme ucuz bir piksel karıştırması olduğundan, sıfır ekstra bilgi için önbellek büyümesini dört katına çıkarırdı
Bellek içi bit eşlem önbelleği, Round(Zoom * 1000) * 4 + Rotation div 90 şeklinde bileşik bir anahtar kullanır, çünkü orada döndürülmüş bit eşlem boyama işleyicisinin (paint handler) tam olarak ihtiyaç duyduğu şeydir. Arka plandaki önceden getirme (prefetch) iş parçacığı aynı disiplini takip eder: Her zaman döndürülmemiş sayfaları işler ve düz yakınlaştırma anahtarlarını bildirir ve ana iş parçacığı sonuçları boşaltırken döndürmeyi uygular; bu, paralel sayfa işleme ve iş parçacığı güvenliğinde tartışılan ana-iş-parçacığı-önbelleğe-sahiptir kuralının aynısıdır. Döndürmeyi bellek katmanının altındaki her şeyin dışında tutmak, depolamanın pahalı olduğu her yerde sayfa ve yakınlaştırma başına bir kurumsal (canonical) işleme anlamına gelir
LRU budaması, önbellek klasörünü nasıl sınırlı tutar?
Önbellek en fazla on belge klasörünü tutar ve yeni belgeler geldiğinde en az kullanılanları (LRU) budar. Güncellik, her belge klasöründeki zaman damgasına hem önbellek yazmalarında hem de önbellek okumalarında dokunulan (touch) bir last-used.marker dosyasıyla izlenir, böylece sadece yeniden okuduğunuz bir belge yeniden yazılmadan sıcak kalır. Şu anda açık olan belgeye ait klasör silinmekten muaftır, bu nedenle budama hiçbir zaman sayfaları aktif görünümün altından çekip alamaz
Önbellekteki her disk işlemi (parmak izi alma, klasör oluşturma, işaretçi dokunuşları, sayfa okuma ve yazmaları, budama) kendi istisnalarını (exceptions) yutar. Bu bilinçli bir duruştur, özensizlik değildir: Önbellek elinden gelenin en iyisini yapan bir hızlandırıcıdır ve dolu bir disk, salt okunur bir klasör veya bir antivirüs kilidi görüntüleyiciyi asla "sayfa görüntülenemedi" durumuna değil, "eskisi gibi işleme yapma" düzeyine düşürmelidir. Bir önbellek G/Ç hatasının üretebileceği en kötü sonuç, bir önbellek kaçırmasıdır (missed hit)
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// Disk önbelleği bu oturumda kaç işleme geçişi kurtardı?
Log(Format('Disk cache hits: %d', [FViewer.DiskCacheHits]));
end;
Delphi'de bir PDF görüntüleyiciyi DPI uyumlu (DPI-aware) nasıl yaparsınız?
TPDFlibViewer, DPI'ye bağımlı her hesaplamayı tek bir alan üzerinden yönlendirir ve bu tek değişiklik noktası tasarımı, monitör başına desteği izlenebilir kılan şeydir. Düzen, önceden getirme ve işleme kodlarının tümü tek bir FScreenDPI değerini okur; %100 yakınlaştırmada bir PDF noktası ScreenDPI / 72 pikselle eşleşir. Genel ScreenDPI özelliği ayarlayıcısı (setter) bit eşlem önbelleğini temizler, düzeni yeniden oluşturur, kaydırma aralıklarını günceller ve pencereyi geçersiz kılar, bu nedenle DPI değiştirmek, her tüketicinin otomatik olarak takip ettiği tek bir atamadır. Aynı piksel matematiği, bir monitör yerine bir yazıcı için işleme yaptığınızda da önemlidir; bu konu baskı önizleme ve cihaz bağlamı işleme altında ele alınmıştır
Algılama, kurucu (constructor) yerine bir CreateWnd geçersiz kılmasında (override) gerçekleşir, çünkü GetDpiForWindow geçerli bir pencere tanıtıcısına (window handle) ihtiyaç duyar ve kurucu bundan çok önce çalışır. CreateWnd tetiklenene kadar, düzen matematiğinin mantıklı kalması için kontrol eski varsayılan değer olan 96'yı tutar. GetDpiForWindow Windows 10 1607+ API'sidir, bu nedenle kontrol bunu GetProcAddress ile dinamik olarak bağlar ve eski sistemlerde GetDeviceCaps(LOGPIXELSY) işlevine geri döner; ünite her yerde yüklenmeye devam eder ve eski Windows sadece sistem DPI değerini bildirir
Pencere bir 4K monitöre taşındığında ne olur?
Monitör başına uyumlu (per-monitor-aware) bir pencere farklı bir ölçek faktörüne sahip bir monitöre geçtiğinde Windows WM_DPICHANGED gönderir ve TPDFlibViewer bunu doğrudan işler. İşleyici, wParam'ın düşük sözcüğünden (low word) yeni DPI'yi okur, bunu SetScreenDPI aracılığıyla atar (bu işlem artık yanlış olan bit eşlem önbelleğini düşürür ve sayfaları yeniden yerleştirir) ve ardından Windows'un lParam içinde SetWindowPos aracılığıyla geçirdiği önerilen pencere dikdörtgenini kabul eder, bu da geçiş boyunca mantıksal istemci boyutunu kararlı tutar. Kullanıcı, işletim sistemi tarafından genişletilmiş bir bit eşlem yerine belgenin yeni ölçekte net bir şekilde yeniden işlendiğini görür
// Normalde ScreenDPI'ye hiç dokunmazsınız: CreateWnd ana monitörü algılar
// ve WM_DPICHANGED geçişleri izler. Bunu yalnızca özel hedefler için geçersiz kılın,
// örn. düzeni %150'lik bir ekran içinmiş gibi işlemek:
FViewer.ScreenDPI := 144; // bit eşlem önbelleğini temizler ve düzeni yeniden oluşturur
Dağıtımdan önce bilinmesi gereken sınırlar
Tasarım incelemenizde üç pratik sınır yer almalıdır. İlk olarak, WM_DPICHANGED yalnızca süreç buna dahil olduğunda (opt in) gelir: Uygulama manifestonuz monitör başına DPI uyumluluğunu (güncel Windows'ta per-monitor v2) beyan etmelidir, bu da Delphi IDE'de Uygulama seçenekleri altındaki DPI uyumluluğu ayarıdır. Sistem DPI uyumlu bir süreç bu mesajı asla almaz ve kontrol daha sonra pencere oluşturulurken algıladığı DPI değerinde işlem yapar. İkinci olarak, disk önbelleği hız için disk alanından ödün verir; birkaç yakınlaştırma seviyesinde on belgelik sayfa bit eşlemi gerçek bir depolama alanıdır, bu nedenle DiskCacheFolder'ı dolaşım profiline (roaming profile) değil, büyümesinden rahatsız olmayacağınız bir konuma yönlendirin. Üçüncü olarak, önbellek klasörünün çalışan kullanıcı için yazma iznine ihtiyacı vardır; bu izin olmadan görüntüleyici sessizce sıfır hit ile çalışmaya devam eder; önbelleğin devreye girmediğinden şüpheleniyorsanız test sırasında DiskCacheHits değerini kontrol edin
İki özellik bir araya geldiğinde, belge ağırlıklı bir uygulamanın algılanan kalitesini çoğu işleme optimizasyonundan daha fazla değiştirir: Kullanıcılarınızın tekrar ziyaret ettiği belgeler anında açılır ve görünüm hangi monitöre düşerse düşsün keskin kalır. Her ikisi de losLab PDF Kütüphanesi içindeki TPDFlibViewer üzerinde sıradan özellikler olarak sunulur, bu nedenle bunları benimsemek bir işleme motoru projesinden ziyade bir öğleden sonra sürecek bir kablolama işidir