HotPDF ejecuta Tesseract dentro de su proceso Delphi a través de HPDFCreateTesseractDLLOCREngine, una factoría añadida en v2.772.0 que carga dinámicamente una DLL compatible con Tesseract 5, maneja su API C (TessBaseAPIInit2, TessBaseAPIRecognize, el iterador de resultados) y devuelve un IHPDFOCREngine. THotPDF.ApplyLoadedOCRTextLayer usa ese motor para añadir una capa de texto Unicode invisible y buscable a páginas PDF escaneadas
El mismo reconocedor ya era alcanzable a través del adaptador tesseract.exe externo que escribe un BMP y parsea TSV. Ese camino funciona, pero cada página paga un lanzamiento de proceso, un archivo bitmap temporal y un formato de texto sin baselines y sin control sobre la segmentación de página. Llamar a la DLL elimina los tres. También elimina el muro de proceso, lo que significa que un binding Pascal se sienta directamente encima de estructuras C, booleanos C y cadenas asignadas en C. La mayor parte de lo que vale la pena saber de este adaptador es dónde ese binding puede torcerse en silencio
¿Cómo se ejecuta Tesseract en proceso desde Delphi con HotPDF?
Ejecutar Tesseract en proceso con HotPDF lleva una llamada a la factoría en la unidad HPDFTesseractRecognition y la misma llamada ApplyLoadedOCRTextLayer que usa todo motor OCR de HotPDF. La factoría valida con ansias. El archivo DLL y el directorio tessdata deben existir, el identificador de idioma solo puede contener letras ASCII, dígitos, _ y +, cada modelo de una combinación como chi_sim+eng debe tener su archivo .traineddata correspondiente, y los 21 exports requeridos deben resolverse antes de devolverse el motor. Los errores de configuración lanzan EArgumentException; una DLL que no carga lanza EOSError con el código de error de Windows y una pista de revisar arquitectura y dependencias
uses
SysUtils, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Una aplicación Win64 necesita una DLL de 64 bits; las DLL de dependencia van junto a ella
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 de páginas vacía significa todas; las que ya tienen texto se saltan
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 fija PageSegMode a tpsAuto, EngineMode a temDefault, TimeoutMilliseconds a 60.000 y MaxPixels a 16.777.216. El presupuesto de píxeles importa más de lo que parece. Una página US Letter a los 300 DPI por defecto renderiza a 2.550 × 3.300 píxeles, unos 8,4 millones, lo que cabe. La misma página a 600 DPI son 5.100 × 6.600, unos 33,7 millones, y el adaptador la rechaza antes de que Tesseract vea un píxel. Suba MaxPixels (el techo es 67.108.864) o mantenga el DPI donde está; cada lado además se topa en 32.767 píxeles
La DLL se carga con LoadLibraryEx usando los flags de búsqueda para la carpeta propia de la DLL más los directorios seguros por defecto, así que las librerías de imagen de las que depende Tesseract pueden vivir a su lado sin tocar PATH ni el directorio en curso. HotPDF no incluye ni descarga ningún runtime ni modelo OCR; usted provisiona ambos
¿Qué cambia respecto al adaptador tesseract.exe?
El adaptador de DLL cambia aislamiento de proceso por salida más rica y menos sobrecarga por página. Ambos adaptadores se enchufan al mismo pipeline de capa de texto, así que el mapeo de coordenadas, el filtrado por confianza y el commit todo-o-nada son idénticos; lo que difiere es cómo entran los píxeles y cómo salen las palabras
| Aspecto | Adaptador tesseract.exe | Adaptador DLL Tesseract |
|---|---|---|
| Factoría | HPDFCreateTesseractOCREngine | HPDFCreateTesseractDLLOCREngine |
| Píxeles que entran | Archivo BMP en un directorio temporal privado | Buffer de escala de grises de 8 bits en memoria |
| Palabras que salen | TSV a nivel de palabra, topado a 64 MiB | Iterador de resultados, UTF-8 por palabra |
| Baselines | No disponibles | Se trasladas desde TessPageIteratorBaseline |
| Segmentación de página y modo de motor | Solo segmentación automática | THPDFTesseractPageSegMode, THPDFTesseractEngineMode |
| Timeout | Duro: el proceso hijo se termina | Cooperativo: Tesseract debe darse por enterado |
| Aislamiento de crash y memoria | Proceso separado | Ninguno, comparte su espacio de direcciones |
Un coste no desaparece. Cada llamada a Recognize crea su propia instancia de API y llama a TessBaseAPIInit2, así que los modelos de idioma se inicializan por página en lugar de una vez por motor. La caché de archivos del sistema operativo suaviza la recarga, pero en conjuntos de modelos grandes multilingües sigue siendo el coste fijo dominante por página, y consume el deadline de reconocimiento. El motor de DLL RapidOCR en proceso toma el diseño opuesto y mantiene residentes sus modelos ONNX durante la vida del motor; los problemas de frontera (ABI C, buffers prestados, trabajo nativo no interrumpible) son de la misma familia
¿Por qué no puede Delphi copiar la struct monitor de Tesseract?
Delphi no puede reflejar con seguridad el monitor de progreso de Tesseract porque ETEXT_DESC contiene campos internos dependientes de la versión, así que un registro copiado a mano coloca el callback de cancelación y el deadline en offsets equivocados en algunos builds. Nada falla ruidosamente cuando eso pasa. Tesseract sencillamente lee su puntero de callback de un campo que ahora contiene otra cosa, o no ve nunca el deadline
HotPDF trata por eso el monitor como un puntero opaco y solo lo toca a través de funciones exportadas: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs y TessMonitorDelete. Si usted bindea la API C por su cuenta para otro propósito, el mismo patrón aplica. El esqueleto de abajo es su propio código de binding, no una API de HotPDF, y refleja las declaraciones que 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*, jamás desreferenciado
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
// Corre en la pila de Tesseract: lea flags y el reloj, jamás lance
Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
(GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;
// Uso, con los punteros de función resueltos por GetProcAddress:
// Monitor := MonitorCreate();
// try
// MonitorSetCancelThis(Monitor, @Job);
// MonitorSetCancelFunc(Monitor, ShouldCancel);
// MonitorSetDeadlineMSecs(Monitor, RemainingMs);
// RC := BaseAPIRecognize(API, Monitor);
// finally
// MonitorDelete(Monitor);
// end;
Dos detalles de ese esqueleto son deliberados. El callback devuelve Boolean, que es un byte tanto en Delphi como en Free Pascal, casando con el bool de C en TessCancelFunc. El BOOL de Windows de cuatro bytes o el LongBool de Delphi parece intercambiable y no lo es: cuando un lado escribe un solo byte y el otro lee cuatro, los bytes altos del registro de retorno son lo que quedara ahí, y un false puede llegar como true. La misma cabecera complica aún más las cosas, porque funciones como TessPageIteratorBoundingBox devuelven un int, que HotPDF declara como Integer. Lea el tipo C de cada valor de retorno en lugar de asumir una convención para toda la API
El segundo detalle es que el callback jamás lanza. Una excepción Delphi deshaciéndose por los frames de C++ de Tesseract es comportamiento indefinido, así que el callback de HotPDF solo lee el token de cancelación y un valor monótono de GetTickCount64. El adaptador convierte el resultado en un diagnóstico de cancelación o timeout después de que TessBaseAPIRecognize retorna, y hace ese check independientemente del código de retorno nativo
¿Qué punteros nativos posee el lado Delphi?
El adaptador de DLL Tesseract de HotPDF posee tres objetos nativos por petición, la instancia de API, el monitor y el iterador de resultados, y toma prestado todo lo demás. Cada llamada a Recognize crea su propio conjunto y lo libera en un bloque finally: TessResultIteratorDelete, luego TessMonitorDelete, luego TessBaseAPIDelete. Liberar la interfaz del motor descarga la biblioteca
TessResultIteratorGetPageIteratordevuelve una vista prestada dentro del iterador de resultados, no un objeto nuevo. HotPDF lo usa paraTessPageIteratorBoundingBoxyTessPageIteratorBaseliney jamás lo libera; borrarlo por separado liberaría la misma memoria dos vecesTessResultIteratorGetUTF8Textdevuelve una cadena asignada por el runtime propio de la DLL. HotPDF la copia y la devuelve a través deTessDeleteTexten un bloquefinally; unFreeMemde Pascal la liberaría en el heap equivocado- El texto de palabra se decodifica con validación UTF-8 estricta y comprobación de longitud antes de la conversión. Las palabras con caracteres de control, UTF-8 malformado, cajas fuera de la imagen, rectángulos invertidos o confianza fuera de 0–100 hacen fallar la petición en lugar de parchearse en silencio
- El texto total por petición se topa en 1.048.576 unidades de código UTF-16, y el recuento de palabras debe caber en el presupuesto de la petición que baja desde
ApplyLoadedOCRTextLayer
La confianza llega como 0–100 y se escala a 0–1, así que THPDFOCRTextLayerOptions.MinimumConfidence significa lo mismo para todo motor. Cuando Tesseract reporta una baseline, ambos extremos se trasladan; si no, el pipeline de la capa de texto recae en su estimación geométrica, exactamente igual que con entrada TSV
¿Por qué validar un enum antes de que llegue a la DLL?
HotPDF copia el ordinal en crudo de PageSegMode y EngineMode a un Integer antes del check de rango, porque un compilador puede asumir que una variable enum siempre contiene un valor declarado y plegar Ord(X) > Ord(High(T)) a una constante false. Los ordinales no son decoración: THPDFTesseractPageSegMode sigue la numeración de segmentación de página de Tesseract de 0 a 13, THPDFTesseractEngineMode sigue la numeración de modo de motor de 0 a 3, y ambos van a la DLL como enteros planos. Un registro de opciones construido con FillChar, rellenado desde un stream, o pasado desde C++Builder con un entero casteado puede llevar un byte como 200. Validar el ordinal copiado convierte eso en una EArgumentException en tiempo de factoría en lugar de un modo indefinido dentro del código nativo. La factoría además rechaza tpsOSDOnly y tpsAutoOnly, que no producen palabras, y exige osd.traineddata para tpsAutoOSD y tpsSparseTextOSD
¿Qué garantiza realmente el timeout de reconocimiento?
El timeout de la DLL Tesseract es cooperativo: HotPDF puede parar su propio trabajo y pedirle a Tesseract que pare, pero no puede forzar a código nativo a retornar. El reloj arranca cuando Recognize comienza, así que la conversión de bitmap y la inicialización de modelos consumen el mismo presupuesto que el reconocimiento. HotPDF comprueba el tiempo transcurrido y el token de cancelación durante la conversión a escala de grises y entre palabras mientras itera los resultados, y pasa los milisegundos restantes a TessMonitorSetDeadlineMSecs antes de llamar a TessBaseAPIRecognize
La brecha está dentro de la llamada nativa. El monitor de Tesseract se consulta durante el reconocimiento de palabras, no durante TessBaseAPIInit2 ni el análisis de layout de página, así que una carga de modelos lenta o un layout patológico puede pasarse del deadline antes de que se reporte el timeout. Los presupuestos de píxeles y de salida tampoco tapan el uso de memoria propio de la librería nativa. Si necesita un worker que pueda matar, use el adaptador de proceso; ese es el trade-off honesto, no una función ausente
La segmentación de página es donde el adaptador de DLL se gana su sueldo con entrada difícil. Formularios, etiquetas y tablas escaneadas con campos dispersos suelen reconocer mejor con tpsSparseText que con la segmentación automática, que intenta ensamblar columnas y párrafos que no están
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; // campos dispersos, sin ensamblado de columnas
TessOptions.EngineMode := temLSTMOnly; // necesita modelos LSTM en tessdata
TessOptions.TimeoutMilliseconds := 20000; // incluye la inicialización de modelos
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 como otlsEngineError con el diagnóstico Tesseract DLL OCR timed out, mientras que un token cancelado emerge como otlsCancelled. En ambos casos ApplyLoadedOCRTextLayer ha reconocido toda página seleccionada antes de arrancar la transacción de commit, así que un fallo en la página 40 de 50 deja el documento cargado exactamente como estaba. Nótese que tpsSingleLine, tpsSingleBlock y tpsSparseText cambian solo la segmentación; ninguno endereza un escaneo torcido
Free Pascal y Lazarus: píxeles caducados y chino perdido
Ambas factorías Tesseract funcionan en builds Free Pascal y Lazarus Win32 y Win64 para Windows desde v2.772.1, tras dos arreglos específicos de FPC. Recompile primero el paquete Lazarus para la arquitectura objetivo; el porte general está cubierto en HotPDF sobre Free Pascal y Lazarus Win64
El primer arreglo trata de los píxeles. Un TBitmap de LCL escrito por scanlines puede actualizar su imagen en crudo sin refrescar el handle de bitmap de Windows, así que GetDIBits sobre ese handle devuelve los píxeles viejos. El síntoma era desconcertante: el texto dibujado directamente sobre un bitmap se reconocía, mientras que una página renderizada por el renderer PDF de HotPDF producía una lista de palabras vacía. En FPC el adaptador lee ahora una instantánea consciente del formato a través de CreateIntfImage, que respeta el formato de píxel y el orden de filas de la imagen en crudo. El build Delphi conserva el camino GetDIBits sobre una copia privada de 24 bits. Ningún build modifica el bitmap del llamador
El segundo arreglo pertenece al adaptador tesseract.exe. El TStringList de FPC guarda cadenas ANSI, así que asignar texto TSV UTF-8 decodificado a Lines.Text descartaba en silencio cada carácter chino o de plano suplementario que la code page ANSI del sistema no pudiera representar. El camino FPC conserva ahora el TSV como bytes UTF-8, retira el BOM a nivel de byte y decodifica cada palabra a UnicodeString individualmente. El adaptador de DLL nunca tuvo este problema porque decodifica cada palabra directamente del iterador
Referencia rápida
- Factoría:
HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options])enHPDFTesseractRecognition, añadida en v2.772.0, soporte FPC en v2.772.1 - Valores por defecto:
tpsAuto,temDefault, 60.000 ms, 16.777.216 píxeles; rango de timeout 1–3.600.000 ms, techo de píxeles 67.108.864 - Casée el bitness de la DLL con la aplicación y coloque las DLL de dependencia junto a la DLL de Tesseract
- Trate el monitor como opaco; jamás copie
ETEXT_DESCa un registro Pascal - Declare el callback de cancelación
cdeclcon un resultadoBooleande un byte, y jamás deje escapar una excepción de él - Libere el texto del iterador con
TessDeleteText; jamás libere el iterador de página obtenido del iterador de resultados - Espere que el deadline sea cooperativo: la inicialización de modelos y el análisis de layout pueden pasarse de él
- Use el adaptador tesseract.exe cuando necesite terminación dura o aislamiento de crashes
El adaptador de DLL Tesseract, los adaptadores de proceso y el motor OCR incorporado vienen todos con el componente Delphi PDF de HotPDF para Delphi, C++Builder y Free Pascal; vea la página de producto de HotPDF para ediciones y descargas