Ekstraksi teks PDF terlihat sederhana sampai Anda menemukan dokumen yang lapisan teksnya tidak ada, rusak, atau terbagi di antara lusinan urutan karakter kecil tanpa urutan yang berarti. PDFium Component memberi Anda dua titik masuk: array Character[] untuk akses berbasis indeks mentah ke setiap glif (glyph) pada halaman, dan ReadablePageContent untuk tampilan terstruktur yang merekonstruksi paragraf dan judul dari pohon tag PDF atau analisis heuristik. Tidak ada satu pun yang selalu menjadi pilihan yang tepat, jadi memahami apa yang diekspos masing-masing adalah hal yang penting
Membuka dokumen dan jebakan kegagalan diam-diam
TPdf membuka berkas dengan menyetel FileName dan mengubah Active := True. Detail pentingnya: menyetel Active := True tidak pernah memicu pengecualian. Jika berkas tersebut hilang, dilindungi kata sandi, atau rusak, PDFium menangkap kesalahan tersebut secara internal dan Active tetap bernilai False. Itu berarti setiap loop ekstraksi harus waspada terhadap hal ini:
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True;
if not Pdf.Active then
begin
ShowMessage('Could not open PDF (damaged or wrong password)');
Exit;
end;
// ekstraksi dilakukan di sini
finally
Pdf.Active := False;
Pdf.Free;
end;
Berkas yang dilindungi kata sandi memerlukan penyetelan Pdf.Password := '...' sebelum Active := True. Tidak ada kesempatan kedua: begitu Active gagal, Anda harus menutup dan membuka kembali dengan kata sandi yang benar
Ekstraksi halaman-demi-halaman dengan Character[]
Pendekatan tingkat terendah menyusuri setiap karakter pada setiap halaman. Setel Pdf.PageNumber untuk memuat lapisan teks untuk halaman tersebut, lalu ulangi entri CharacterCount menggunakan properti Character[]. Dua bendera (flags) pada setiap entri patut diperiksa: CharacterGenerated[i] menandai glif sintetis yang dimasukkan oleh perender (tanda hubung lunak pada jeda baris, misalnya) yang tidak memiliki nilai Unicode nyata, dan CharacterMapError[i] menandakan bahwa PDFium tidak dapat memetakan glif ke titik kode (code point), yang terjadi pada pengodean font yang tidak memiliki tabel ToUnicode
procedure ExtractAllText(Pdf: TPdf; Output: TStrings);
var
Page, I: Integer;
Line: string;
Ch: WideChar;
begin
for Page := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := Page;
Line := '';
for I := 0 to Pdf.CharacterCount - 1 do
begin
if Pdf.CharacterGenerated[I] or Pdf.CharacterMapError[I] then
Continue;
Ch := Pdf.Character[I];
if Ch = #13 then
Ch := #10; // normalisasikan CR ke LF
Line := Line + Ch;
end;
Output.Add(Line);
end;
end;
Hasilnya adalah string datar dari titik kode Unicode dalam urutan yang dicantumkan oleh PDFium, yang merupakan urutan kemunculannya dalam aliran konten, tidak selalu dalam urutan pembacaan dari kiri ke kanan. Untuk sebagian besar dokumen beraksara Latin yang dihasilkan oleh alat kantor standar, hal ini tidak masalah. Untuk PDF hasil pemindaian yang di-OCR dengan urutan glif yang tidak biasa, atau untuk teks dari kanan ke kiri, urutannya bisa salah. Di sinilah ReadablePageContent menjadi lebih berguna
Ekstraksi terstruktur dengan ReadablePageContent
ReadablePageContent naik satu tingkat lebih tinggi: ia mengembalikan rekaman TPdfReadableContent yang array Fragments-nya membawa fragmen konten bertag, masing-masing dengan Kind yang mengidentifikasi paragraf, judul, item daftar, sel tabel, dan sebagainya. Ketika PDF membawa pohon struktur (periksa Pdf.IsTagged), sumbernya adalah rosStructure dan urutan pembacaan bersifat otoritatif. Untuk berkas tanpa tag, PDFium beralih ke rosHeuristic, yang mengelompokkan karakter berdasarkan kotak pembatasnya ke dalam unit pembacaan yang masuk akal tetapi tidak dapat menjamin keakuratan
procedure ExtractStructured(Pdf: TPdf; Output: TStrings);
var
Page: Integer;
Content: TPdfReadableContent;
Fragment: TPdfContentFragment;
begin
for Page := 1 to Pdf.PageCount do
begin
Content := Pdf.ReadablePageContent(Page);
for Fragment in Content.Fragments do
begin
case Fragment.Kind of
cfHeading : Output.Add('# ' + Fragment.Text);
cfParagraph : Output.Add(Fragment.Text);
cfListItem : Output.Add('- ' + Fragment.Text);
else
Output.Add(Fragment.Text);
end;
end;
end;
end;
Jika Content.Source = rosHeuristic and output Anda terlihat rusak, lapisan teks dokumen tersebut mungkin tidak ditulis dengan memperhatikan urutan pembacaan. Pada titik itu, satu-satunya perbaikan yang andal adalah mengekspor kembali dari aplikasi sumber dengan tag yang tepat, atau menjalankan langkah pascapemrosesan yang mengurutkan asal karakter (character origins) berdasarkan Y kemudian X
Apa yang diberikan CharacterOrigin dan CharacterRectangle untuk Anda
Kedua properti mengembalikan posisi karakter di ruang halaman (poin, titik asal di sudut kiri bawah, Y meningkat ke atas). CharacterOrigin[i] adalah titik jangkar baseline glif; CharacterRectangle[i] adalah kotak pembatas (bounding box) penuh. Ini adalah blok bangunan untuk apa pun di luar teks biasa: mendeteksi batas kolom, mengelompokkan karakter ke dalam baris dengan membandingkan koordinat Y dalam toleransi tertentu, atau membangun peta uji tabrakan (hit-test map) untuk pemilihan teks dalam viewer. Jika Anda perlu menemukan karakter mana yang berada di bawah klik mouse, CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) melakukan pencarian itu secara langsung tanpa Anda harus mengulangi persegi panjang
Menempatkan DLL di tempatnya
PDFium Component mendelegasikan semua penguraian PDF ke DLL native, baik pdfium32.dll atau pdfium64.dll tergantung pada platform target Anda. Komponen menyertakan skrip CopyDlls.bat yang menyalin berkas yang tepat ke direktori sistem Windows. Menjalankannya sebagai Administrator sekali di mesin pengembangan sudah cukup; untuk penyebaran, Anda menyalin DLL tersebut di samping executable aplikasi. Varian berkemampuan V8 (pdfium32v8.dll, pdfium64v8.dll) jauh lebih besar dan hanya diperlukan jika PDF Anda berisi JavaScript yang harus dijalankan. Untuk ekstraksi teks murni, build standar adalah pilihan yang tepat
Jika DLL tidak ada saat runtime, Active := True akan gagal secara diam-diam seperti halnya untuk berkas yang hilang, karena komponen menangkap kesalahan pemuatan secara internal. Selalu uji pada mesin yang bersih sebelum mengirimkannya
Menggunakan FontSize[] bersama Character[] untuk analisis tata letak
Di luar teks biasa, API tingkat karakter mengekspos FontSize[i], yang mengembalikan ukuran poin yang dirender dari setiap glif. Dikombinasikan dengan CharacterOrigin[i] dan CharacterRectangle[i], ini memungkinkan Anda membedakan teks isi dari judul tanpa bergantung pada pohon struktur. Urutan karakter di mana ukuran font melonjak di atas ambang batas hampir pasti merupakan judul dalam dokumen tanpa tag. Teknik yang sama berlaku untuk mendeteksi teks keterangan (teks kecil di bawah kotak pembatas gambar) atau catatan kaki (teks kecil di dekat bagian bawah halaman). Semua ini tidak memerlukan rendering; ketiga properti membaca langsung dari lapisan teks yang dibangun PDFium selama Active := True
Satu nuansa: FontSize[i] mencerminkan ukuran setelah CTM (current transformation matrix) halaman diterapkan, sehingga dokumen di mana penulis menskalakan seluruh halaman akan melaporkan ukuran yang disesuaikan secara proporsional. Jika Anda membandingkan ukuran di seluruh halaman dengan dimensi halaman yang berbeda, normalisasikan terhadap tinggi MediaBox setiap halaman sebelum membuat keputusan ambang batas
Menulis output ke berkas
TStringList Delphi menangani output UTF-8 dengan bersih sejak versi XE. Setel WriteBOM := False jika Anda memerlukan berkas bebas BOM (banyak konsumen hilir terhambat oleh BOM utama):
var
Lines: TStringList;
begin
Lines := TStringList.Create;
try
ExtractAllText(Pdf, Lines);
Lines.WriteBOM := False;
Lines.SaveToFile('output.txt', TEncoding.UTF8);
finally
Lines.Free;
end;
end;
Untuk dokumen yang sangat besar di mana memori menjadi perhatian, tulislah langsung ke TStreamWriter dengan TEncoding.UTF8 di dalam loop halaman alih-alih mengumpulkan semuanya ke dalam daftar terlebih dahulu
API Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent, dan CharacterIndexAtPos yang ditunjukkan di sini adalah bagian dari PDFium Component untuk Delphi and C++Builder