Artikel Teknis

Tesseract DLL: C API In-Process untuk OCR Delphi di HotPDF

HotPDF menjalankan Tesseract di dalam proses Delphi Anda lewat HPDFCreateTesseractDLLOCREngine, sebuah factory yang ditambahkan di v2.772.0 yang memuat secara dinamis DLL kompatibel Tesseract 5, menggerakkan C API-nya (TessBaseAPIInit2, TessBaseAPIRecognize, result iterator), dan mengembalikan sebuah IHPDFOCREngine. THotPDF.ApplyLoadedOCRTextLayer memakai engine itu untuk menambahkan text layer Unicode tak terlihat yang searchable ke halaman PDF hasil scan

Recognizer yang sama sebenarnya sudah terjangkau lewat adapter tesseract.exe eksternal yang menulis BMP dan mem-parse TSV. Jalur itu bekerja, tapi setiap halaman membayar peluncuran proses, satu file bitmap sementara, dan format teks tanpa baseline serta tanpa kendali atas page segmentation. Memanggil DLL menghapus ketiganya. Ia juga menghapus wall proses, yang berarti sebuah binding Pascal duduk langsung di atas struktur C, boolean C, dan string alokasi C. Sebagian besar hal yang layak diketahui tentang adapter ini adalah di mana binding itu bisa salah dengan senyap

Bagaimana menjalankan Tesseract in-process dari Delphi dengan HotPDF?

Menjalankan Tesseract in-process dengan HotPDF butuh satu panggilan factory di unit HPDFTesseractRecognition dan panggilan ApplyLoadedOCRTextLayer yang sama yang dipakai setiap engine OCR HotPDF. Factory-nya memvalidasi dengan serobot. File DLL dan direktori tessdata harus ada, identifier bahasa hanya boleh berisi huruf ASCII, digit, _, dan +, setiap model dalam kombinasi seperti chi_sim+eng harus punya file .traineddata yang cocok, dan semua 21 export wajib harus resolve sebelum engine dikembalikan. Kesalahan konfigurasi me-raise EArgumentException; DLL yang gagal dimuat me-raise EOSError dengan kode error Windows dan petunjuk untuk mengecek arsitektur serta dependensi

uses
  SysUtils, HPDFDoc, HPDFTesseractRecognition;

procedure MakeSearchable(const SourceFile, TargetFile: string);
var
  Doc: THotPDF;
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  // Aplikasi Win64 butuh DLL 64-bit; DLL dependensi diletakkan di sebelahnya
  Engine := HPDFCreateTesseractDLLOCREngine('C:\OCR\Win64\libtesseract-5.dll',
    'C:\OCR\tessdata', 'chi_sim+eng');   // THPDFTesseractOptions.Default
  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
    // Daftar halaman kosong berarti semua halaman; halaman yang sudah punya teks dilewati
    if not Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
      raise Exception.Create(string(Info.Diagnostic));
    Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
      ' words accepted, ', Info.DroppedWordCount, ' dropped');
    Doc.SaveLoadedDocument(TargetFile);
  finally
    Doc.Free;
  end;
end;

THPDFTesseractOptions.Default men-set PageSegMode ke tpsAuto, EngineMode ke temDefault, TimeoutMilliseconds ke 60.000, dan MaxPixels ke 16.777.216. Anggaran pikselnya lebih penting daripada kelihatannya. Halaman US Letter pada 300 DPI default terender menjadi 2.550 × 3.300 piksel, sekitar 8,4 juta, yang muat. Halaman yang sama pada 600 DPI adalah 5.100 × 6.600, sekitar 33,7 juta, dan adapter menolaknya sebelum Tesseract melihat satu piksel pun. Naikkan MaxPixels (langit-langitnya 67.108.864) atau biarkan DPI di tempatnya; tiap sisi juga dibatasi 32.767 piksel

DLL dimuat dengan LoadLibraryEx memakai flag pencarian untuk folder DLL itu sendiri plus direktori safe default, sehingga image library yang menjadi dependensi Tesseract bisa tinggal di sebelahnya tanpa menyentuh PATH atau direktori saat ini. HotPDF tak membundel atau mengunduh runtime atau model OCR apa pun; keduanya Anda provisionkan sendiri

