Teknik Makale

Delphi'de PDFium Component ile Yan Yana PDF Karşılaştırması

İki belgenin aynı anda açık olması, aynı sayfa numarasında olması ve her birinin kendi kaydırılabilir panelinde yer alması: bir karşılaştırma görüntüleyicisinin özü budur. PDFium Component, dosyaya TPdf'nin ve görünüme TPdfView'in sahip olduğu anlaşılır bir nesne modeli aracılığıyla bunu sağlar. Bir belge, bir TPdf, bir TPdfView. Üç panel isterseniz, üç çiftiniz olur. İşin zor kısımları API çağrıları değildir; pencere yeniden boyutlandırıldığındaki düzen aritmetiği ve hangi görünümün hangisini izleyeceğine karar verdiğinizdeki sayfa senkronizasyonu mantığıdır

Form Düzeni

VCL formu, her birinin içinde bir TPdfView bulunan ve kutuyu doldurması için alClient olarak hizalanmış, yan yana üç TScrollBox kapsayıcısına ev sahipliği yapar. Kullanıcının çalışma zamanında sütun genişliklerini ayarlayabilmesi için kutuların arasında iki TSplitter bileşeni bulunur. Panellerin üzerindeki bir araç çubuğu; açma düğmelerini, yakınlaştırma denetimlerini ve iki görünümlü / üç görünümlü geçişini taşır

Üç görünüm modu, formun dahili olarak izlediği bir boolean'dır (mantıksal değer). Değiştiğinde genişlikleri yeniden hesaplar ve üçüncü sütunu gösterir veya gizlersiniz. En basit yaklaşım, tüm Align özelliklerini temizlemek, bölücüleri (splitter) gizlemek ve ardından mutlak konumları ayarlamaktır:

procedure TFormMain.UpdateLayout;
var
  TotalWidth: Integer;
begin
  TotalWidth := ClientWidth;

  if ThreeViewMode then
  begin
    ScrollBox3.Visible := True;
    ScrollBox1.Left   := 0;
    ScrollBox1.Width  := TotalWidth div 3;
    ScrollBox2.Left   := ScrollBox1.Width;
    ScrollBox2.Width  := TotalWidth div 3;
    ScrollBox3.Left   := ScrollBox2.Left + ScrollBox2.Width;
    ScrollBox3.Width  := TotalWidth - ScrollBox3.Left;
    // Apply the same (ClientHeight - toolbar height) to all three Height values
  end
  else
  begin
    ScrollBox3.Visible := False;
    ScrollBox1.Left   := 0;
    ScrollBox1.Width  := TotalWidth div 2;
    ScrollBox2.Left   := ScrollBox1.Width;
    ScrollBox2.Width  := TotalWidth - ScrollBox2.Left;
  end;
end;

Tamsayı aritmetiğinden önce her üç kutuda da Align := alNone ayarının yapılması, VCL kısıtlama motorunun (constraint engine) atamalarınızla çakışmasını önler. İki görünüm modunda sürükle-yeniden boyutlandır (drag-to-resize) işlevini istiyorsanız, konumlandırmadan sonra bölücü görünürlüğünü geri yükleyin

Her kaydırma kutusunun (scroll box) yüksekliği, istemci alanından (client area) araç çubuğu paneli yüksekliğinin çıkarılmasıyla bulunur. Araç çubuğu üstte alTop ile sabitlendiğinden, ClientHeight - PanelButtons.Height size kullanılabilir dikey alanı verir. Bunu aynı UpdateLayout çağrısı içindeki üç kutuya da atayın, böylece hiçbir zaman bir kutunun diğerlerinden daha uzun olduğu ve düzen titreşimine (flicker) neden olduğu bir kare oluşmaz

Bir Belgeyi Açmak

Her panel çiftinin kendi açma prosedürüne ihtiyacı vardır. Kalıp kısadır: bileşeni devre dışı bırakın, dosya adını ayarlayın, etkinleştirmeye çalışın, dosya parola gerektiriyorsa EPdfError hatasını yakalayın. İşlemeyi (rendering) kontrol edenin TPdfView.Active olduğunu, ancak dosyayı asıl açanın TPdf.Active olduğunu unutmayın; bunlar birbirinden bağımsızdır. Bağlantılı TPdf'si henüz etkin değilken PdfView.Active := True olarak ayarlamak zararsızdır ancak hiçbir şey görüntülemez

procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
  PdfViewComponent: TPdfView);
var
  Password: string;
begin
  if not OpenDialog.Execute then
    Exit;

  PdfComponent.Active   := False;
  PdfComponent.FileName := OpenDialog.FileName;
  PdfComponent.Password := '';

  try
    PdfComponent.Active := True;
  except
    on E: EPdfError do
    begin
      if InputQuery('Password', 'Enter document password:', Password) then
      begin
        PdfComponent.Password := Password;
        PdfComponent.Active   := True;
      end
      else
        raise;
    end;
  end;

  if PdfComponent.Active then
  begin
    PdfViewComponent.PageNumber := 1;
    SetActivePdfView(PdfViewComponent);
  end;
