Artikel Teknis

Memisahkan Dokumen PDF Menjadi Beberapa File dengan PDFium di Delphi

PDFium Component memberikan satu metode untuk pemisahan PDF: ImportPages. Segala sesuatunya, baik Anda mengisolasi satu halaman, memotong pada batas sewenang-wenang, atau mengikuti struktur bookmark dokumen sendiri, hanyalah cara berbeda untuk memutuskan nomor halaman mana yang masuk ke setiap file output. Mekanikanya tetap sama. Memahami itu lebih awal menghemat banyak kesalahan arah

Untuk setiap output, buat instance TPdf yang bersih, panggil CreateDocument, impor halaman dengan ImportPages, simpan hasilnya, lalu set Active := False sebelum memulai iterasi berikutnya. Instance luar boleh dipakai ulang agar tekanan alokasi tetap rendah pada batch besar

Cara kerja loop pemisahan

Polanya sama terlepas dari bagaimana Anda membagi dokumen sumber. Buat instance TPdf baru, panggil CreateDocument pada instance tersebut untuk menginisialisasi PDF kosong dalam memori, impor halaman yang Anda inginkan dengan ImportPages, simpan hasilnya, lalu reset Active ke False sebelum iterasi berikutnya. Langkah terakhir itulah yang sering dilewatkan orang: CreateDocument selalu memulai dokumen baru, tetapi jika Active masih True ketika dijalankan lagi, ia membuang dokumen yang masih dalam memori secara implisit, jadi reset terlebih dahulu menjaga status tetap bersih dan terdefinisi dengan baik. Instance TPdf luar digunakan kembali di seluruh iterasi, yang menjaga tekanan alokasi tetap rendah pada pekerjaan besar

Berikut tampilan pemisahan halaman demi halaman yang distrip ke esensinya:

procedure SplitIntoPages(Source: TPdf; const OutputDir: string);
var
  I: Integer;
  PdfOut: TPdf;
  OutFile: string;
begin
  PdfOut := TPdf.Create(nil);
  try
    for I := 1 to Source.PageCount do
    begin
      PdfOut.CreateDocument;

      // Range is a 1-based page number string; insertion point 1 = first position
      PdfOut.ImportPages(Source, IntToStr(I), 1);

      OutFile := OutputDir + '\page_' + Format('%.4d', [I]) + '.pdf';
      PdfOut.SaveAs(OutFile);

      PdfOut.Active := False;   // reset before next CreateDocument
    end;
  finally
    PdfOut.Free;
  end;
end;

Parameter Range ke ImportPages adalah format string yang sama yang digunakan PDFium secara internal: daftar nomor halaman yang dipisahkan koma atau rentang yang dibatasi tanda hubung, semua berbasis 1. '3' mengimpor halaman 3. '1-5' mengimpor halaman 1 hingga 5 secara berurutan. '2,5,8' mengimpor ketiga halaman tersebut. Parameter ketiga adalah posisi penyisipan berbasis 1 dalam dokumen tujuan; melewati 1 selalu menempatkan halaman yang diimpor di awal file yang kosong, yang merupakan yang Anda inginkan di sini

Memisahkan berdasarkan rentang halaman

Ketika pemanggil menyediakan daftar seperti 1-12,13-24,25-36, Anda mengurainya menjadi pasangan awal/akhir dan menjalankan loop yang sama, membuat string rentang dari setiap pasangan:

procedure SplitByRanges(Source: TPdf; const RangeList: array of string;
  const OutputDir: string);
var
  I: Integer;
  PdfOut: TPdf;
  OutFile: string;
begin
  PdfOut := TPdf.Create(nil);
  try
    for I := 0 to High(RangeList) do
    begin
      PdfOut.CreateDocument;
      PdfOut.ImportPages(Source, RangeList[I], 1);
      OutFile := Format('%s\section_%d.pdf', [OutputDir, I + 1]);
      PdfOut.SaveAs(OutFile);
      PdfOut.Active := False;
    end;
  finally
    PdfOut.Free;
  end;
end;

Validasi sebelum Anda mencapai ImportPages penting di sini. ImportPages mengembalikan False ketika nomor halaman dalam string rentang melebihi Source.PageCount, tetapi tidak memunculkan pengecualian dan tidak menghasilkan file output parsial yang dapat Anda deteksi dari nama saja. Periksa nilai kembalian SaveAs dan catat kegagalan secara terpisah; rentang yang menghasilkan file output kosong tidak terlihat jelas salah sampai seseorang membukanya

Memisahkan di batas bookmark

