Teknik Makale

Delphi'de PDFium Component ile PDF Görüntüleyici Oluşturma

Delphi'de bir PDF görüntüleyici (viewer) iki bileşene (component) ve aralarındaki bağlantıya indirgenir. TPdf belgeye sahiptir: dosyayı açar, şifresini çözer, sayfa sayısı ve meta veri (metadata) ile ilgili soruları yanıtlar. TPdfView, sayfaları ekranda boyayan ve kaydırma (scrolling), yakınlaştırma (zoom) ve kullanıcının şu anda baktığı sayfayı işleyen görsel denetimdir (visual control). PDFium Component, Chrome'un içinde gönderilen aynı oluşturma (rendering) motorunu sarar, böylece tuval (canvas) üzerinde elde ettiğiniz glifler, kenar yumuşatma (anti-aliasing) ve renk, kullanıcılarınızın tarayıcılarında zaten gördükleriyle eşleşir. İş oluşturmada değildir. Belge nesnesini görünüme (view) bağlamak, hasarlı veya parola korumalı bir dosyada çökmeden (crashing) yükleme yapmak ve kullanıcıya bir görüntüleyiciyi tamamlanmış hissettiren bir avuç denetimi vermektedir: sayfayı çevir, yakınlaştırmayı değiştir, sayfayı pencereye sığdır

Bu, aslında onu oluşturduğunuz sırayla bu birleştirmeyi (assembly) adım adım anlatır. Buradaki her şey aynı anda tek bir sayfayı oluşturur ki çoğu belge iş akışının (workflow) istediği şey budur. Sayfaların sürekli kayan tek bir sütunda yığılmasına ihtiyacınız varsa, bu farklı bir düzen (layout) kararıdır ve buradaki yol değildir

TPdf'yi TPdfView'a Bağlama

Forma bir TPdf ve bir TPdfView bırakın, ardından görünüme hangi belgeyi görüntüleyeceğini söyleyin. Bu tek atama, görsel olmayan belge ile onu boyayan denetim arasındaki bağlantının tamamıdır

TPdf'nin belgeye sahip olduğu, TPdfView'un onu boyadığı ve tek bir özellik atamasının PDFium DLL'si üzerinde ikisini bağladığı Delphi PDF görüntüleyici mimarisi
TPdf belgenin sahibiyken TPdfView onu boyar ve tek bir atama, ikisini paylaşılan PDFium motoru üzerinden bağlar
procedure TFormMain.FormCreate(Sender: TObject);
begin
  // Pdf ve PdfView tasarım aşamasında yerleştirildi.
  PdfView.Pdf := Pdf;                 // görünüm, bu belgenin içerdiğini çizer
  PdfView.FitMode := pfmFitWidth;     // kullanıcıyı makul bir yakınlaştırmayla başlat
end;

Bunların hiçbiri çalışmadan önce, PDFium yerel kütüphanesinin (native library) makinede olması gerekir. PDFium Component, hedef platformunuza bağlı olarak pdfium32.dll veya pdfium64.dll dosyasını çağırır ve DLL bulunamazsa belge basitçe açılmayı reddeder. Eşleşen DLL'yi çalıştırılabilir (executable) dosyanızın yanında gönderin veya sistem yükleyicisinin bulabileceği bir yere yerleştirin. V8 özellikli derlemeler (builds) yalnızca yürütmek istediğiniz JavaScript'i taşıyan PDF'ler için vardır, ki sade bir görüntüleyici bunu yapmaz, bu yüzden yapmamak için somut bir nedeniniz yoksa standart DLL'ye ulaşın

Girişe güvenmeden bir belgeyi yükleme

İçgüdü (instinct), yüklemeyi (load) bir try/except içine sarmak ve atılan (thrown) bir istisnayı başarısızlık olarak ele almaktır. Bu içgüdü burada yanlıştır ve bunu yanlış yapmak, birisi ona bozuk bir dosya verene kadar iyi görünen bir görüntüleyici üretir. Active := True ayarı, bir yükleme hatasında artış (raise) göstermez. PDFium Component dahili (internal) hatayı yakalar ve Active durumunu False'da bırakır, bu nedenle belgenin açılıp açılmadığını bilmenin tek dürüst yolu, ayarladıktan sonra özelliği geri okumaktır

Bir Delphi PDFium görüntüleyici için yükleme karar akışı: Active ayarlamak asla istisna yükseltmez; sessiz bir false yanlış parola veya hasarlı bir dosya anlamına gelir ve bir parola yeniden denemesi izler
Etkinleştirme başarısızlıkta asla yükseltmez; bu yüzden görüntüleyici Active'i geri okur ve sessiz bir false'u tek bir parola yeniden denemesiyle yanıtlar
procedure TFormMain.OpenDocument(const FileName: string);
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;                 // asla istisna üretmez; hata durumunda Active = False kalır
  if not Pdf.Active then
  begin
    ShowMessage('Could not open ' + FileName);
    Exit;
  end;
  PdfView.PageNumber := 1;            // görünüm kendi geçerli sayfasını takip eder
  UpdatePageLabel;
