HotPDF fa girare Tesseract dentro il tuo processo Delphi attraverso HPDFCreateTesseractDLLOCREngine, una factory aggiunta nella v2.772.0 che carica dinamicamente una DLL compatibile Tesseract 5, pilota la sua C API (TessBaseAPIInit2, TessBaseAPIRecognize, l'iteratore dei risultati) e restituisce un IHPDFOCREngine. THotPDF.ApplyLoadedOCRTextLayer usa quel motore per aggiungere un livello di testo Unicode invisibile e ricercabile alle pagine PDF scansionate
Lo stesso recognizer era già raggiungibile attraverso l'adapter esterno tesseract.exe che scrive una BMP e analizza il TSV. Quella via funziona, ma ogni pagina paga un avvio di processo, un file bitmap temporaneo e un formato di testo senza baseline e senza controllo sulla segmentazione di pagina. Chiamare la DLL rimuove tutti e tre. Rimuove anche il muro del processo, il che significa che un binding Pascal sta dritto sopra strutture C, booleani C e stringhe allocate in C. Gran parte di ciò che vale la pena sapere su questo adapter è dove quel binding può andare storto in silenzio
Come fai girare Tesseract in-process da Delphi con HotPDF?
Far girare Tesseract in-process con HotPDF richiede una chiamata alla factory nell'unità HPDFTesseractRecognition e la stessa chiamata ApplyLoadedOCRTextLayer che usa ogni motore OCR di HotPDF. La factory valida con eager. Il file DLL e la directory tessdata devono esistere, l'identificatore di lingua può contenere solo lettere ASCII, cifre, _ e +, ogni modello in una combinazione come chi_sim+eng deve avere un file .traineddata corrispondente, e tutti i 21 export richiesti devono risolversi prima che il motore venga restituito. Gli errori di configurazione sollevano EArgumentException; una DLL che non carica solleva EOSError con il codice di errore Windows e un suggerimento di controllare architettura e dipendenze
uses
SysUtils, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Un'applicazione Win64 richiede una DLL a 64 bit; le DLL di dipendenza stanno accanto
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
// Una lista pagine vuota significa tutte; le pagine con testo vengono saltate
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 imposta PageSegMode a tpsAuto, EngineMode a temDefault, TimeoutMilliseconds a 60.000 e MaxPixels a 16.777.216. Il budget di pixel conta più di quanto sembri. Una pagina US Letter alla default di 300 DPI si renderizza in 2.550 × 3.300 pixel, circa 8,4 milioni, che ci stanno. La stessa pagina a 600 DPI è 5.100 × 6.600, circa 33,7 milioni, e l'adapter la rifiuta prima che Tesseract veda un pixel. Alza MaxPixels (il tetto è 67.108.864) o tieni il DPI dov'è; ogni lato è limitato anche a 32.767 pixel
La DLL viene caricata con LoadLibraryEx usando i flag di ricerca per la cartella della DLL stessa più le directory sicure di default, così le librerie immagini di cui Tesseract dipende possono stare accanto a lei senza toccare PATH o la directory corrente. HotPDF non include né scarica alcun runtime o modello OCR; provisioni entrambi tu
Che cosa cambia rispetto all'adapter tesseract.exe?
L'adapter DLL scambia l'isolamento di processo con un output più ricco e un overhead per pagina più basso. Entrambi gli adapter si innestano nella stessa pipeline del livello di testo, quindi mappatura delle coordinate, filtro di confidenza e la scrittura tutto-o-nulla sono identici; ciò che differisce è come entrano i pixel e come escono le parole
| Aspetto | Adapter tesseract.exe | Adapter DLL Tesseract |
|---|---|---|
| Factory | HPDFCreateTesseractOCREngine | HPDFCreateTesseractDLLOCREngine |
| Pixel in ingresso | File BMP in una directory temporanea privata | Buffer grayscale a 8 bit in memoria |
| Parole in uscita | TSV a livello parola, limitato a 64 MiB | Iteratore dei risultati, UTF-8 per parola |
| Baseline | Non disponibili | Trasmessi da TessPageIteratorBaseline |
| Segmentazione di pagina e modalità motore | Solo segmentazione automatica | THPDFTesseractPageSegMode, THPDFTesseractEngineMode |
| Timeout | Duro: il processo figlio viene terminato | Cooperativo: Tesseract deve accorgersene |
| Isolamento di crash e memoria | Processo separato | Nessuno, condivide il tuo spazio di indirizzi |
Un costo non sparisce. Ogni chiamata Recognize crea la propria istanza API e chiama TessBaseAPIInit2, quindi i modelli linguistici vengono inizializzati per pagina anziché una volta per motore. La cache dei file del sistema operativo ammorbidisce il ricaricamento, ma su grandi insiemi di modelli multilingua resta comunque il costo fisso dominante per pagina, e va a carico della scadenza di riconoscimento. Il motore della DLL RapidOCR in-process prende il progetto opposto e tiene i propri modelli ONNX residenti per tutta la vita del motore; i problemi di confine (C ABI, buffer in prestito, lavoro nativo non interrompibile) sono della stessa famiglia
Perché Delphi non può copiare la struct monitor di Tesseract?
Delphi non può replicare in sicurezza il monitor di avanzamento di Tesseract perché ETEXT_DESC contiene campi interni dipendenti dalla versione, quindi un record copiato a mano mette il callback di cancel e la scadenza agli offset sbagliati su alcune build. Niente fallisce rumorosamente quando succede. Tesseract semplicemente legge il tuo puntatore al callback da un campo che ora contiene altro, o non vede mai la scadenza
HotPDF quindi tratta il monitor come un puntatore opaco e lo tocca solo attraverso funzioni esportate: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs e TessMonitorDelete. Se bindi tu la C API per un altro scopo, lo stesso pattern si applica. Lo schizzo qui sotto è il tuo codice di binding, non API HotPDF, e rispecchia le dichiarazioni che HotPDF usa internamente
type
// C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
TTessMonitorCreate = function: Pointer; cdecl; // ETEXT_DESC*, mai dereferenziato
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
// Gira sullo stack di Tesseract: leggi flag e orologio, non sollevare mai
Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
(GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;
// Uso, con i puntatori a funzione risolti da GetProcAddress:
// Monitor := MonitorCreate();
// try
// MonitorSetCancelThis(Monitor, @Job);
// MonitorSetCancelFunc(Monitor, ShouldCancel);
// MonitorSetDeadlineMSecs(Monitor, RemainingMs);
// RC := BaseAPIRecognize(API, Monitor);
// finally
// MonitorDelete(Monitor);
// end;
Due dettagli in quello schizzo sono deliberati. Il callback restituisce Boolean, che è un byte sia in Delphi sia in Free Pascal, corrispondente al bool C in TessCancelFunc. Il BOOL Windows a quattro byte o il LongBool Delphi sembra intercambiabile e non lo è: quando un lato scrive un singolo byte e l'altro ne legge quattro, i byte alti del registro di ritorno sono ciò che vi era rimasto, e un false può arrivare come true. Lo stesso header complica ulteriormente le cose, perché funzioni come TessPageIteratorBoundingBox restituiscono un int, che HotPDF dichiara come Integer. Leggi il tipo C di ogni valore di ritorno invece di presumere una convenzione per tutta l'API
Il secondo dettaglio è che il callback non solleva mai. Un'eccezione Delphi che fa unwinding attraverso i frame C++ di Tesseract è comportamento indefinito, quindi il callback di HotPDF legge solo il token di cancellazione e un valore monotono GetTickCount64. L'adapter trasforma il risultato in una diagnostica di cancellazione o timeout dopo che TessBaseAPIRecognize torna, e esegue quel controllo indipendentemente dal codice di ritorno nativo
Quali puntatori nativi possiede il lato Delphi?
L'adapter DLL Tesseract di HotPDF possiede tre oggetti nativi per richiesta, l'istanza API, il monitor e l'iteratore dei risultati, e prende in prestito tutto il resto. Ogni chiamata Recognize crea il proprio insieme e lo rilascia in un blocco finally: TessResultIteratorDelete, poi TessMonitorDelete, poi TessBaseAPIDelete. Rilasciare l'interfaccia del motore scarica la libreria
TessResultIteratorGetPageIteratorrestituisce una vista in prestito dentro l'iteratore dei risultati, non un nuovo oggetto. HotPDF la usa perTessPageIteratorBoundingBoxeTessPageIteratorBaselinee non la libera mai; cancellarla separatamente libererebbe la stessa memoria due volteTessResultIteratorGetUTF8Textrestituisce una stringa allocata dal runtime della DLL stessa. HotPDF la copia e la restituisce attraversoTessDeleteTextin un bloccofinally; unaFreeMemPascal la rilascerebbe sull'heap sbagliato- Il testo delle parole viene decodificato con validazione UTF-8 strict e controllo di lunghezza prima della conversione. Le parole con caratteri di controllo, UTF-8 malformato, box fuori dall'immagine, rettangoli invertiti o confidenza fuori da 0–100 fanno fallire la richiesta invece di venire sistemate in silenzio
- Il testo totale per richiesta è limitato a 1.048.576 code unit UTF-16, e il conteggio di parole deve stare nel budget della richiesta consegnato da
ApplyLoadedOCRTextLayer
La confidenza arriva come 0–100 e viene scalata a 0–1, quindi THPDFOCRTextLayerOptions.MinimumConfidence significa la stessa cosa per ogni motore. Quando Tesseract riporta una baseline, entrambi gli estremi vengono trasmessi; altrimenti la pipeline del livello di testo ripiega sulla propria stima geometrica, esattamente come fa per l'input TSV
Perché validare un enum prima che raggiunga la DLL?
HotPDF copia l'ordinale grezzo di PageSegMode e EngineMode in una Integer prima del controllo di intervallo, perché un compilatore può presumere che una variabile enum tenga sempre un valore dichiarato e ridurre Ord(X) > Ord(High(T)) a una costante false. Gli ordinali non sono decorazione: THPDFTesseractPageSegMode segue la numerazione di segmentazione pagina di Tesseract da 0 a 13, THPDFTesseractEngineMode segue la numerazione delle modalità motore da 0 a 3, e entrambi vanno alla DLL come interi semplici. Un record di opzioni costruito con FillChar, riempito da uno stream, o passato da C++Builder con un intero castato può portare un byte come 200. Validare l'ordinale copiato trasforma quello in una EArgumentException al tempo della factory invece di una modalità indefinita dentro codice nativo. La factory rifiuta anche tpsOSDOnly e tpsAutoOnly, che non producono parole, e richiede osd.traineddata per tpsAutoOSD e tpsSparseTextOSD
Che cosa garantisce davvero il timeout di riconoscimento?
Il timeout della DLL Tesseract è cooperativo: HotPDF può fermare il proprio lavoro e chiedere a Tesseract di fermarsi, ma non può forzare il codice nativo a tornare. L'orologio parte quando Recognize comincia, quindi la conversione bitmap e l'inizializzazione dei modelli consumano lo stesso budget del riconoscimento. HotPDF controlla il tempo trascorso e il token di cancellazione durante la conversione in grayscale e tra le parole mentre itera i risultati, e passa i millisecondi rimanenti a TessMonitorSetDeadlineMSecs prima di chiamare TessBaseAPIRecognize
Il varco sta dentro la chiamata nativa. Il monitor di Tesseract viene consultato durante il riconoscimento delle parole, non durante TessBaseAPIInit2 o l'analisi del layout di pagina, così un caricamento lento di modello o un layout patologico può superare la scadenza prima che il timeout venga riportato. I budget di pixel e output inoltre non limitano l'uso di memoria della libreria nativa stessa. Se ti serve un worker che puoi uccidere, usa l'adapter di processo; è il compromesso onesto, non una funzionalità mancante
La segmentazione di pagina è dove l'adapter DLL si ripaga sull'input difficile. Moduli, etichette e tabelle scansionate con campi sparsi spesso riconoscono meglio con tpsSparseText che con la segmentazione automatica, che prova ad assemblare colonne e paragrafi che non ci sono
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; // campi sparsi, nessun assemblaggio colonne
TessOptions.EngineMode := temLSTMOnly; // richiede modelli LSTM in tessdata
TessOptions.TimeoutMilliseconds := 20000; // include l'inizializzazione dei modelli
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;
Un timeout emerge come otlsEngineError con la diagnostica Tesseract DLL OCR timed out, mentre un token cancellato emerge come otlsCancelled. In entrambi i casi ApplyLoadedOCRTextLayer ha riconosciuto ogni pagina selezionata prima di iniziare la transazione di scrittura, quindi un fallimento alla pagina 40 di 50 lascia il documento caricato esattamente com'era. Nota che tpsSingleLine, tpsSingleBlock e tpsSparseText cambiano solo la segmentazione; nessuna di esse raddrizza una scansione storta
Free Pascal e Lazarus: pixel stanti e cinese perduto
Entrambe le factory Tesseract funzionano nelle build Windows Free Pascal e Lazarus Win32 e Win64 dalla v2.772.1, dopo due correzioni specifiche FPC. Ricompila prima il pacchetto Lazarus per l'architettura bersaglio; il porting generale è coperto in HotPDF su Free Pascal e Lazarus Win64
La prima correzione riguarda i pixel. Una TBitmap LCL scritta attraverso scanline può aggiornare la propria immagine grezza senza rinfrescare l'handle bitmap Windows, così GetDIBits su quell'handle restituisce i vecchi pixel. Il sintomo era sconcertante: il testo disegnato direttamente su una bitmap veniva riconosciuto, mentre una pagina renderizzata dal renderer PDF di HotPDF produceva una lista parole vuota. Su FPC l'adapter ora legge uno snapshot consapevole del formato attraverso CreateIntfImage, che rispetta il formato pixel e l'ordine delle righe dell'immagine grezza. La build Delphi conserva la via GetDIBits su una copia privata a 24 bit. Nessuna delle due build modifica la bitmap del chiamante
La seconda correzione appartiene all'adapter tesseract.exe. La TStringList di FPC conserva stringhe ANSI, quindi assegnare il testo TSV decodificato UTF-8 a Lines.Text scartava in silenzio ogni carattere cinese o del piano supplementare che la code page ANSI di sistema non poteva rappresentare. La via FPC ora tiene il TSV come byte UTF-8, toglie il BOM a livello di byte e decodifica ogni parola a UnicodeString singolarmente. L'adapter DLL non ha mai avuto questo problema perché decodifica ogni parola direttamente dall'iteratore
Riferimento rapido
- Factory:
HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options])inHPDFTesseractRecognition, aggiunta nella v2.772.0, supporto FPC nella v2.772.1 - Default:
tpsAuto,temDefault, 60.000 ms, 16.777.216 pixel; intervallo di timeout 1–3.600.000 ms, tetto pixel 67.108.864 - Abbina la bitness della DLL all'applicazione e colloca le DLL di dipendenza accanto alla DLL Tesseract
- Tratta il monitor come opaco; non copiare mai
ETEXT_DESCin un record Pascal - Dichiara il callback di cancel
cdeclcon un risultatoBooleanda un byte, e non lasciare mai che un'eccezione gli sfugga - Libera il testo dell'iteratore con
TessDeleteText; non liberare mai l'iteratore di pagina ottenuto dall'iteratore dei risultati - Aspettati che la scadenza sia cooperativa: l'inizializzazione dei modelli e l'analisi del layout possono sforarla
- Usa l'adapter tesseract.exe quando ti serve terminazione dura o isolamento dai crash
L'adapter DLL Tesseract, gli adapter di processo e il motore OCR integrato viaggiano tutti con il componente HotPDF Delphi per Delphi, C++Builder e Free Pascal; vedi la pagina prodotto HotPDF per edizioni e download