Pendekatan ketiga menggunakan struktur dokumen sendiri daripada daftar yang disediakan eksternal. Setiap bookmark tingkat atas membawa nomor halaman target; bagian yang didefinisikannya berjalan dari halaman tersebut hingga satu sebelum halaman bookmark berikutnya, atau hingga akhir dokumen untuk entri terakhir

procedure SplitByBookmarks(Source: TPdf; const OutputDir: string);
var
  Bm: TBookmarks;
  I, StartPage, EndPage: Integer;
  PdfOut: TPdf;
  RangeStr, OutFile, SafeTitle: string;
begin
  Bm := Source.Bookmarks;
  if Length(Bm) = 0 then
    Exit;

  PdfOut := TPdf.Create(nil);
  try
    for I := 0 to High(Bm) do
    begin
      StartPage := Bm[I].PageNumber;
      if I < High(Bm) then
        EndPage := Bm[I + 1].PageNumber - 1
      else
        EndPage := Source.PageCount;

      if (StartPage < 1) or (EndPage < StartPage) then
        Continue;

      RangeStr := Format('%d-%d', [StartPage, EndPage]);

      PdfOut.CreateDocument;
      PdfOut.ImportPages(Source, RangeStr, 1);

      SafeTitle := StringReplace(Bm[I].Title, '/', '_', [rfReplaceAll]);
      SafeTitle := StringReplace(SafeTitle, ':', '_', [rfReplaceAll]);
      OutFile := Format('%s\%02d_%s.pdf', [OutputDir, I + 1, SafeTitle]);
      PdfOut.SaveAs(OutFile);

      PdfOut.Active := False;
    end;
  finally
    PdfOut.Free;
  end;
end;

Dokumen yang tidak memiliki bookmark bukan kondisi kesalahan yang layak disampaikan kepada pengguna sebagai satu; itu hanya berarti mode pemisahan ini tidak memiliki apa pun untuk dikerjakan. Penjaga Length(Bm) = 0 menangani itu secara diam-diam. Yang layak disampaikan adalah ketika nomor halaman bookmark di luar rentang dokumen, yang terjadi dalam file yang salah bentuk di mana garis besar tidak pernah diperbarui setelah halaman dihapus. Pemeriksaan batas pada StartPage dan EndPage melewati entri tersebut daripada melewati rentang sampah ke ImportPages

Penamaan file output dan reset Active

Keamanan nama file untuk nama yang berasal dari bookmark memerlukan perhatian eksplisit. Judul bookmark dapat berisi karakter yang valid dalam string PDF tetapi tidak dalam jalur sistem file. Minimal, ganti garis miring ke depan, garis miring ke belakang, dan titik dua sebelum membangun jalur output. Di Windows, *, ?, ", <, >, dan | juga dilarang; loop sederhana atas satu set tetap mencakup semuanya tanpa menarik regex

Baris Active := False di akhir setiap iterasi layak ditekankan karena itu adalah satu-satunya persyaratan yang tidak jelas dalam pola. CreateDocument tidak secara implisit menutup apa pun yang terbuka. Jika Active masih True ketika CreateDocument dijalankan lagi, PDFium membuang dokumen saat ini dan memulai yang baru tanpa kesalahan, tetapi perilakunya ditentukan oleh implementasi dalam kasus tepi dan niatnya lebih jelas ketika Anda reset secara eksplisit. Anggap itu sebagai pasangan dari try/finally: blok finally membebaskan objek luar; Active := False mereset status dokumen dalam antara iterasi loop

Penggunaan memori di seluruh pekerjaan pemisahan besar tetap datar dengan pendekatan ini karena Anda tidak pernah menahan lebih dari satu dokumen output dalam memori sekaligus. Dokumen sumber tetap terbuka dan hanya baca sepanjang waktu; ImportPages menyalin data halaman ke dokumen baru tanpa memodifikasi sumber. Jika sumber dienkripsi, buka dengan kata sandinya sebelum loop dan halaman yang disalin dalam setiap file output akan tidak terenkripsi, yang biasanya merupakan perilaku yang tepat untuk output terpisah yang didistribusikan ke penerima yang berbeda

Satu hal lagi tentang SaveAs: ia mengembalikan Boolean. Direktori output yang tidak ada, jalur dengan karakter yang ditolak OS, atau kondisi disk penuh semuanya akan menyebabkan SaveAs mengembalikan False tanpa memunculkan pengecualian. Dalam pekerjaan batch yang membagi dokumen 200 halaman menjadi 200 file satu halaman, kegagalan diam pada halaman 147 mudah terlewatkan. Periksa nilai kembalian pada setiap panggilan dan hitung keberhasilan terhadap total yang diharapkan ketika loop selesai

Metode ImportPages dan CreateDocument yang ditunjukkan di sini adalah bagian dari PDFium Component untuk Delphi dan C++Builder