O HotPDF roda o Tesseract dentro do seu processo Delphi através do HPDFCreateTesseractDLLOCREngine, uma factory acrescentada na v2.772.0 que carrega dinamicamente uma DLL compatível com Tesseract 5, comandar a C API dela (TessBaseAPIInit2, TessBaseAPIRecognize, o result iterator) e retorna um IHPDFOCREngine. O THotPDF.ApplyLoadedOCRTextLayer usa essa engine para acrescentar uma camada de texto Unicode invisível e pesquisável a páginas de PDF escaneadas
O mesmo recognizer já era alcançável pelo adapter externo tesseract.exe que grava um BMP e parseia TSV. Esse caminho funciona, mas toda página paga por um launch de processo, um arquivo de bitmap temporário e um formato de texto sem baselines e sem controle sobre segmentação de página. Chamar a DLL elimina os três. Ela também elimina a parede de processo, o que significa que um binding Pascal senta direto em cima de estruturas C, booleans C e strings alocadas em C. Boa parte do que vale saber sobre este adapter é onde esse binding pode dar errado em silêncio
Como rodar o Tesseract in-process do Delphi com o HotPDF?
Rodar o Tesseract in-process com o HotPDF leva uma chamada de factory na unit HPDFTesseractRecognition e a mesma chamada de ApplyLoadedOCRTextLayer que toda engine OCR do HotPDF usa. A factory valida com antecedência. O arquivo DLL e o diretório tessdata precisam existir, o identificador de idioma só pode conter letras ASCII, dígitos, _ e +, todo model numa combinação como chi_sim+eng precisa ter um arquivo .traineddata correspondente, e todos os 21 exports requeridos precisam resolver antes de a engine ser devolvida. Erros de configuração levantam EArgumentException; uma DLL que falha ao carregar levanta EOSError com o error code do Windows e uma dica para conferir arquitetura e dependências
uses
SysUtils, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Uma aplicação Win64 precisa de uma DLL de 64 bits; DLLs de dependência ficam ao lado dela
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
// Uma lista de páginas vazia significa todas as páginas; páginas que já têm texto são puladas
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;
O THPDFTesseractOptions.Default seta PageSegMode para tpsAuto, EngineMode para temDefault, TimeoutMilliseconds para 60.000 e MaxPixels para 16.777.216. O orçamento de pixels importa mais do que parece. Uma página US Letter no default de 300 DPI renderiza em 2.550 × 3.300 pixels, uns 8,4 milhões, o que cabe. A mesma página a 600 DPI é 5.100 × 6.600, uns 33,7 milhões, e o adapter a recusa antes de o Tesseract ver um pixel. Suba o MaxPixels (o teto é 67.108.864) ou mantenha o DPI onde está; cada lado também é limitado a 32.767 pixels
A DLL é carregada com LoadLibraryEx usando as flags de busca para a própria pasta da DLL mais os default safe directories, então as image libraries de que o Tesseract depende podem morar ao lado dela sem tocar no PATH nem no diretório atual. O HotPDF não embarca nem baixa nenhuma runtime ou model OCR; você provisiona ambos
O que muda em comparação com o adapter tesseract.exe?
O adapter de DLL troca isolamento de processo por output mais rico e overhead por página menor. Ambos os adapters se plugam no mesmo pipeline de camada de texto, então mapeamento de coordenadas, filtragem por confidence e o commit all-or-nothing são idênticos; o que difere é como os pixels entram e as palavras saem
| Aspecto | Adapter tesseract.exe | Adapter Tesseract DLL |
|---|---|---|
| Factory | HPDFCreateTesseractOCREngine | HPDFCreateTesseractDLLOCREngine |
| Pixels entrando | Arquivo BMP num diretório temporário privado | Buffer grayscale de 8 bits em memória |
| Palavras saindo | TSV em nível de palavra, limitado a 64 MiB | Result iterator, UTF-8 por palavra |
| Baselines | Não disponíveis | Passadas através do TessPageIteratorBaseline |
| Segmentação de página e engine mode | Só segmentação automática | THPDFTesseractPageSegMode, THPDFTesseractEngineMode |
| Timeout | Duro: o processo filho é terminado | Cooperativo: o Tesseract precisa notar |
| Isolamento de crash e memória | Processo separado | Nenhum, compartilha o seu address space |
Um custo não desaparece. Cada chamada de Recognize cria a própria instância de API dela e chama TessBaseAPIInit2, então os language models são inicializados por página em vez de uma vez por engine. O file cache do sistema operacional suaviza o reload, mas em conjuntos grandes de models multi-language ainda é o custo fixo dominante por página, e ele conta contra o recognition deadline. A engine de DLL RapidOCR in-process toma o design oposto e mantém os models ONNX dela residentes pelo lifetime da engine; os problemas de fronteira (C ABI, buffers emprestados, trabalho nativo ininterruptível) são da mesma família
Por que o Delphi não pode copiar a struct monitor do Tesseract?
O Delphi não pode espelhar com segurança o progress monitor do Tesseract porque o ETEXT_DESC contém campos internos dependentes de versão, então um record copiado à mão põe o cancel callback e o deadline nos offsets errados em alguns builds. Nada falha ruidosamente quando isso acontece. O Tesseract simplesmente lê o seu ponteiro de callback de um campo que agora guarda outra coisa, ou nunca vê o deadline
O HotPDF portanto trata o monitor como um ponteiro opaco e o toca só através de funções exportadas: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs e TessMonitorDelete. Se você faz o binding da C API você mesmo para outro propósito, o mesmo padrão se aplica. O esqueleto abaixo é código de binding seu, não API do HotPDF, e espelha as declarações que o 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*, nunca 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
// Roda na stack do Tesseract: leia flags e o clock, nunca levante
Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
(GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;
// Uso, com os function pointers resolvidos por GetProcAddress:
// Monitor := MonitorCreate();
// try
// MonitorSetCancelThis(Monitor, @Job);
// MonitorSetCancelFunc(Monitor, ShouldCancel);
// MonitorSetDeadlineMSecs(Monitor, RemainingMs);
// RC := BaseAPIRecognize(API, Monitor);
// finally
// MonitorDelete(Monitor);
// end;
Dois detalhes nesse esqueleto são deliberados. O callback retorna Boolean, que é um byte tanto em Delphi quanto em Free Pascal, casando com o bool de C no TessCancelFunc. O BOOL de quatro bytes do Windows ou o LongBool do Delphi parece intercambiável e não é: quando um lado escreve um único byte e o outro lê quatro, os bytes altos do return register são o que quer que tenha sobrado ali, e um false pode chegar como true. O mesmo header complica mais ainda, porque funções como TessPageIteratorBoundingBox retornam um int, que o HotPDF declara como Integer. Leia o tipo C de todo valor de retorno em vez de assumir uma convenção para a API inteira
O segundo detalhe é que o callback nunca levanta. Uma exceção Delphi fazendo unwinding através dos frames C++ do Tesseract é undefined behavior, então o callback do HotPDF só lê o cancellation token e um valor monotônico de GetTickCount64. O adapter transforma o resultado num diagnóstico de cancelamento ou de timeout depois de TessBaseAPIRecognize retornar, e ele faz essa checagem independentemente do return code nativo
Quais ponteiros nativos o lado Delphi possui?
O adapter de DLL Tesseract do HotPDF é dono de três objetos nativos por request, a instância de API, o monitor e o result iterator, e empresta todo o resto. Cada chamada de Recognize cria o próprio conjunto dela e o libera num bloco finally: TessResultIteratorDelete, depois TessMonitorDelete, depois TessBaseAPIDelete. Liberar a interface da engine descarrega a library
TessResultIteratorGetPageIteratorretorna uma view emprestada dentro do result iterator, não um objeto novo. O HotPDF o usa paraTessPageIteratorBoundingBoxeTessPageIteratorBaselinee nunca o libera; deletá-lo separadamente liberaria a mesma memória duas vezesTessResultIteratorGetUTF8Textretorna uma string alocada pela própria runtime da DLL. O HotPDF a copia e a devolve através doTessDeleteTextnum blocofinally; umFreeMemPascal a liberaria no heap errado- O texto de palavra é decodificado com validação UTF-8 estrita e checagem de comprimento antes da conversão. Palavras com caracteres de controle, UTF-8 malformado, boxes fora da imagem, retângulos invertidos ou confidence fora de 0–100 falham o request em vez de serem silenciosamente remendadas
- O texto total por request é limitado a 1.048.576 code units UTF-16, e a contagem de palavras precisa caber no budget do request handed down pelo
ApplyLoadedOCRTextLayer
A confidence chega como 0–100 e é escalada para 0–1, então o THPDFOCRTextLayerOptions.MinimumConfidence significa a mesma coisa para toda engine. Quando o Tesseract reporta uma baseline, ambos os endpoints são passados através; caso contrário o pipeline de camada de texto cai para a estimativa geométrica dele, exatamente como faz para input TSV
Por que validar um enum antes de ele chegar à DLL?
O HotPDF copia o ordinal cru de PageSegMode e EngineMode para um Integer antes de checar a faixa, porque um compilador pode assumir que uma variável enum sempre guarda um valor declarado e dobrar Ord(X) > Ord(High(T)) para uma constante false. Os ordinais não são enfeite: o THPDFTesseractPageSegMode segue a numeração de page segmentation do Tesseract de 0 a 13, o THPDFTesseractEngineMode segue a numeração de engine mode de 0 a 3, e ambos vão para a DLL como inteiros simples. Um options record construído com FillChar, preenchido a partir de um stream, ou passado do C++Builder com um integer com cast pode carregar um byte como 200. Validar o ordinal copiado transforma isso num EArgumentException na hora da factory em vez de um modo indefinido dentro de código nativo. A factory também rejeita tpsOSDOnly e tpsAutoOnly, que não produzem palavras, e exige osd.traineddata para tpsAutoOSD e tpsSparseTextOSD
O que o timeout de recognition realmente garante?
O timeout da DLL Tesseract é cooperativo: o HotPDF pode parar o próprio trabalho dele e pedir ao Tesseract que pare, mas não pode forçar código nativo a retornar. O clock começa quando o Recognize começa, então conversão de bitmap e inicialização de models consomem o mesmo orçamento que o recognition. O HotPDF confere tempo decorrido e o cancellation token durante a conversão para grayscale e entre palavras enquanto itera resultados, e passa os milissegundos restantes ao TessMonitorSetDeadlineMSecs antes de chamar TessBaseAPIRecognize
O vão está dentro da chamada nativa. O monitor do Tesseract é consultado durante o word recognition, não durante TessBaseAPIInit2 nem a análise de page layout, então um load de model lento ou um layout patológico pode passar do deadline antes de o timeout ser reportado. Os orçamentos de pixel e de output também não limitam o uso de memória da própria native library. Se você precisa de um worker que possa matar, use o adapter de processo; esse é o trade-off honesto, não uma feature que falta
Page segmentation é onde o adapter de DLL ganha o sustento dele em input difícil. Formulários, labels e tabelas escaneadas com campos espalhados costumam reconhecer melhor com tpsSparseText do que com segmentação automática, que tenta montar colunas e parágrafos que não estão lá
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 espalhados, sem montagem de colunas
TessOptions.EngineMode := temLSTMOnly; // precisa de models LSTM no tessdata
TessOptions.TimeoutMilliseconds := 20000; // inclui inicialização de models
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;
Um timeout aparece como otlsEngineError com o diagnóstico Tesseract DLL OCR timed out, enquanto um token cancelado aparece como otlsCancelled. Em ambos os casos o ApplyLoadedOCRTextLayer reconheceu toda página selecionada antes de começar a transação de commit, então uma falha na página 40 de 50 deixa o documento carregado exatamente como estava. Note que tpsSingleLine, tpsSingleBlock e tpsSparseText mudam só a segmentação; nenhum deles endireita um scan torto
Free Pascal e Lazarus: pixels obsoletos e chinês perdido
Ambas as factories de Tesseract funcionam em builds Windows Free Pascal e Lazarus Win32 e Win64 desde a v2.772.1, depois de duas correções específicas de FPC. Reconstrua o package do Lazarus para a arquitetura alvo primeiro; a port em geral está coberta em HotPDF no Free Pascal e Lazarus Win64
A primeira correção diz respeito a pixels. Um TBitmap da LCL escrito através de scanlines pode atualizar a raw image dele sem refrescar o Windows bitmap handle, então o GetDIBits naquele handle retorna os pixels velhos. O sintoma era desconcertante: texto desenhado direto num bitmap era reconhecido, enquanto uma página renderizada pelo PDF renderer do HotPDF produzia uma lista de palavras vazia. No FPC o adapter agora lê um snapshot consciente de formato através do CreateIntfImage, que respeita o pixel format e a ordem de linhas da raw image. O build Delphi mantém o caminho GetDIBits numa cópia privada de 24 bits. Nenhum dos builds modifica o bitmap do caller
A segunda correção pertence ao adapter tesseract.exe. O TStringList do FPC guarda ANSI strings, então atribuir texto TSV UTF-8 decodificado a Lines.Text descartava silenciosamente todo caractere chinês ou do supplementary plane que a ANSI code page do sistema não podia representar. O caminho FPC agora mantém o TSV como bytes UTF-8, tira o BOM em nível de byte e decodifica cada palavra para UnicodeString individualmente. O adapter de DLL nunca teve esse problema porque decodifica cada palavra direto do iterator
Referência rápida
- Factory:
HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options])naHPDFTesseractRecognition, acrescentada na v2.772.0, suporte a FPC na v2.772.1 - Defaults:
tpsAuto,temDefault, 60.000 ms, 16.777.216 pixels; faixa de timeout 1–3.600.000 ms, teto de pixels 67.108.864 - Casamento do bitness da DLL com a aplicação e colocação das DLLs de dependência ao lado da DLL Tesseract
- Trate o monitor como opaco; nunca copie
ETEXT_DESCpara um record Pascal - Declare o cancel callback
cdeclcom resultadoBooleande um byte, e nunca deixe uma exceção escapar dele - Libere texto de iterator com
TessDeleteText; nunca libere o page iterator obtido do result iterator - Espere que o deadline seja cooperativo: inicialização de models e análise de layout podem passá-lo
- Use o adapter tesseract.exe quando você precisar de terminação dura ou isolamento de crash
O adapter de DLL Tesseract, os adapters de processo e a engine OCR embutida saem todos com o componente HotPDF PDF para Delphi, C++Builder e Free Pascal; veja a página de produto do HotPDF para edições e downloads