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

procedure TFormMain.FormCreate(Sender: TObject);
begin
  // Pdf and PdfView were dropped at design time.
  PdfView.Pdf := Pdf;                 // the view paints whatever this document holds
  PdfView.FitMode := pfmFitWidth;     // start the user at a sensible zoom
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

procedure TFormMain.OpenDocument(const FileName: string);
begin
  Pdf.FileName := FileName;
  Pdf.Active := True;                 // never raises; failure leaves Active = False
  if not Pdf.Active then
  begin
    ShowMessage('Could not open ' + FileName);
    Exit;
  end;
  PdfView.PageNumber := 1;            // the view tracks its own current page
  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;       // must be set before Active := True
      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;

// the four navigation buttons reduce to one call each
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

// fixed magnifications
PdfView.Zoom := 100;     // actual size
PdfView.Zoom := 50;      // half
PdfView.Zoom := 200;     // double

// let the view size the page to the window, and keep it sized on resize
PdfView.FitMode := pfmFitWidth;   // page width fills the control
PdfView.FitMode := pfmFitPage;    // whole page visible
PdfView.FitMode := pfmActualSize; // 1:1 with the document's points

İş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

// seed a zoom readout from the fit-to-width value of the current page
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