PDFium Component memberi Anda satu metode untuk pemecahan PDF: ImportPages. Selebihnya, entah Anda sedang mengisolasi satu halaman, memotong pada batas sembarang, atau mengikuti struktur bookmark milik dokumen itu sendiri, hanyalah cara berbeda untuk memutuskan nomor halaman mana yang masuk ke setiap file keluaran. Mekanismenya tetap sama. Memahami itu sejak awal menghemat banyak belokan yang keliru
Cara kerja loop pemecahannya
Polanya sama tanpa peduli bagaimana Anda membagi dokumen sumbernya. Buat sebuah instance TPdf yang baru, panggil CreateDocument padanya untuk menginisialisasi PDF kosong di memori, impor halaman yang Anda inginkan dengan ImportPages, simpan hasilnya, lalu setel ulang Active menjadi False sebelum iterasi berikutnya. Langkah terakhir itulah yang sering terlewat: CreateDocument tidak menutup secara tersirat dokumen yang masih ada di memori, jadi Anda harus menyimpan keluaran Anda lalu menyetel ulang Active := False secara eksplisit sebelum memanggilnya lagi; menyetel ulang lebih dulu menjaga keadaannya tetap bersih dan terdefinisi jelas. Instance TPdf di lapisan luar dipakai ulang di seluruh iterasi, dan itu menjaga tekanan alokasi tetap rendah pada pekerjaan besar
Beginilah rupa pemecahan halaman demi halaman ketika dikupas sampai intinya:
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 adalah string nomor halaman berbasis 1; titik sisip 1 = posisi pertama
if not PdfOut.ImportPages(Source, IntToStr(I), 1) then
raise Exception.CreateFmt('Failed to import page %d', [I]);
OutFile := OutputDir + '\page_' + Format('%.4d', [I]) + '.pdf';
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False; // setel ulang sebelum CreateDocument berikutnya
end;
finally
PdfOut.Free;
end;
end;
Parameter Range pada ImportPages memakai format string yang sama seperti yang dipakai PDFium secara internal: daftar nomor halaman yang dipisah koma atau rentang yang dibatasi tanda hubung, semuanya berbasis 1. '3' mengimpor halaman 3. '1-5' mengimpor halaman 1 sampai 5 secara berurutan. '2,5,8' mengimpor ketiga halaman itu. Parameter ketiganya adalah posisi penyisipan berbasis 1 di dalam dokumen tujuan; memberikan 1 selalu menempatkan halaman yang diimpor di awal sebuah file yang selain itu kosong, dan itulah yang Anda inginkan di sini
Memecah berdasarkan rentang halaman
Ketika pemanggilnya menyodorkan daftar seperti 1-12,13-24,25-36, Anda mem-parsingnya menjadi pasangan awal/akhir lalu menjalankan loop yang sama, dengan merangkai string rentangnya 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;
if not PdfOut.ImportPages(Source, RangeList[I], 1) then
raise Exception.Create('Invalid page range: ' + RangeList[I]);
OutFile := Format('%s\section_%d.pdf', [OutputDir, I + 1]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Validasi sebelum Anda sampai ke ImportPages itu penting di sini. ImportPages mengembalikan False ketika sebuah nomor halaman di dalam string rentangnya melebihi Source.PageCount, namun ia tidak melempar exception dan tidak menghasilkan file keluaran separuh jadi yang bisa Anda kenali hanya dari namanya. Periksa nilai kembalian SaveAs dan catat kegagalannya secara terpisah; sebuah rentang yang menghasilkan file keluaran kosong tidak tampak salah sampai ada yang membukanya
Memecah pada batas bookmark
Pendekatan ketiga memakai struktur milik dokumen itu sendiri alih-alih daftar yang disodorkan dari luar. Setiap bookmark tingkat teratas membawa nomor halaman sasarannya; bagian yang didefinisikannya berjalan dari halaman itu sampai satu halaman sebelum halaman bookmark berikutnya, atau sampai akhir dokumen untuk entri terakhirnya
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;
if not PdfOut.ImportPages(Source, RangeStr, 1) then
begin
PdfOut.Active := False;
Continue; // lewati bagian cacat alih-alih menulis file kosong
end;
SafeTitle := StringReplace(Bm[I].Title, '/', '_', [rfReplaceAll]);
SafeTitle := StringReplace(SafeTitle, ':', '_', [rfReplaceAll]);
OutFile := Format('%s\%02d_%s.pdf', [OutputDir, I + 1, SafeTitle]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Dokumen yang tidak punya bookmark bukanlah kondisi error yang layak disodorkan kepada pengguna sebagai error; itu sekadar berarti mode pemecahan ini tidak punya bahan untuk dikerjakan. Penjaga Length(Bm) = 0 menanganinya tanpa ribut. Yang layak disodorkan adalah ketika nomor halaman sebuah bookmark berada di luar rentang dokumennya, yang terjadi pada file cacat tempat outline-nya tidak pernah diperbarui setelah halaman dihapus. Pemeriksaan batas pada StartPage dan EndPage melewati entri semacam itu alih-alih meneruskan rentang sampah ke ImportPages
Penamaan file keluaran dan reset Active
Keamanan nama file untuk nama yang diturunkan dari bookmark butuh perhatian tersendiri. Judul bookmark bisa memuat karakter yang sah di dalam sebuah string PDF namun tidak sah di dalam path filesystem. Setidaknya, gantilah garis miring, garis miring terbalik, dan titik dua sebelum merangkai path keluarannya. Di Windows, *, ?, ", <, >, dan | juga terlarang; sebuah loop sederhana atas himpunan tetap sudah mencakup semuanya tanpa perlu menarik regex
Baris Active := False di akhir setiap iterasi pantas ditekankan karena itulah satu-satunya syarat yang tidak kentara dalam pola ini. CreateDocument tidak menutup secara tersirat apa pun yang sedang terbuka. Jika Active masih bernilai True ketika CreateDocument berjalan lagi, dokumen yang masih ada di memori tidak pernah ditutup atau disimpan dengan benar, dan Anda tidak bisa mengandalkan perilaku yang terdefinisi jelas dalam keadaan itu, jadi simpan dan setel ulang secara eksplisit sebelum memulai dokumen berikutnya. Anggaplah ia pasangan bagi try/finally: blok finally membebaskan objek luarnya; Active := False menyetel ulang keadaan dokumen di dalamnya di antara iterasi loop
Pemakaian memori di sepanjang pekerjaan pemecahan yang besar tetap datar dengan pendekatan ini karena Anda tidak pernah menahan lebih dari satu dokumen keluaran di memori sekaligus. Dokumen sumbernya tetap terbuka dan hanya-baca sepanjang prosesnya; ImportPages menyalin data halamannya ke dalam dokumen baru tanpa mengubah sumbernya. Jika sumbernya terenkripsi, bukalah dengan password-nya sebelum loop-nya, dan halaman yang disalin ke setiap file keluaran akan tidak terenkripsi, yang biasanya merupakan perilaku yang tepat untuk keluaran pecahan yang dibagikan ke penerima yang berbeda-beda
Satu hal lagi tentang SaveAs: ia mengembalikan sebuah Boolean. Direktori keluaran yang tidak ada, sebuah path dengan karakter yang ditolak OS, atau kondisi disk penuh semuanya akan membuat SaveAs mengembalikan False tanpa melempar exception. Dalam pekerjaan batch yang memecah dokumen 200 halaman menjadi 200 file satu halaman, kegagalan senyap pada halaman 147 mudah terlewat. Periksa nilai kembaliannya pada setiap panggilan lalu hitung keberhasilannya terhadap total yang diharapkan ketika loop-nya selesai
Metode ImportPages dan CreateDocument yang ditampilkan di sini adalah bagian dari PDFium Component untuk Delphi dan C++Builder