end;

İki şey dikkati hak ediyor. Birincisi, PageNumber'ın her iki nesnede de var olması ve ikisinin birbirinden bağımsız olmasıdır. Pdf.PageNumber belgenin geçerli bir sayfa kavramıdır; PdfView.PageNumber denetimin gerçekte görüntülediği sayfadır ve kullanıcıyı dosya içinde taşımak için ayarladığınız sayfadır. Birini ayarlamak diğerini taşımaz, bu nedenle bir görüntüleyici (viewer) her zaman görünümün (view) özelliğini yönlendirir. İkincisi, 1 tabanlı indekslemedir: sayfalar 0'dan değil, 1'den Pdf.PageCount'a kadar uzanır, bu da sıfır tabanlı dizilere (arrays) alışkın olan herkesi yakalar

Şifrelenmiş bir dosyayı işleme

Şifrelenmiş belgeler aynı yükleme yoluna katlanır (fold). Açık (open) parola etkinleştirmeden önce ayarlanırsa, belge açılırken şifresi çözülür; eğer yanlışsa veya eksikse, tıpkı bozuk bir dosya için olduğu gibi Active tam olarak False kalır. Bu nedenle kurtarma, bir parola sormak ve etkinleştirmeyi tekrar denemektir

procedure TFormMain.OpenWithPassword(const FileName: string);
var
  Password: string;
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;
  if not Pdf.Active then
  begin
    if InputQuery('Password required', 'Password:', Password) then
    begin
      Pdf.Password := Password;       // Active := True'dan önce ayarlanmalıdır
      Pdf.Active := True;
    end;
    if not Pdf.Active then
    begin
      ShowMessage('Unable to open the document.');
      Exit;
    end;
  end;
  PdfView.PageNumber := 1;
end;

Başarısızlık hem kötü bir parola hem de hasarlı bir dosya için sessiz olduğundan, ikisini yalnızca Active'den ayıramazsınız. Pratikte bu bir görüntüleyici için kabul edilebilirdir: kullanıcı ya doğru parolayı sağlar ya da dosyanın açılmayacağını öğrenir ve mesaj her iki durumda da aynı şekilde okunur

Belge boyunca sayfalama

Belge açıkken gezinme (navigation), Pdf.PageCount ile sınırlanan PdfView.PageNumber üzerinde aritmetiktir. Tek gerçek iş kenetlemedir (clamping), böylece düğmeler (buttons) sayfayı asla menzilin dışına itmez ve ilk ve son düğmeler dosyanın sonlarında devre dışı (disabled) kalır

procedure TFormMain.GoToPage(NewPage: Integer);
begin
  if not Pdf.Active then
    Exit;
  if NewPage < 1 then
    NewPage := 1
  else if NewPage > Pdf.PageCount then
    NewPage := Pdf.PageCount;
  PdfView.PageNumber := NewPage;
  UpdatePageLabel;
end;

// dört gezinme düğmesi her biri tek bir çağrıya indirgenir
procedure TFormMain.FirstClick(Sender: TObject);  begin GoToPage(1); end;
procedure TFormMain.PrevClick(Sender: TObject);   begin GoToPage(PdfView.PageNumber - 1); end;
procedure TFormMain.NextClick(Sender: TObject);   begin GoToPage(PdfView.PageNumber + 1); end;
procedure TFormMain.LastClick(Sender: TObject);   begin GoToPage(Pdf.PageCount); end;

Bir "sayfa N'ye git" metin kutusu, ayrıştırılmış (parsed) bir tamsayıdan (integer) beslenen aynı GoToPage çağrısıdır ve kelepçe (clamp), kullanıcının on sayfalık bir dosyaya 9999 yazdığı durumu kapsar. Göstergenin görünümün (view) gösterdiğiyle hiçbir zaman senkronizasyon dışına kaymaması için UpdatePageLabel'ı "Sayfa 3 / 12" yazan tek yer olarak tutun

Yakınlaştırma (Zoom): açık yüzdeler ve sığdırma modları

TPdfView üzerindeki yakınlaştırma, etkileşime giren iki çeşitte gelir ve etkileşimi anlamak, uslu duran bir yakınlaştırma denetimi ile kullanıcıyla savaşan biri arasındaki farktır. Doğrudan yol, 100'ün gerçek boyut (actual size) anlamına geldiği bir yüzde olan Zoom özelliğidir. Diğer yol, görünüme yakınlaştırmayı sizin için hesaplamasını ve pencere yeniden boyutlandırıldıkça (resizes) hesaplamaya devam etmesini söyleyen FitMode'dur