end;

Atamadan sonra her zaman PdfComponent.Active durumunu kontrol edin; hasarlı bir dosya veya yanlış bir parola, varsayılan yolda bir istisna (exception) oluşturmadan yüklemenin sessizce başarısız olmasına neden olur. Başarılı bir açılışın ardından açıkça PdfViewComponent.PageNumber := 1 ayarının yapılması, önceki belgeden kalan bayat (stale) bir sayfa numarasını önler

Yukarıdaki parola işleme kodu, bilinen parola iletisi dışındaki tüm hatalarda istisna (exception) fırlatır. Bu kasıtlıdır: bozuk veya desteklenmeyen dosyaların sessiz ve boş bir panel olarak yutulması yerine hemen ortaya çıkmasını istersiniz. Hiçbir şey görmeyen bir kullanıcı, dosyanın yüklenip yüklenmediği ve basitçe boş mu olduğu yoksa bileşenin bunu reddedip reddetmediği konusunda hiçbir fikre sahip değildir. Fırlatmak (raising) hatayı görünür kılar

Etkin panel izleme

Kullanıcı bir panelin içini tıkladığında, o panel etkin hale gelir. Form, özel bir FActivePdfView: TPdfView alanını izler. Görsel geri bildirim, kapsayıcı TScrollBox üzerindeki bir kenarlık rengi değişikliğidir: etkin olan için clHighlight ve diğerleri için clWindow olarak ayarlayın. Bunu her TPdfView.OnClick olayına ve açma prosedürüne bağlayın, böylece odak, az önce açtığınız belgeyi izler

Bazı işlemler sadece etkin olan yerine görünen tüm panellere uygulanır. Formdaki mantıksal (boolean) bir FAllViewsMode bu dalı yönlendirir. Doğru (true) olduğunda, yakınlaştırma değişiklikleri ve sayfa gezinmesi, etkin bir belgeye sahip her panele yayılır:

procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
  if PdfView1.Active then PdfView1.Zoom := NewZoom;
  if PdfView2.Active then PdfView2.Zoom := NewZoom;
  if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;

Senkronize sayfa gezinmesi

Senkronize gezinme isteğe bağlıdır, ancak her iki dosyanın da aynı sayfa aralığını kapsadığı belge revizyon iş akışları için kullanışlıdır. Mantık, kullanıcı bir görünümde gezindikten sonra tetiklenen bir olay işleyicisine aittir. Bir kaynak görünüm PageNumber'ını (sayfa numarası) değiştirdiğinde, işleyici bu numarayı bir koruma (guard) koşuluyla diğer görünümlere yayar: hedef görünüm en az o kadar sayfaya sahip olmalıdır, aksi takdirde atlayın

TPdfView ve TPdf üzerindeki PageNumber değerleri birbirinden bağımsızdır. TPdf.PageNumber, belge bileşeninin hangi sayfayı geçerli kabul ettiğini izler; TPdfView.PageNumber ise ekranda neyin görüntülendiğini izler. Gezinme amaçları için belge özelliğini değil, görünüm özelliğini istersiniz

"Sayfaları senkronize et" (Sync pages) gibi etiketlenmiş bir onay kutusu (checkbox), kullanıcıya kontrol sağlar. İşaretlenmediğinde, her panel bağımsız olarak gezinir ve işleyici hemen çıkar. İki belgenin farklı sayfa sayılarına sahip olduğu veya kullanıcının farklı bir sayfada başlayan bir çeviride eşdeğer pasajı bulmak istediği kullanım durumlarında bu bağımsızlık önemlidir. Senkronizasyonu her zaman zorlamak, aracı basit bir iki pencereli masaüstü düzeninden daha zor kullanılabilir hale getirir

Dikkat edilmesi gereken bir şey var: senkronizasyon işleyicisi içinde PdfView.PageNumber'ı programatik olarak ayarlamak, o görünümdeki değişiklik olayını kendisi tetikleyecektir. Atamadan önce ayarladığınız ve hemen sonrasında temizlediğiniz bir boolean bayrakla sonsuz özyinelemeye (infinite recursion) karşı koruma sağlayın. Bayrak, görünüm başına değil form başınadır, çünkü üç görünüm de aynı işleyiciyi paylaşır

Panel Başına Yakınlaştırma

