PDFium Component menambahkan lapisan teks yang dapat dicari ke halaman PDF hasil scan dari Delphi melalui ApplyOcrSearchLayer. Ia merender setiap halaman yang dipilih, menyerahkan piksel ke provider OCR yang Anda sediakan, dan menulis kembali kata yang dikenali sebagai objek teks tidak terlihat yang diposisikan di atas kata-kata dalam scan tersebut. Gambar halaman asli tidak pernah didekode, dienkode ulang, atau diganti, sehingga hasil visualnya adalah halaman yang sama byte demi byte dengan yang Anda mulai
Mesin pengenalan dengan sengaja bukan bagian dari library ini. PDFium mengekspos rendering halaman, pemetaan koordinat, pemuatan font, pembuatan objek teks, dan mode render tidak terlihat, tetapi tidak mengandung mesin OCR, dan berpura-pura sebaliknya akan berarti membundel produk pengenalan milik orang lain ke dalam sebuah komponen PDF. Sebaliknya, pengenalan berada di balik interface IPdfOcrProvider: library meneruskan piksel BGRA top-origin dengan layout tetap, dan provider mengembalikan teks Unicode, nilai confidence, dan quadrilateral kata
Apa sebenarnya lapisan teks yang dapat dicari itu?
PDF hasil scan adalah gambar dari sebuah dokumen. Konten halamannya adalah satu gambar besar, dan tidak ada apa pun yang bisa dipilih, dicari, disalin, atau diindeks. Lapisan teks yang dapat dicari menambahkan objek teks sungguhan di atas gambar tersebut dengan render mode diatur ke tidak terlihat, sehingga penampil tidak menggambar apa pun tetapi pemilihan, pencarian, dan ekstraksi menemukan kata-kata tepat di tempat kemunculannya
Positioning adalah seluruh intinya. Jika teks tidak terlihat itu bergeser beberapa point, highlight pemilihan mendarat di samping kata-kata alih-alih tepat di atasnya, dan menyalin sebuah paragraf menghasilkan teks dengan urutan yang salah. Inilah sebabnya geometrinya harus berasal dari transformasi yang sama yang digunakan PDFium untuk merender halaman, bukan dari tebakan proporsional
Mengimplementasikan provider
Kontrak provider hanya satu metode. Ia menerima rekaman gambar halaman yang membawa dimensi, stride, DPI, format piksel, dan byte piksel itu sendiri, plus token pembatalan, dan mengembalikan kata-kata atau pesan error:
uses
PDFium;
type
TMyOcrProvider = class(TInterfacedObject, IPdfOcrProvider)
public
function RecognizePage(const Image: TPdfOcrImage;
const CancellationToken: IPdfCancellationToken;
out Words: TPdfOcrWords; out ErrorMessage: string): Boolean;
end;
function TMyOcrProvider.RecognizePage(const Image: TPdfOcrImage;
const CancellationToken: IPdfCancellationToken;
out Words: TPdfOcrWords; out ErrorMessage: string): Boolean;
var
I: Integer;
begin
// Image.Pixels menyimpan baris BGRA top-origin sepanjang Image.Stride byte.
// Serahkan ke engine Anda, lalu isi satu entri per kata yang dikenali
SetLength(Words, RecognisedCount);
for I := 0 to RecognisedCount - 1 do
begin
Words[I].Text := EngineWordText(I);
Words[I].Confidence := EngineWordConfidence(I); // 0..1
Words[I].Quad := TPdfOcrQuad.FromRectangle(
EngineLeft(I), EngineTop(I), EngineRight(I), EngineBottom(I));
end;
ErrorMessage := '';
Result := True;
end;
Quad, bukan rectangle, karena scan jarang persis siku terhadap halaman. Sebuah kata pada halaman yang sedikit miring menempati jajaran genjang, dan TPdfOcrQuad membawa empat titik sudut sehingga kata yang miring dan berotasi tetap mendapat region pemilihan yang akurat. Engine yang hanya melaporkan kotak axis-aligned dapat menggunakan FromRectangle, yang membangun quad degenerate
Mengapa posisi kata tidak dapat diskalakan secara proporsional?
Ada godaan untuk mengonversi koordinat piksel ke koordinat halaman dengan membaginya dengan lebar render dan mengalikannya dengan lebar halaman. Itu hanya berhasil untuk halaman tanpa rotasi, CropBox yang identik dengan MediaBox, dan origin di titik nol, dan banyak dokumen hasil scan gagal memenuhi setidaknya satu dari kondisi tersebut
PDFium Component memetakan setiap dari empat sudut quad secara individual melalui FPDF_DeviceToPage, pemetaan yang sama yang digunakan renderer untuk menghasilkan piksel, sehingga entri /Rotate dan crop box yang tergeser ditangani secara konstruksi. Matriks affine untuk objek teks kemudian dibangun dari tiga titik yang telah dipetakan, sudut kiri-bawah, kanan-bawah, dan kiri-atas, yang tepat cukup untuk menyatakan posisi, skala, rotasi, dan shear
Objek teks itu sendiri dibuat dengan ukuran font unit sehingga batas font sesungguhnya dapat diukur, dan batas objek yang terukur itu kemudian dipetakan ke quad target. Menentukan ukuran dengan point size yang ditebak dan berharap cocok dengan kata hasil scan akan melenceng seiring setiap substitusi font; mengukur lebih dulu membuat kecocokan tersebut tidak bergantung pada font apa yang digunakan lapisan itu
Menjalankannya pada seluruh dokumen
Rekaman opsi mengendalikan resolusi, filtering, dan setiap budget. Filtering confidence lebih penting daripada yang terlihat: kata sampah pada confidence rendah mencemari hasil pencarian secara permanen, dan berbeda dengan rendering yang salah, tidak ada yang menyadarinya sampai sebuah pencarian mengembalikan omong kosong:
var
Pdf: TPdf;
Options: TPdfOcrOptions;
Report: TPdfOcrReport;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'scanned-contract.pdf';
Pdf.LoadDocument;
Options := TPdfOcrOptions.Default;
Options.Dpi := 300; // resolusi pengenalan
Options.MinConfidence := 0.60; // buang kata yang tidak pasti
Options.SkipPagesWithText := True; // biarkan halaman born-digital tetap utuh
Options.ContinueOnError := True; // satu halaman buruk tidak boleh menghentikan job
Options.MaxPixelsPerPage := 40 * 1000 * 1000;
if Pdf.ApplyOcrSearchLayer(TMyOcrProvider.Create, Options, Report) then
Pdf.SaveAs('scanned-contract-searchable.pdf');
for I := 0 to High(Report.Pages) do
if Report.Pages[I].Status = popsFailed then
Writeln(Format('page %d failed: %s',
[Report.Pages[I].PageNumber, Report.Pages[I].ErrorMessage]));
Writeln(Format('%d word(s) inserted, %d rejected, %d page(s) skipped',
[Report.InsertedWordCount, Report.RejectedWordCount,
Report.SkippedPageCount]));
finally
Pdf.Free;
end;
end;
SkipPagesWithText layak ditekankan pada arsip campuran. PDF yang sudah membawa teks sungguhan, baik born digital maupun yang sebelumnya sudah diproses, akan mendapat lapisan teks kedua jika Anda menjalankan OCR di atasnya secara membabi buta, dan duplikat itu membuat ekstraksi mengembalikan setiap kata dua kali. Status per halaman popsSkippedExistingText memberitahu Anda tepat halaman mana yang dibiarkan utuh
Budget, pembatalan, dan penahanan kegagalan
Setiap kuantitas yang dapat digelembungkan dokumen yang jahat atau sekadar sangat besar memiliki batas atas: piksel per halaman dan total, kata per halaman dan total, serta karakter per kata. Semuanya diperiksa sebelum halaman ditulis, bukan sesudahnya, dan estimasi piksel dihitung dari dimensi halaman dan DPI sebelum bitmap apa pun dialokasikan. Menaikkan DPI dari 150 ke 300 melipatempatkan memori per halaman, sehingga batas per halaman adalah parameter pertama yang perlu disetel ketika job batch mulai gagal pada format besar
Token pembatalan menjalar sepanjang seluruh jalur: rendering progresif, panggilan provider, dan loop penyisipan per kata. Itu berarti pengguna yang membatalkan selama pengenalan berkas 400 halaman berhenti dalam satu halaman alih-alih di akhir dokumen, dan pola token yang sama yang digunakan di tempat lain dalam komponen ini, dijelaskan dalam rendering progresif yang dapat dibatalkan, berlaku di sini tanpa perubahan
Penahanan kegagalan bersifat per halaman. Library mengumpulkan handle objek yang disisipkannya pada sebuah halaman dan memanggil FPDFPage_GenerateContent sekali, setelah semua kata ditempatkan. Jika ada yang gagal di tengah jalan, baik error provider maupun masalah font, objek yang disisipkan pada halaman itu dihapus dalam urutan terbalik dan konten halaman diregenerasi, sehingga halaman yang gagal kembali ke keadaan aslinya alih-alih menyimpan lapisan teks yang setengah jadi. Loop dokumen kemudian melanjutkan atau berhenti sesuai ContinueOnError, dan halaman aktif selalu dipulihkan
Memverifikasi bahwa gambar benar-benar tidak tersentuh
Pemeriksaan terkuat yang tersedia juga yang paling sederhana: render halaman sebelum dan sesudah menerapkan lapisan pada ukuran yang sama dan bandingkan bitmap-nya. Keduanya seharusnya identik byte demi byte, karena teks tidak terlihat tidak menggambar apa pun dan stream gambar tidak pernah didekode. Perbedaan apa pun berarti ada sesuatu selain lapisan teks yang mengubah halaman
Setelah itu, verifikasi sisi teksnya dengan mengekstraksi dari berkas yang telah diproses dan mengonfirmasi bahwa posisi kata mendarat di atas scan-nya. Jalur ekstraksi sama dengan yang dijelaskan dalam mengekstraksi teks dari dokumen PDF, dan untuk pemeriksaan visual cepat atas penyelarasan, merender halaman ke gambar seperti dalam mengonversi halaman PDF ke JPEG memungkinkan Anda melapisi kotak kata di atas scan-nya
Layering OCR, rendering, ekstraksi, dan editing semuanya berjalan terhadap objek dokumen yang sama di Delphi, C++Builder, dan Lazarus; permukaan API lengkapnya dijelaskan pada halaman PDFium Component untuk Delphi