Bir PDFium Delphi görüntüleyicide Zoom ve FitMode etkileşimi: kesin bir Zoom atamak FitMode'u pfmNone'a temizler ve bir sığdırma modu seçmek yakınlaştırmayı görünüme geri verir
Kesin bir yakınlaştırma atamak sığdırma kipini temizler; bir sığdırma kipi seçmek ise yakınlaştırma hesabını görünüme geri verir
// fixed magnifications
PdfView.Zoom := 100;     // actual size
PdfView.Zoom := 50;      // half
PdfView.Zoom := 200;     // double

// görünümün sayfayı pencereye göre boyutlandırmasına izin ver ve yeniden boyutlandırmada bu boyutu koru
PdfView.FitMode := pfmFitWidth;   // sayfa genişliği denetimi doldurur
PdfView.FitMode := pfmFitPage;    // sayfanın tamamı görünür
PdfView.FitMode := pfmActualSize; // belgenin noktalarıyla 1:1

İşte insanları çelmeleyen kısım burası. Zoom ataması, FitMode'u doğrudan pfmNone'a sıfırlar (resets). Bu doğru bir davranıştır, bir hata değildir: kullanıcının tam bir %150 seçtiği an, görünüm artık "genişliğe sığdır" (fit to width) seçeneğini de onurlandıramaz, çünkü iki istek çatışır. Kullanıcı arayüzünüz (UI) için bunun sonucu, bir yakınlaştırma (zoom-in) düğmesi ve bir sayfaya sığdır (fit-to-page) düğmesinin birbirini dışlayan durumlar olmasıdır ve araç çubuğu (toolbar) etkin modu görünür kılmalıdır. Kullanıcı sayfaya sığdır'ı tıkladığında FitMode'u ayarlayın; sayısal bir yakınlaştırmaya tıkladıklarında Zoom'u ayarlayın ve sığdırma modunu kendi başına temizlemesine izin verin

Belki de mevcut sığdırma yüzdesiyle bir yakınlaştırma kaydırıcısını (zoom slider) tohumlamak için (seed), sığdırma (fit) değerini kendiniz hesaplamayı tercih ederseniz, sayfa başına yardımcılar (helpers) modu değiştirmeden size sayıları verir. PageWidthZoom[N], PageZoom[N] ve ActualSizeZoom[N] N sayfasını genişliğe sığdıracak, bütünüyle sığdıracak veya gerçek boyutta oluşturacak (render) yüzdeyi döndürür

// geçerli sayfanın genişliğe sığdırma değerinden bir yakınlaştırma göstergesi başlat
var
  FitPercent: Double;
begin
  FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
  ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;

Tamamlanmış bir görüntüleyici aslında neye ihtiyaç duyar?

Yukarıdaki görüntüleyici (viewer) birkaç düzine satırdır ve bir belge iş akışının (workflow) ihtiyaç duyduğu işi zaten yapar: bir dosya açmak, kötü bir tanesinden kurtulmak, bir sayfa göstermek, sayfalar arasında gezinmek ve büyütmeyi (magnification) elle veya sığdırarak değiştirmek. PDFium zor kısımları sessizce yapar. Gömülü (embedded) yazı tipleri çözülür, ek açıklamalar (annotations) ve form alanları (fields) belgenin onları yerleştirdiği yere boyanır ve gördüğünüz sayfa bir Chrome kullanıcısının göreceği sayfayla eşleşir, çünkü her ikisini de çizen aynı motordur

Bu temelden (base) itibaren eklemeler yapısal olmaktan ziyade artımlıdır (incremental). Metin seçimi ve arama, PDFium'un zaten oluşturduğu aynı metin katmanından okur; Pdf.Title ve Pdf.Author gibi meta veriler (metadata) bir özellik okuma uzağındadır; döndürme ve gri tonlamalı (grayscale), bir sayfayı bir bit eşleme (bitmap) çizerken aktardığınız oluşturma (render) seçenekleridir. Bunların hiçbiri, belge nesnesi, görünüm (view) ve bunları birbirine bağlayan "yükle, ardından gezin" (load-then-navigate) akışı olan buradaki omurgayı (spine) değiştirmez. O omurgayı doğru yapın ve geri kalanı dekorasyondur

Baştan sona kullanılan TPdf ve TPdfView bileşenleri, ürün sayfasında tam görüntüleyici referansını (reference) taşıyan Delphi ve C++Builder için PDFium Component'in bir parçasıdır