HotPDF mengubah halaman PDF hasil pindai menjadi PDF searchable dengan Tesseract lewat HPDFCreateTesseractOCREngine, sebuah factory yang membungkus executable Tesseract yang terpasang lokal sebagai IHPDFOCREngine. Anda menyerahkan engine itu ke ApplyLoadedOCRTextLayer, yang merender setiap halaman, menjalankan Tesseract sekali per halaman, mem-parse output TSV level kata-nya, dan meng-commit text layer Unicode tak terlihat untuk semua halaman yang diminta dalam satu transaksi, atau untuk tidak sama sekali
Alasan adapter ini ada adalah cakupan. OCR engine bawaan berbasis template matching sengaja dibuat sempit: huruf dan angka ASCII hasil cetakan mesin, tidak lebih. Faktur dengan nama beraksen, kontrak bahasa Mandarin, dan arsip multi-bahasa butuh recognizer sungguhan dengan language model terlatih, dan Tesseract adalah kandidat yang paling jelas karena ia program command-line yang bisa Anda sediakan di samping aplikasi Anda. Memanggil program eksternal dari sebuah document library terdengar sepele. Tidak sepele, dan sebagian besar kode menarik di adapter ini justru soal apa yang terjadi ketika program itu berperilaku buruk, menggantung, dibatalkan, atau mewarisi hal-hal yang tak seharusnya dilihatnya
Bagaimana HotPDF menggerakkan Tesseract dari aplikasi Delphi?
HotPDF menjalankan Tesseract sebagai proses anak tersembunyi per halaman, memberinya bitmap hasil render dan membaca kembali file TSV, dan mengekspos hasilnya lewat seam IHPDFOCREngine yang sama dengan yang dipakai engine bawaan. Tak ada yang berubah di hilir: pemetaan koordinat, penanganan rotasi, validasi Unicode, penyaringan confidence, dan commit atomik adalah pipeline text layer yang sudah Anda miliki. Factory-nya tinggal di unit HPDFTesseractRecognition dan memvalidasi dengan cerewet: executable-nya harus ada, direktori tessdata harus ada, timeout harus di antara 1 dan 3.600.000 milidetik, dan identifier bahasa hanya boleh memuat huruf ASCII, angka, _, dan +. Pemeriksaan terakhir itu penting karena string bahasa berakhir di sebuah command line, dan eng+chi_sim adalah nilai Tesseract yang sah sementara apa pun dengan kutip atau spasi bukan
uses
SysUtils, HPDFTypes, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string;
Token: THPDFCancellationToken);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// melempar EArgumentException untuk executable yang hilang, tessdata yang hilang,
// identifier bahasa yang buruk, atau timeout di luar 1..3600000 ms
Engine := HPDFCreateTesseractOCREngine(
'C:\OCR\Tesseract\tesseract.exe',
'C:\OCR\Tesseract\tessdata',
'eng+chi_sim', // beberapa model dirangkai dengan '+'
120000); // batas per halaman, bawaannya 60000
Doc := THotPDF.Create(nil);
try
Doc.AutoLaunch := False;
if Doc.LoadFromFile(SourceFile) < 1 then
raise Exception.Create('Cannot load ' + SourceFile);
Options := THPDFOCRTextLayerOptions.Default; // 300 DPI, MinimumConfidence 0.5
Options.CancellationToken := Token;
// daftar halaman kosong berarti semua halaman; halaman berteks dilewati secara bawaan
if Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
begin
Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
' words accepted, ', Info.DroppedWordCount, ' dropped');
Doc.SaveLoadedDocument(TargetFile);
end
else
case Info.Status of
otlsCancelled: Writeln('Cancelled, document unchanged');
otlsEngineError: Writeln('Engine: ', string(Info.Diagnostic));
otlsBudgetExceeded: Writeln('Budget: ', string(Info.Diagnostic));
else
Writeln(string(Info.Diagnostic));
end;
finally
Doc.Free;
end;
end;
Untuk setiap halaman, Recognize membuat direktori privat di bawah temp path bernama HotPDF-OCR-{GUID}, menyimpan bitmap hasil render sebagai input.bmp, dan meluncurkan tesseract input.bmp output --tessdata-dir … -l … --dpi N --psm 3 -c tessedit_create_tsv=1, dengan setiap argumen path dikutip memakai aturan escaping command-line Windows untuk backslash dan kutip terselip. Nilai --dpi adalah DPI render dari THPDFOCRTextLayerOptions.DPI, jadi Tesseract tak perlu menduga resolusi dari metadata gambar, dan --psm 3 meminta segmentasi halaman yang sepenuhnya otomatis. Engine ini melaporkan dirinya sebagai Tesseract (local CLI), dan itulah yang mendarat di Info.EngineName. Tesseract dan language model-nya tidak dibundel dengan HotPDF; memasangnya adalah pekerjaan aplikasi Anda
Kenapa parser TSV-nya seketat itu?
Parser TSV di HotPDF menggagalkan seluruh halaman pada baris malformed mana pun, karena daftar kata yang ter-parse sebagian menghasilkan text layer yang diam-diam berseberangan dengan gambarnya. Output TSV milik Tesseract punya header dua belas kolom yang tetap, dari level sampai text, dan HotPDF membandingkan baris pertama terhadap header persis itu setelah mencabut byte order mark opsional. Setiap baris berikutnya harus terbelah menjadi tepat dua belas field, dan pembelahan berhenti setelah tab kesebelas supaya tab di dalam teks hasil pengenalan tetap bagian dari kata alih-alih menciptakan kolom ketiga belas. Hanya baris level 5 yang berupa kata; level 1 sampai 4 menggambarkan halaman, blok, paragraf, dan baris, dan mereka dilewati. Baris level 5 yang teksnya kosong atau whitespace murni juga dilewati, karena kata kosong punya kotak tapi tak ada yang bisa dilokasi atau dicari. Sisanya diperiksa keras: geometri integer, confidence yang di-parse dengan format en-US invarian supaya locale Jerman tak membaca 93.5 sebagai sampah, kotak yang sepenuhnya berada di dalam bitmap, dan confidence antara 0 dan 100. Satu kegagalan melempar exception, engine mengembalikan False, dan array katanya dibersihkan. Regresinya mencakup persis kasus itu: satu kata valid disusul satu baris rusak harus menghasilkan nol kata, bukan satu
// dipadatkan dari loop level-5 di HPDFLocalTSVRecognition
if (Fields.Count <> 12) or not TryStrToInt(Fields[0], Level) then
raise EConvertError.Create('Invalid Local OCR TSV row');
if Level <> 5 then Continue; // baris halaman/blok/paragraf/baris
WordText := Fields[11];
if Trim(WordText) = '' then Continue; // kata whitespace tak punya posisi
if not TryStrToInt(Fields[6], X) or not TryStrToInt(Fields[7], Y) or
not TryStrToInt(Fields[8], W) or not TryStrToInt(Fields[9], H) or
not TryStrToFloat(Fields[10], Confidence, Settings) then
raise EConvertError.Create('Invalid Local OCR word geometry');
if (X < 0) or (Y < 0) or (W <= 0) or (H <= 0) or
(Int64(X) + W > Request.Bitmap.Width) or
(Int64(Y) + H > Request.Bitmap.Height) or
not ((Confidence >= 0) and (Confidence <= 100)) then
raise EConvertError.Create('Local OCR word is outside the image');
Words[Count].Confidence := Confidence / 100; // pipeline mengharapkan 0..1
Baris terakhir itu berinteraksi dengan satu bawaan yang mungkin tak Anda duga. Confidence Tesseract berjalan dari 0 sampai 100, pipeline bekerja di 0 sampai 1, dan THPDFOCRTextLayerOptions.MinimumConfidence bawaannya 0,5, jadi setiap kata Tesseract di bawah 50 terhitung di Info.DroppedWordCount dan tak pernah sampai ke halaman. Pada pindai 300 DPI yang bersih itu lantai yang wajar. Pada fax yang bising, itu bisa membuang porsi halaman yang mengejutkan, dan langkah yang benar adalah melihat jumlah yang dibuang sebelum menurunkan ambangnya, karena kata berconfidence rendah persislah yang paling mungkin salah
Apa yang diwarisi proses anak Tesseract?
Proses anak Tesseract mewarisi tepat dua handle dari HotPDF: handle NUL untuk input dan output standar, dan handle file untuk standard error. Presisi itulah intinya. CreateProcess dengan bInheritHandles = True adalah cara Anda memberi standard handle ke anak, tapi begitu saja ia meneruskan setiap handle yang bisa diwarisi di proses host, termasuk file, pipe, dan event yang dibuka kode tak berkaitan di aplikasi Anda. Anaknya lalu mempertahankan objek-objek itu hidup sampai ia keluar, jadi sebuah file tetap terkunci atau sebuah pipe tak pernah melihat ujungnya selagi Tesseract menggerus satu halaman. HotPDF menutup celah itu dengan startup record yang diperluas: STARTUPINFOEX, attribute list yang membawa PROC_THREAD_ATTRIBUTE_HANDLE_LIST, dan flag pembuatan EXTENDED_STARTUPINFO_PRESENT. Dengan handle list terpasang, bInheritHandles tetap harus True, tapi hanya handle yang terdaftar yang melintasi batasnya. Pola pikir penahanan yang sama menggerakkan mengisolasi codec gambar PDF di worker process, di mana anaknya kode tak terpercaya; di sini anaknya terpercaya, tapi host bukan pemilik tunggal tabel handle miliknya sendiri
// konstanta ditampilkan dengan namanya; sumbernya mengirim nilai numeriknya
// kedua handle dibuat dengan bInheritHandle = True
InheritedHandles[0] := NullHandle; // stdin dan stdout
InheritedHandles[1] := ErrorHandle; // stderr.txt di direktori privat
InitializeProcThreadAttributeList(Startup.AttributeList, 1, 0, AttributeBytes);
UpdateProcThreadAttribute(Startup.AttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
@InheritedHandles[0], SizeOf(InheritedHandles), nil, nil);
CreateProcess(PChar(Executable), PChar(Command), nil, nil,
True, // disyaratkan handle list
CREATE_NO_WINDOW or EXTENDED_STARTUPINFO_PRESENT,
nil, PChar(DirectoryName), Startup.StartupInfo, ProcessInfo);
Kenapa run OCR yang dibatalkan bisa tampak seperti kegagalan engine?
Run OCR yang dibatalkan tampak seperti kegagalan engine karena IHPDFOCREngine.Recognize mengembalikan satu Boolean, dan False berarti "Tesseract gagal" sekaligus "pengguna menekan Cancel". Adapter mem-polling cancellation token dan timeout setiap 25 milidetik selama anaknya berjalan, dan ketika token menyala ia melempar exception di dalam Recognize, menangkap exception miliknya sendiri, membersihkan, dan mengembalikan False dengan diagnostik. Andai pipeline memperlakukan itu sebagai error engine, pemanggil akan melihat otlsEngineError untuk pekerjaan yang sengaja dihentikan penggunanya. ApplyLoadedOCRTextLayer karena itu memeriksa token lebih dulu setiap kali Recognize mengembalikan False, dan hanya mengubah hasilnya menjadi kegagalan engine jika token tak disetel. Urutan itu menjaga kontrak multi-halaman: pengenalan, validasi, pembukuan budget, dan pembangunan konten berjalan untuk setiap halaman yang diminta sebelum transaksi graph-nya terbuka, jadi pembatalan di halaman 40 dari 50 melaporkan otlsCancelled dan membiarkan dokumennya, termasuk 39 halaman pertama, tak tersentuh. Tak ada file yang semi-searchable untuk dijelaskan belakangan, dan sisa penanganan kegagalannya mengikuti gaya berbatas yang sama:
- Timeout-nya per panggilan
Recognize, diukur dari mulainya, jadi bawaan 60.000 ms berlaku untuk setiap halaman alih-alih untuk seluruh dokumen - Anak yang masih berjalan saat timeout atau pembatalan di-terminate, ditunggu sampai 5 detik, dan direktori privatnya dihapus dalam blok
finally output.tsvdipatok 64 MiB danstderr.txt1 MiB, diperiksa selama anaknya berjalan maupun setelah ia keluar- Jumlah kata dan code unit UTF-16 dipatok per halaman oleh sisa budget
MaxWordsPerPage,MaxTotalWords, danMaxTextCodeUnits, dan melampauinya menggagalkan run alih-alih memotong daftar katanya - Standard output menuju
NULkarena Tesseract menulisoutput.tsv, sementara standard error menuju file supaya exit code bukan nol dilaporkan dengan sampai 4.096 karakter keluhan milik engine sendiri, biasanya cara tercepat mengetahui bahwa file.traineddatahilang
Bagaimana kata hasil pengenalan menjadi text layer tak terlihat
HotPDF menulis kata-kata Tesseract sebagai teks tak terlihat dengan text rendering mode 3, mode tanpa-fill-dan-tanpa-stroke yang didefinisikan ISO 32000-1 §9.3.6, jadi halaman tetap menampilkan gambar hasil pindai sementara pencarian dan penyalinan bekerja pada kata-kata hasil pengenalan. Content stream membuka BT dengan 3 Tr, dan setiap kata kebagian matriks Tm di baseline-nya, ukuran font yang diturunkan dari tinggi kotak dalam piksel pada DPI render, dan skala horizontal Tz yang meregangkan run glyph ke lebar kotak hasil ukur, dan itulah kenapa highlight pencarian mendarat di kata yang ada di gambar alih-alih melayang melewatinya
TSV milik Tesseract punya kotak tapi tak punya baseline, jadi adapter melaporkan setiap kata tanpa baseline dan pipeline mengestimasi baseline di seperlima tinggi kotak di atas tepi bawahnya. Teksnya sendiri melewati satu font Type0 bersama yang tak tertanam dengan encoding Identity-H dan CMap ToUnicode yang dibuat otomatis, satu CID per Unicode scalar berbeda di sepanjang seluruh run, dan itulah cara karakter Mandarin, Latin beraksen, dan supplementary-plane selamat di penyalinan dan pencarian. Desain itu punya dua batas yang layak disebut di depan: satu run bisa membawa paling banyak 65.535 scalar berbeda, dan font tak tertanamnya tak memenuhi syarat penyematan font dari ISO 19005, jadi output PDF/A butuh font yang patuh yang tertanam terpisah. Memeriksa hasilnya sederhana dan layak diotomasi: simpan, muat ulang, dan jalankan jalur teks dokumen-termuat yang biasa dari mengekstrak teks dari PDF termuat di Delphi; kalau kata-katanya kembali di halaman yang diharapkan, layer-nya sungguhan
RapidOCR dan engine lain di protokol TSV yang sama
HotPDF memakai ulang process runner dan parser TSV yang sama untuk RapidOCR lewat HPDFCreateRapidOCREngine(PythonExecutable, BridgeScript, ModelDirectory, TimeoutMilliseconds), yang merupakan pilihan lebih berguna untuk pindai bahasa Mandarin sederhana. Command line-nya identik kecuali path script bridge disisipkan setelah executable Python, dan bahasanya dipatok chi_sim. HotPDF mengirim bridge itu sebagai tools/OCR/rapidocr_tsv.py; ia mengharapkan paket rapidocr dan onnxruntime plus tiga model ONNX lokal, mematikan unduhan model otomatis, dan menulis TSV berbentuk Tesseract supaya sisi Delphi tak butuh parser kedua. Nama engine yang dilaporkan di Info.EngineName adalah RapidOCR (local ONNX). Bentuk itu menyarankan resep umumnya: recognizer apa pun yang bisa Anda bungkus dalam skrip kecil yang menerima daftar argumen gaya Tesseract dan mengeluarkan TSV dua belas kolom mewarisi isolasi handle, timeout, pembatalan, budget output, dan commit semua-atau-tidak sama sekali secara gratis. Adapter-nya hanya untuk Windows, menjalankan satu halaman satu waktu secara sinkron, dan tak mendekemiringkan atau memproses-ulang gambar melebihi apa yang dihasilkan renderer, jadi kualitas gambar yang masuk tetap menetapkan langit-langit apa yang keluar
Adapter Tesseract dan RapidOCR, penulis text layer tak terlihat, renderer halaman yang memberi makan keduanya, dan ekstraksi teks yang memverifikasi hasilnya semuanya ikut terkirim di komponen VCL native yang sama untuk Delphi dan C++Builder. Kalau Anda sedang menambahkan OCR ke aplikasi document capture atau arsip, HotPDF Delphi PDF component memberi Anda pipeline-nya dengan hanya engine OCR-nya sendiri yang tersisa untuk dipasang