Artikel Teknis

Perbandingan PDF Berdampingan di Delphi dengan PDFium Component

Dua dokumen terbuka sekaligus, nomor halaman yang sama, masing-masing dalam panel yang dapat digulir: itulah inti dari viewer perbandingan. PDFium Component menyediakan ini melalui model objek yang mudah di mana TPdf memiliki file dan TPdfView memiliki tampilan. Satu dokumen, satu TPdf, satu TPdfView. Anda ingin tiga panel, Anda memiliki tiga pasang. Bagian yang sulit bukan panggilan API; itu adalah aritmatika tata letak ketika jendela diubah ukurannya dan logika sinkronisasi halaman ketika Anda memutuskan tampilan mana yang harus mengikuti yang mana

Form VCL menggunakan dua atau tiga TScrollBox berdampingan, masing-masing berisi TPdfView yang diatur alClient. Dua TSplitter memungkinkan lebar panel diubah saat runtime, sementara toolbar menyediakan pembukaan file, zoom, dan pilihan tampilan dua atau tiga panel

Tata letak form

Form VCL menampung tiga kontainer TScrollBox berdampingan, masing-masing dengan TPdfView di dalamnya dan disejajarkan ke alClient sehingga mengisi kotak. Dua komponen TSplitter duduk di antara kotak sehingga pengguna dapat menyesuaikan lebar kolom saat runtime. Toolbar di atas panel membawa tombol buka, kontrol zoom, dan tombol toggle dua tampilan / tiga tampilan

Mode tiga tampilan adalah boolean yang dilacak form secara internal. Ketika berubah, Anda menghitung ulang lebar dan menampilkan atau menyembunyikan kolom ketiga. Pendekatan paling sederhana adalah menghapus semua properti Align, menyembunyikan splitter, lalu mengatur posisi absolut:

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;

Mengatur Align := alNone pada semua tiga kotak sebelum aritmatika integer menghindari engine constraint VCL melawan penugasan Anda. Pulihkan visibilitas splitter setelah pemosisian jika Anda ingin drag-to-resize dalam mode dua tampilan

Tinggi setiap kotak gulir adalah area klien dikurangi tinggi panel toolbar. Karena toolbar berlabuh di atas dengan alTop, ClientHeight - PanelButtons.Height memberi Anda ruang vertikal yang dapat digunakan. Tetapkan ini ke semua tiga kotak di dalam panggilan UpdateLayout yang sama sehingga tidak pernah ada frame di mana satu kotak lebih tinggi dari yang lain dan menyebabkan flicker tata letak

Membuka dokumen

Setiap pasang panel membutuhkan prosedur buka sendiri. Polanya singkat: nonaktifkan komponen, atur nama file, coba aktifkan, tangkap EPdfError jika file memerlukan kata sandi. Perhatikan bahwa TPdfView.Active yang mengontrol rendering, tetapi TPdf.Active yang sebenarnya membuka file; keduanya independen. Mengatur PdfView.Active := True ketika TPdf tertautnya belum aktif tidak berbahaya tetapi tidak menampilkan apa pun

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;

Selalu periksa PdfComponent.Active setelah penugasan; file yang rusak atau kata sandi yang salah menyebabkan pemuatan gagal secara diam-diam tanpa memunculkan pengecualian di jalur default. Mengatur PdfViewComponent.PageNumber := 1 secara eksplisit setelah pembukaan yang berhasil menghindari nomor halaman yang basi dari dokumen sebelumnya

Kode penanganan kata sandi di atas memunculkan pengecualian apa pun selain pesan kata sandi yang diketahui. Itu disengaja: Anda ingin file yang rusak atau tidak didukung muncul segera daripada ditelan sebagai panel kosong yang tenang. Pengguna yang tidak melihat apa pun tidak tahu apakah file dimuat dan hanya kosong, atau apakah komponen menolaknya. Memunculkan membuat kesalahan terlihat

Pelacakan panel aktif

Ketika pengguna mengklik di dalam panel, panel tersebut menjadi aktif. Form melacak field FActivePdfView: TPdfView privat. Umpan balik visual adalah perubahan warna border pada TScrollBox yang berisi: atur ke clHighlight untuk yang aktif dan clWindow untuk yang lain. Hubungkan ini ke setiap TPdfView.OnClick dan ke prosedur buka sehingga fokus mengikuti dokumen yang baru saja dibuka

Beberapa operasi berlaku untuk semua panel yang terlihat daripada hanya yang aktif. Boolean FAllViewsMode pada form mendorong cabang tersebut. Ketika benar, perubahan zoom dan navigasi halaman menyebar ke setiap panel yang memiliki dokumen aktif:

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;

Navigasi halaman tersinkronisasi

