Teknik Makale

Delphi'de PDF Görüntüleyici Disk Önbelleği ve Monitör Başına DPI

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