Apa yang berubah dibandingkan adapter tesseract.exe?

Adapter DLL menukar isolasi proses dengan output yang lebih kaya dan overhead per halaman yang lebih rendah. Kedua adapter menyambung ke pipeline text layer yang sama, jadi pemetaan koordinat, filtering confidence, dan commit all-or-nothing identik; yang berbeda adalah bagaimana piksel masuk dan kata keluar

AspekAdapter tesseract.exeAdapter DLL Tesseract
FactoryHPDFCreateTesseractOCREngineHPDFCreateTesseractDLLOCREngine
Piksel masukFile BMP di direktori sementara privatBuffer grayscale 8-bit di memori
Kata keluarTSV level kata, dibatasi 64 MiBResult iterator, UTF-8 per kata
BaselineTak tersediaDiteruskan dari TessPageIteratorBaseline
Page segmentation dan engine modeHanya segmentasi otomatisTHPDFTesseractPageSegMode, THPDFTesseractEngineMode
TimeoutKeras: proses anak di-terminateKooperatif: Tesseract harus menyadarinya
Isolasi crash dan memoriProses terpisahTak ada, berbagi address space Anda

Satu biaya tak lenyap. Setiap panggilan Recognize menciptakan instance API miliknya sendiri dan memanggil TessBaseAPIInit2, sehingga language model diinisialisasi per halaman alih-alih sekali per engine. File cache sistem operasi melembutkan reload itu, tapi pada set model multi-bahasa yang besar itu tetap biaya tetap dominan per halaman, dan ia mengonsumsi deadline recognition. Engine DLL RapidOCR in-process mengambil desain sebaliknya dan menahan model ONNX-nya resident sepanjang umur engine; masalah boundary-nya (C ABI, buffer pinjaman, kerja native yang tak bisa diinterupsi) satu keluarga

Kenapa Delphi tak boleh menyalin struct monitor Tesseract?

Delphi tak bisa dengan aman me-mirror progress monitor Tesseract karena ETEXT_DESC memuat field internal yang bergantung versi, sehingga record hasil salinan tangan menempatkan cancel callback dan deadline di offset yang salah pada beberapa build. Tak ada yang gagal dengan keras ketika itu terjadi. Tesseract sekadar membaca pointer callback Anda dari field yang kini berisi hal lain, atau tak pernah melihat deadline sama sekali

HotPDF karenanya memperlakukan monitor sebagai pointer opaque dan menyentuhnya hanya lewat fungsi yang di-export: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs, dan TessMonitorDelete. Kalau Anda mem-bind C API itu sendiri untuk tujuan lain, pola yang sama berlaku. Sketsa di bawah adalah code binding milik Anda, bukan API HotPDF, dan meniru deklarasi yang dipakai HotPDF secara internal

Penanganan monitor DLL Tesseract HotPDF: menyalin record ETEXT_DESC yang bergantung versi menempatkan cancel callback dan deadline di offset yang salah dan gagal senyap, sedangkan HotPDF memperlakukan monitor sebagai opaque, menggerakkan TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc dan TessMonitorSetDeadlineMSecs, dan menjaga callback cdecl bebas exception
Satu pointer opaque plus lima export adalah seluruh kontraknya; callback tetap Boolean satu byte yang hanya membaca satu flag dan satu jam
type
  // C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
  TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
  TTessMonitorCreate = function: Pointer; cdecl;   // ETEXT_DESC*, tak pernah di-dereference
  TTessMonitorDelete = procedure(Monitor: Pointer); cdecl;
  TTessMonitorSetCancelFunc = procedure(Monitor: Pointer; Func: TTessCancelFunc); cdecl;
  TTessMonitorSetCancelThis = procedure(Monitor, CancelThis: Pointer); cdecl;
  TTessMonitorSetDeadlineMSecs = procedure(Monitor: Pointer; MSecs: Integer); cdecl;
  TTessBaseAPIRecognize = function(Handle, Monitor: Pointer): Integer; cdecl;

  TOCRJob = record
    CancelRequested: Boolean;
    DeadlineTick: UInt64;
  end;
  POCRJob = ^TOCRJob;