Navigasi tersinkronisasi opsional tetapi berguna untuk alur kerja revisi dokumen di mana kedua file mencakup rentang halaman yang sama. Logikanya berada dalam event handler yang diaktifkan setelah pengguna menavigasi satu tampilan. Ketika tampilan sumber mengubah PageNumber-nya, handler menyebarkan nomor tersebut ke tampilan lain, dengan satu penjaga: tampilan target harus memiliki setidaknya halaman sebanyak itu, jika tidak lewati

PageNumber pada TPdfView dan pada TPdf independen. TPdf.PageNumber melacak halaman mana yang dianggap saat ini oleh komponen dokumen; TPdfView.PageNumber melacak apa yang ditampilkan di layar. Untuk tujuan navigasi Anda ingin properti tampilan, bukan properti dokumen

Kotak centang berlabel sesuatu seperti "Sinkronkan halaman" memberi pengguna kontrol. Ketika tidak dicentang, setiap panel menavigasi secara independen dan handler keluar segera. Independensi itu penting untuk kasus penggunaan di mana dua dokumen memiliki jumlah halaman yang berbeda, atau di mana pengguna ingin menemukan bagian yang setara dalam terjemahan yang dimulai pada halaman yang berbeda. Memaksa sinkronisasi selalu akan membuat alat lebih sulit digunakan daripada susunan dua jendela desktop biasa

Satu hal yang perlu diperhatikan: mengatur PdfView.PageNumber secara programatik di dalam handler sinkronisasi itu sendiri akan memicu acara perubahan pada tampilan tersebut. Lindungi dari rekursi tak terbatas dengan flag boolean yang Anda set sebelum penugasan dan hapus segera setelahnya. Flag tersebut per-form, bukan per-tampilan, karena semua tiga tampilan berbagi handler yang sama

Zoom per panel

Setiap TPdfView membawa properti Zoom-nya sendiri, sebuah Double dalam persen di mana Zoom := 100 berarti ukuran sebenarnya (100%). Mengaturnya menggantikan FitMode aktif apa pun. Untuk tombol fit-to-width pada panel aktif, baca fit zoom dari PdfView.PageWidthZoom[PdfView.PageNumber] dan tetapkan. Untuk fit-to-page, gunakan PageZoom[PageNumber]. Keduanya adalah properti array yang diindeks oleh nomor halaman berbasis 1, jadi lindungi terhadap nomor halaman nol sebelum mengaksesnya

Ketika Anda mengekspor halaman saat ini ke gambar, baca rotasi dari tampilan tetapi panggil RenderPage pada komponen TPdf, bukan tampilan. Bentuk bitmap dari TPdf.RenderPage mengambil dimensi piksel eksplisit ditambah nilai TRotation dan set TRenderOptions. Varian fungsi mengembalikan TBitmap yang dimiliki pemanggil yang Anda bebaskan sendiri setelah menyimpan:

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;

Pengganda 2x pada lebar dan tinggi memberikan output yang lebih tajam untuk dokumen dengan teks halus. try/finally di sekitar bitmap free tidak opsional; pembatalan TSaveDialog masih mencapai blok finally, dan Anda ingin bitmap dibebaskan terlepas dari apa yang dilakukan pengguna

Persyaratan DLL

PDFium Component membungkus pustaka pdfium native. Proses host 32-bit membutuhkan pdfium32.dll; host 64-bit membutuhkan pdfium64.dll. Varian dengan engine JavaScript V8 menambahkan sufiks v8 dan berbobot sekitar 23-27 MB dibandingkan build standar 5-6 MB. Untuk viewer perbandingan yang menonaktifkan pengisian form (Pdf.FormFill := False), build standar non-V8 sudah cukup dan menjaga distribusi lebih kecil

Tempatkan DLL di direktori yang sama dengan executable, atau di direktori mana pun di PATH sistem. Komponen memuatnya sesuai permintaan ketika TPdf pertama diaktifkan, sehingga DLL yang hilang muncul pada saat itu daripada saat aplikasi mulai. Jika Anda menggunakan installer, pendekatan paling andal adalah menyalin DLL ke folder aplikasi selama instalasi daripada mengandalkan direktori sistem yang mungkin kemudian dibersihkan oleh administrator

Build V8 terutama berguna ketika Anda perlu berinteraksi dengan tindakan JavaScript PDF, misalnya untuk memicu field kalkulasi atau handler submit. Viewer perbandingan pasif tidak memiliki alasan untuk menjalankan JavaScript; mengatur Pdf.FormFill := False sebelum Active := True melewatkan lingkungan pengisian form sepenuhnya, yang juga berarti tidak ada engine JS yang diinisialisasi bahkan jika build standar digunakan. Itu adalah default yang benar untuk viewer read-only terlepas dari varian DLL mana yang Anda kirimkan

Untuk detail lebih lanjut tentang komponen PDFium Component dan API lengkapnya, kunjungi halaman produk Delphi PDFium Component