Her TPdfView kendi Zoom (yakınlaştırma) özelliğini taşır; bu özellik, Zoom := 100'ün gerçek boyut (%100) anlamına geldiği yüzde cinsinden bir Double değeridir. Ayarlanması etkin tüm FitMode (sığdırma modu) durumlarını geçersiz kılar. Etkin paneldeki genişliğe sığdır (fit-to-width) düğmesi için, PdfView.PageWidthZoom[PdfView.PageNumber] değerinden sığdırma yakınlaştırmasını (fit zoom) okuyun ve onu atayın. Sayfaya sığdır (fit-to-page) için PageZoom[PageNumber] kullanın. Her ikisi de 1 tabanlı sayfa numarasıyla dizinlenmiş dizi özellikleridir, bu nedenle onlara erişmeden önce sıfır sayfa numarasına karşı koruma sağlayın

Geçerli sayfayı bir resim olarak dışa aktarırken, dönüş değerini görünümden okuyun ancak RenderPage öğesini görünümde değil, TPdf bileşeninde çağırın. TPdf.RenderPage öğesinin bit eşlem (bitmap) formu, TRotation (döndürme) değeri ve bir TRenderOptions (işleme seçenekleri) kümesine ek olarak açık piksel boyutları alır. İşlev varyantı, kaydettikten sonra kendi kendinize serbest bıraktığınız (free) ve çağırana ait olan bir TBitmap döndürür:

procedure TFormMain.SaveActiveViewAsImage;
var
  Pdf: TPdf;
  Bmp: TBitmap;
  Jpeg: TJpegImage;
begin
  if not Assigned(FActivePdfView) or not FActivePdfView.Active then
    Exit;

  Pdf := FActivePdfView.Pdf;
  Pdf.PageNumber := FActivePdfView.PageNumber;

  Bmp := Pdf.RenderPage(
    0, 0,
    Round(Pdf.PageWidth * 2),
    Round(Pdf.PageHeight * 2),
    FActivePdfView.Rotation, [], clWhite);
  try
    if SavePictureDialog.Execute then
    begin
      Jpeg := TJpegImage.Create;
      try
        Jpeg.Assign(Bmp);
        Jpeg.CompressionQuality := 90;
        Jpeg.SaveToFile(SavePictureDialog.FileName);
      finally
        Jpeg.Free;
      end;
    end;
  finally
    Bmp.Free;
  end;
end;

Genişlik ve yükseklik üzerindeki 2x çarpanı, ince metinlere sahip belgeler için daha keskin çıktı sağlar. Bit eşlem serbest bırakmanın (bitmap free) etrafındaki try/finally isteğe bağlı değildir; bir TSaveDialog iptali yine de finally bloğuna ulaşır ve kullanıcının ne yaptığına bakılmaksızın bit eşlemin serbest bırakılmasını istersiniz

DLL Gereksinimleri

PDFium Component, yerel (native) pdfium kitaplığını sarmalar. 32 bitlik bir ana makine işleminin pdfium32.dll dosyasına; 64 bitlik bir ana makinenin ise pdfium64.dll dosyasına ihtiyacı vardır. V8 JavaScript motoruna sahip varyantlar v8 son ekini ekler ve 5-6 MB'lık standart yapılara kıyasla kabaca 23-27 MB ağırlığındadır. Form doldurmayı (Pdf.FormFill := False) devre dışı bırakan bir karşılaştırma görüntüleyicisi için standart V8 olmayan yapı yeterlidir ve dağıtımı daha küçük tutar

DLL'yi çalıştırılabilir (executable) dosyayla aynı dizine veya sistemdeki PATH üzerinde herhangi bir dizine yerleştirin. Bileşen, ilk TPdf etkinleştirildiğinde onu talep üzerine yükler, böylece eksik bir DLL uygulama başlangıcından ziyade o noktada ortaya çıkar. Bir yükleyici (installer) gönderiyorsanız en güvenilir yaklaşım, bir yöneticinin daha sonra temizleyebileceği bir sistem dizinine güvenmek yerine, kurulum sırasında DLL'yi uygulama klasörüne kopyalamaktır

V8 yapıları öncelikle, örneğin hesaplama alanlarını tetiklemek veya işleyicileri (handlers) göndermek gibi PDF JavaScript eylemleriyle etkileşime girmeniz gerektiğinde kullanışlıdır. Pasif bir karşılaştırma görüntüleyicisinin JavaScript çalıştırması için hiçbir nedeni yoktur; Active := True öncesinde Pdf.FormFill := False ayarının yapılması form doldurma ortamını tamamen atlar, bu da standart yapı kullanılsa bile hiçbir JS motorunun başlatılmadığı anlamına gelir. Gönderdiğiniz DLL varyantı hangisi olursa olsun, salt okunur bir görüntüleyici için doğru varsayılan budur

PDFium Component bileşeni ve tam API'si hakkında daha fazla ayrıntı için Delphi PDFium Component ürün sayfasını ziyaret edin