function ShouldCancel(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
begin
  // Jalan di stack Tesseract: baca flag dan jam, tak pernah raise
  Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
    (GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;

// Pemakaian, dengan function pointer di-resolve oleh GetProcAddress:
//   Monitor := MonitorCreate();
//   try
//     MonitorSetCancelThis(Monitor, @Job);
//     MonitorSetCancelFunc(Monitor, ShouldCancel);
//     MonitorSetDeadlineMSecs(Monitor, RemainingMs);
//     RC := BaseAPIRecognize(API, Monitor);
//   finally
//     MonitorDelete(Monitor);
//   end;

Dua detail di sketsa itu disengaja. Callback mengembalikan Boolean, yang berukuran satu byte di Delphi maupun Free Pascal, cocok dengan bool milik C di TessCancelFunc. BOOL Windows empat byte atau LongBool Delphi tampak bisa dipertukarkan dan tidak: ketika satu sisi menulis satu byte dan sisi lain membaca empat, byte-byte atas register return berisi apa pun yang tertinggal di sana, dan false bisa tiba sebagai true. Header yang sama mempersulit lebih jauh, karena fungsi seperti TessPageIteratorBoundingBox mengembalikan int, yang HotPDF deklarasikan sebagai Integer. Baca tipe C dari setiap nilai kembalian alih-alih mengasumsikan satu konvensi untuk seluruh API

Detail kedua adalah callback tak pernah me-raise. Exception Delphi yang meng-unwind melintasi frame C++ Tesseract adalah undefined behavior, jadi callback milik HotPDF hanya membaca cancellation token dan satu nilai GetTickCount64 yang monotonik. Adapter mengubah hasilnya menjadi diagnostik pembatalan atau timeout setelah TessBaseAPIRecognize kembali, dan ia melakukan pengecekan itu apa pun kode kembalian natifnya

Pointer native mana yang dimiliki sisi Delphi?

Adapter DLL Tesseract HotPDF memiliki tiga objek native per permintaan, yaitu instance API, monitor, dan result iterator, dan meminjam sisanya. Setiap panggilan Recognize menciptakan setnya sendiri dan melepaskannya dalam blok finally: TessResultIteratorDelete, lalu TessMonitorDelete, lalu TessBaseAPIDelete. Melepas interface engine meng-unload library-nya

Kepemilikan objek DLL Tesseract HotPDF per panggilan Recognize: result iterator, monitor dan instance API dimiliki dan di-free dalam urutan itu di dalam finally, page iterator dari TessResultIteratorGetPageIterator adalah view pinjaman yang tak boleh pernah di-free, dan string GetUTF8Text disalin lalu dikembalikan lewat TessDeleteText
Tiga objek dimiliki, sisanya pinjaman: free dalam urutan yang tetap, jangan pernah double-free page iterator, dan jangan pernah mencampur allocator
  • TessResultIteratorGetPageIterator mengembalikan view pinjaman ke dalam result iterator, bukan objek baru. HotPDF memakainya untuk TessPageIteratorBoundingBox dan TessPageIteratorBaseline dan tak pernah me-free-nya; menghapusnya secara terpisah akan mem-free memori yang sama dua kali
  • TessResultIteratorGetUTF8Text mengembalikan string yang dialokasikan oleh runtime milik DLL itu sendiri. HotPDF menyalinnya dan menyerahkannya kembali lewat TessDeleteText dalam blok finally; FreeMem Pascal akan melepaskannya di heap yang salah
  • Teks kata di-decode dengan validasi UTF-8 strict dan dicek panjangnya sebelum konversi. Kata dengan karakter kontrol, UTF-8 malformed, box di luar image, rectangle terbalik, atau confidence di luar 0–100 menggagalkan permintaan alih-alih diam-diam ditambal
  • Total teks per permintaan dibatasi 1.048.576 unit UTF-16, dan jumlah kata harus muat dalam anggaran permintaan yang diturunkan oleh ApplyLoadedOCRTextLayer

Confidence datang sebagai 0–100 dan diskalakan ke 0–1, sehingga THPDFOCRTextLayerOptions.MinimumConfidence bermakna sama untuk setiap engine. Ketika Tesseract melaporkan baseline, kedua ujungnya diteruskan; kalau tidak, pipeline text layer jatuh ke estimasi geometrisnya, persis seperti untuk input TSV

Kenapa memvalidasi enum sebelum ia sampai ke DLL?

HotPDF menyalin ordinal mentah dari PageSegMode dan EngineMode ke sebuah Integer sebelum range check, karena compiler boleh mengasumsikan variabel enum selalu memegang nilai yang dideklarasikan dan melipat Ord(X) > Ord(High(T)) menjadi constant false. Ordinalnya bukan hiasan: THPDFTesseractPageSegMode mengikuti penomoran page segmentation Tesseract dari 0 sampai 13, THPDFTesseractEngineMode mengikuti penomoran engine mode dari 0 sampai 3, dan keduanya menuju DLL sebagai integer polos. Record opsi yang dibangun dengan FillChar, diisi dari stream, atau diteruskan dari C++Builder dengan integer yang di-cast bisa membawa byte seperti 200. Memvalidasi ordinal hasil salinan mengubahnya menjadi EArgumentException saat factory alih-alih mode yang tak terdefinisi di dalam code native. Factory juga menolak tpsOSDOnly dan tpsAutoOnly yang tak menghasilkan kata, dan mewajibkan osd.traineddata untuk tpsAutoOSD dan tpsSparseTextOSD

Sebenarnya apa yang dijamin timeout recognition itu?

Timeout DLL Tesseract itu kooperatif: HotPDF bisa menghentikan kerjanya sendiri dan meminta Tesseract berhenti, tapi tak bisa memaksa code native untuk kembali. Jamnya mulai ketika Recognize dimulai, jadi konversi bitmap dan inisialisasi model mengonsumsi anggaran yang sama dengan recognition. HotPDF mengecek waktu berlalu dan cancellation token selama konversi grayscale dan di antara kata-kata selama mengiterasi hasil, dan meneruskan sisa milidetik ke TessMonitorSetDeadlineMSecs sebelum memanggil TessBaseAPIRecognize

Celahnya ada di dalam panggilan native. Monitor Tesseract dikonsultasikan selama recognition kata, bukan selama TessBaseAPIInit2 atau analisis layout halaman, jadi load model yang lambat atau layout yang patologis bisa berjalan melewati deadline sebelum timeout dilaporkan. Anggaran piksel dan output juga tak membatasi pemakaian memori milik library native itu sendiri. Kalau Anda butuh worker yang bisa dibunuh, pakai adapter proses; itulah trade-off yang jujur, bukan fitur yang hilang

Anatomi timeout kooperatif DLL Tesseract HotPDF: jam mulai ketika Recognize dimulai dan mencover konversi grayscale, TessBaseAPIInit2 dan analisis layout, tapi monitor hanya dikonsultasikan selama recognition kata, sehingga load model dan layout bisa melewati batas sebelum HotPDF melaporkan otlsEngineError atau otlsCancelled
Deadline di sini adalah permintaan, bukan jaminan: init dan analisis layout bisa berjalan lama, dan worker yang benar-benar bisa dibunuh butuh adapter proses

Page segmentation adalah tempat adapter DLL membayar dirinya sendiri pada input yang sulit. Formulir, label, dan tabel hasil scan dengan field yang tersebar sering lebih baik dikenali dengan tpsSparseText daripada segmentasi otomatis, yang berusaha merakit kolom dan paragraf yang tidak ada

procedure OCRFormPages(Doc: THotPDF; const Pages: array of Integer);
var
  Engine: IHPDFOCREngine;
  TessOptions: THPDFTesseractOptions;
  LayerOptions: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  TessOptions := THPDFTesseractOptions.Default;
  TessOptions.PageSegMode := tpsSparseText;  // field tersebar, tanpa perakitan kolom
  TessOptions.EngineMode := temLSTMOnly;     // butuh model LSTM di tessdata
  TessOptions.TimeoutMilliseconds := 20000;  // termasuk inisialisasi model
  Engine := HPDFCreateTesseractDLLOCREngine('C:\OCR\Win64\libtesseract-5.dll',
    'C:\OCR\tessdata', 'eng+deu', TessOptions);

  LayerOptions := THPDFOCRTextLayerOptions.Default;
  LayerOptions.MinimumConfidence := 0.6;
  if not Doc.ApplyLoadedOCRTextLayer(Pages, Engine, LayerOptions, Info) then
    case Info.Status of
      otlsCancelled:
        Writeln('OCR cancelled, document unchanged');
      otlsEngineError:
        Writeln('Tesseract failed or timed out: ', string(Info.Diagnostic));
    else
      Writeln(string(Info.Diagnostic));
    end;
end;

Timeout muncul sebagai otlsEngineError dengan diagnostik Tesseract DLL OCR timed out, sementara token yang dibatalkan muncul sebagai otlsCancelled. Dalam kedua kasus, ApplyLoadedOCRTextLayer sudah mengenali setiap halaman terpilih sebelum memulai transaksi commit-nya, jadi kegagalan di halaman 40 dari 50 membiarkan dokumen yang dimuat persis seperti semula. Perhatikan bahwa tpsSingleLine, tpsSingleBlock, dan tpsSparseText hanya mengubah segmentasi; tak satu pun meluruskan scan yang miring

Free Pascal dan Lazarus: piksel basi dan Tionghoa yang hilang

Kedua factory Tesseract bekerja di Free Pascal Windows dan build Lazarus Win32 serta Win64 sejak v2.772.1, setelah dua perbaikan khusus FPC. Bangun ulang package Lazarus untuk arsitektur target lebih dulu; port umumnya dibahas di HotPDF di Free Pascal dan Lazarus Win64

Perbaikan pertama soal piksel. LCL TBitmap yang ditulis lewat scanline bisa memperbarui raw image-nya tanpa me-refresh handle bitmap Windows, sehingga GetDIBits pada handle itu mengembalikan piksel lama. Gejalanya membingungkan: teks yang digambar langsung ke sebuah bitmap terkenali, sementara halaman yang dirender oleh PDF renderer HotPDF menghasilkan daftar kata kosong. Di FPC, adapter kini membaca snapshot yang sadar-format lewat CreateIntfImage, yang menghormati pixel format dan urutan baris raw image-nya. Build Delphi mempertahankan jalur GetDIBits pada salinan 24-bit privat. Tak ada build yang memodifikasi bitmap milik caller

Perbaikan kedua milik adapter tesseract.exe. TStringList milik FPC menyimpan string ANSI, sehingga meng-assign teks TSV hasil decode UTF-8 ke Lines.Text diam-diam membuang setiap karakter Tionghoa atau supplementary-plane yang tak bisa direpresentasikan oleh ANSI code page sistem. Jalur FPC kini menyimpan TSV sebagai byte UTF-8, membuang BOM-nya di level byte, dan men-decode tiap kata ke UnicodeString satu per satu. Adapter DLL tak pernah mengalami masalah ini karena ia men-decode tiap kata langsung dari iterator

Referensi cepat

  • Factory: HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options]) di HPDFTesseractRecognition, ditambah di v2.772.0, dukungan FPC di v2.772.1
  • Default: tpsAuto, temDefault, 60.000 ms, 16.777.216 piksel; rentang timeout 1–3.600.000 ms, langit-langit piksel 67.108.864
  • Cocokkan bitness DLL dengan aplikasi dan letakkan DLL dependensi di sebelah DLL Tesseract
  • Perlakukan monitor sebagai opaque; jangan pernah menyalin ETEXT_DESC ke record Pascal
  • Deklarasikan cancel callback cdecl dengan hasil Boolean satu byte, dan jangan pernah membiarkan exception lolos darinya
  • Free teks iterator dengan TessDeleteText; jangan pernah me-free page iterator yang didapat dari result iterator
  • Harapkan deadline-nya kooperatif: inisialisasi model dan analisis layout bisa melewatinya
  • Pakai adapter tesseract.exe ketika Anda butuh terminasi keras atau isolasi crash

Adapter DLL Tesseract, adapter proses, dan engine OCR bawaan semuanya dikirim bersama komponen PDF Delphi HotPDF untuk Delphi, C++Builder, dan Free Pascal; lihat halaman produk HotPDF untuk edisi dan unduhan