HotPDF kører Tesseract inde i din Delphi-proces gennem HPDFCreateTesseractDLLOCREngine, en factory tilføjet i v2.772.0, der dynamisk loader en Tesseract 5-kompatibel DLL, driver dens C API (TessBaseAPIInit2, TessBaseAPIRecognize, result iterator) og returnerer en IHPDFOCREngine. THotPDF.ApplyLoadedOCRTextLayer bruger den engine til at tilføje et usynligt, søgbart Unicode-tekstlag til scannede PDF-sider
Samme recognizer var allerede tilgængelig gennem den eksterne tesseract.exe-adapter, der skriver en BMP og parser TSV. Den vej virker, men hver side betaler for et processtart, en midlertidig bitmap-fil og et tekstformat uden baselines og uden kontrol over pagesegmentering. At kalde DLL'en fjerner alle tre. Den fjerner også procesmuren, hvilket betyder, at en Pascal-binding sidder direkte oven på C-strukturer, C-booleaner og C-allokerede strenge. Det meste af det, der er værd at vide om denne adapter, er, hvor den binding stille kan gå galt
Hvordan kører du Tesseract in-process fra Delphi med HotPDF?
At køre Tesseract in-process med HotPDF tager ét factory-kald i HPDFTesseractRecognition-uniten og samme ApplyLoadedOCRTextLayer-kald, som hver HotPDF OCR-engine bruger. Factoryen validerer tidligt. DLL-filen og tessdata-mappen skal findes, sprog-identifikatoren må kun indeholde ASCII-bogstaver, cifre, _ og +, hver model i en kombination som chi_sim+eng skal have en matchende .traineddata-fil, og alle 21 påkrævede exports skal kunne opløses, før enginen returneres. Konfigurationsfejl rejser EArgumentException; en DLL, der fejler at loade, rejser EOSError med Windows-fejlkoden og et hint om at tjekke arkitektur og afhængigheder
uses
SysUtils, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// En Win64-applikation behøver en 64-bit DLL; afhængigheds-DLL'er lægges ved siden af
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
// En tom sideliste betyder alle sider; sider med tekst i forvejen skippes
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 sætter PageSegMode til tpsAuto, EngineMode til temDefault, TimeoutMilliseconds til 60.000 og MaxPixels til 16.777.216. Pixel-budgettet betyder mere, end det ser ud. En US Letter-side ved default 300 DPI renderer til 2.550 × 3.300 pixels, omkring 8,4 millioner, hvilket er plads. Samme side ved 600 DPI er 5.100 × 6.600, omkring 33,7 millioner, og adapteren afviser den, før Tesseract ser en pixel. Hæv MaxPixels (loftet er 67.108.864) eller lad DPI'en stå; hver side er også kappet ved 32.767 pixels
DLL'en loades med LoadLibraryEx med search-flaggene for DLL'ens egen folder plus default safe directories, så de billedbiblioteker, Tesseract er afhængig af, kan ligge ved siden af uden at røre PATH eller den aktuelle mappe. HotPDF bundler eller downloader intet OCR-runtime eller nogen model; du provisionerer begge
Hvad ændrer sig sammenlignet med tesseract.exe-adapteren?
DLL-adapteren bytter procesisolering til rigere output og lavere omkostning pr. side. Begge adaptere kobler sig på samme tekstlags-pipeline, så koordinatmapping, confidence-filtrering og alt-eller-intet-committet er identiske; det, der adskiller, er, hvordan pixels kommer ind, og hvordan ord kommer ud
| Aspekt | tesseract.exe-adapter | Tesseract DLL-adapter |
|---|---|---|
| Factory | HPDFCreateTesseractOCREngine | HPDFCreateTesseractDLLOCREngine |
| Pixels ind | BMP-fil i en privat midlertidig mappe | 8-bit grayscale-buffer i hukommelsen |
| Ord ud | Word-level TSV, loft 64 MiB | Result iterator, UTF-8 pr. ord |
| Baselines | Ikke tilgængelige | Givet videre fra TessPageIteratorBaseline |
| Pagesegmentering og engine mode | Kun automatisk segmentering | THPDFTesseractPageSegMode, THPDFTesseractEngineMode |
| Timeout | Hård: barneprocessen termineres | Kooperativ: Tesseract selv må bemærke det |
| Crash- og hukommelsesisolering | Separat proces | Ingen, deler dit address space |
Én omkostning forsvinder ikke. Hvert Recognize-kald opretter sin egen API-instans og kalder TessBaseAPIInit2, så sprogmodellerne initialiseres pr. side frem for én gang pr. engine. Operativsystemets filcache blødgør genindlæsningen, men på store multi-sprog-modelsæt er det stadig den dominerende faste omkostning pr. side, og det tæller med i recognition-deadlinen. In-process RapidOCR-DLL-enginen tager det modsatte design og holder sine ONNX-modeller residente i enginens levetid; grænsefladeproblemerne (C ABI, lånte buffere, uafbrydeligt native arbejde) er samme familie
Hvorfor kan Delphi ikke kopiere Tesseract monitor-strukturen?
Delphi kan ikke sikkert spejle Tesseracts progress monitor, for ETEXT_DESC indeholder versionsafhængige interne felter, så en håndkopieret record placerer cancel-callbacket og deadlinen på forkerte offsets på nogle builds. Intet fejler højtlydende, når det sker. Tesseract læser bare din callback-pointer fra et felt, der nu holder noget andet, eller ser aldrig deadlinen
HotPDF behandler derfor monitoren som en opak pointer og rører den kun gennem eksporterede funktioner: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs og TessMonitorDelete. Binder du C API'et selv til et andet formål, gælder samme mønster. Skitsen nedenfor er din egen bindingskode, ikke HotPDF API, og spejler de deklarationer, HotPDF bruger internt
type
// C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
TTessMonitorCreate = function: Pointer; cdecl; // ETEXT_DESC*, aldrig derefereret
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
// Kører på Tesseracts stak: læs flag og uret, rejse aldrig
Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
(GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;
// Anvendelse, med funktionspointerne opløst af GetProcAddress:
// Monitor := MonitorCreate();
// try
// MonitorSetCancelThis(Monitor, @Job);
// MonitorSetCancelFunc(Monitor, ShouldCancel);
// MonitorSetDeadlineMSecs(Monitor, RemainingMs);
// RC := BaseAPIRecognize(API, Monitor);
// finally
// MonitorDelete(Monitor);
// end;
To detaljer i den skitse er bevidste. Callbacket returnerer Boolean, som er én byte i både Delphi og Free Pascal og matcher C bool i TessCancelFunc. Den fire-byte Windows BOOL eller Delphi LongBool ser udskiftelig ud og er det ikke: skriver den ene side én enkelt byte, og læser den anden fire, er de øvre bytes i return-registeret, hvad end der var tilbage dér, og en false kan ankomme som true. Samme header komplicerer det yderligere, for funktioner som TessPageIteratorBoundingBox returnerer en int, som HotPDF deklarerer som Integer. Læs C-typen af hver returværdi i stedet for at antage én konvention for hele API'et
Den anden detalje er, at callbacket aldrig rejser. En Delphi-exception, der unwinder gennem Tesseracts C++-frames, er udefineret adfærd, så HotPDF's callback læser kun cancellation-tokenet og en monoton GetTickCount64-værdi. Adapteren omdanner resultatet til en cancellation- eller timeout-diagnostik, efter TessBaseAPIRecognize returnerer, og den udfører det tjek uanset den native returkode
Hvilke native pointere ejer Delphi-siden?
HotPDF's Tesseract-DLL-adapter ejer tre native objekter pr. request, API-instansen, monitoren og result iteratoren, og låner alt andet. Hvert Recognize-kald opretter sit eget sæt og frigiver det i en finally-blok: TessResultIteratorDelete, derefter TessMonitorDelete, derefter TessBaseAPIDelete. At frigive engine-interfacet unloades biblioteket
TessResultIteratorGetPageIteratorreturnerer et lånt view ind i result iteratoren, ikke et nyt objekt. HotPDF bruger det tilTessPageIteratorBoundingBoxogTessPageIteratorBaselineog frigør det aldrig; at slette det separat ville frigøre samme hukommelse to gangeTessResultIteratorGetUTF8Textreturnerer en streng allokeret af DLL'ens eget runtime. HotPDF kopierer den og giver den tilbage gennemTessDeleteTexti enfinally-blok; PascalFreeMemville frigive den på den forkerte heap- Ordtekst dekodes med strict UTF-8-validering og længdetjekkes før konvertering. Ord med kontroltegn, misdannet UTF-8, boxes uden for billedet, inverterede rektangler eller confidence uden for 0-100 fejler requesten i stedet for at blive lydløst lappet
- Samlet tekst pr. request er kappet ved 1.048.576 UTF-16 kodeenheder, og ordantallet skal passe til den request-budget,
ApplyLoadedOCRTextLayergiver videre
Confidence ankommer som 0-100 og skaleres til 0-1, så THPDFOCRTextLayerOptions.MinimumConfidence betyder det samme for hver engine. Når Tesseract melder en baseline, gives begge endepunkter videre; ellers falder tekstlags-pipelinen tilbage til sit geometriske estimat, præcis som ved TSV-input
Hvorfor validere en enum, før den når DLL'en?
HotPDF kopierer den rå ordinal af PageSegMode og EngineMode ind i en Integer, før den tjekker intervallet, for en kompiler kan antage, at en enum-variabel altid holder en deklareret værdi, og folde Ord(X) > Ord(High(T)) sammen til en konstant false. Ordinalerne er ikke pynt: THPDFTesseractPageSegMode følger Tesseracts pagesegmenterings-nummerering fra 0 til 13, THPDFTesseractEngineMode følger engine mode-nummereringen fra 0 til 3, og begge går til DLL'en som rene integers. En options-record bygget med FillChar, fyldt fra en stream eller givet videre fra C++Builder med et cast integer kan bære en byte som 200. At validere den kopierede ordinal omdanner det til en EArgumentException på factory-tidspunktet i stedet for en udefineret mode inde i native kode. Factoryen afviser desuden tpsOSDOnly og tpsAutoOnly, som ikke producerer nogen ord, og kræver osd.traineddata for tpsAutoOSD og tpsSparseTextOSD
Hvad garanterer recognition-timeouten egentlig?
Tesseract-DLL-timeouten er kooperativ: HotPDF kan stoppe sit eget arbejde og bede Tesseract om at stoppe, men den kan ikke tvinge native kode til at returnere. Uret starter, når Recognize begynder, så bitmap-konvertering og model-initialisering bruger samme budget som recognition. HotPDF tjekker forløbet tid og cancellation-tokenet under grayscale-konverteringen og mellem ord, mens den itererer resultater, og giver de resterende millisekunder til TessMonitorSetDeadlineMSecs, før den kalder TessBaseAPIRecognize
Hullet er inde i det native kald. Tesseracts monitor konsulteres under ord-genkendelse, ikke under TessBaseAPIInit2 eller side-layout-analyse, så en langsom model-load eller et patologisk layout kan løbe forbi deadlinen, før timeouten meldes. Pixel- og output-budgetter begrænser heller ikke det native biblioteks egen hukommelsesbrug. Behøver du en worker, du kan dræbe, så brug procesadapteren; det er den ærlige afvejning, ikke en manglende feature
Pagesegmentering er dér, DLL-adapteren tjener sit hold på besværligt input. Formularer, labels og scannede tabeller med spredte felter genkender ofte bedre med tpsSparseText end med automatisk segmentering, som forsøger at samle kolonner og afsnit, der ikke er der
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; // spredte felter, ingen kolonne-samling
TessOptions.EngineMode := temLSTMOnly; // behøver LSTM-modeller i tessdata
TessOptions.TimeoutMilliseconds := 20000; // inkluderer model-initialisering
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;
En timeout viser sig som otlsEngineError med diagnostikken Tesseract DLL OCR timed out, mens et annulleret token viser sig som otlsCancelled. I begge tilfælde har ApplyLoadedOCRTextLayer genkendt hver valgte side, før den starter commit-transaktionen, så en fejl på side 40 af 50 efterlader det indlæste dokument præcis, som det var. Bemærk, at tpsSingleLine, tpsSingleBlock og tpsSparseText kun ændrer segmentering; ingen af dem retter en skæv scanning op
Free Pascal og Lazarus: forældede pixels og mistet kinesisk
Begge Tesseract-factories virker i Windows Free Pascal og Lazarus Win32- og Win64-builds siden v2.772.1, efter to FPC-specifikke fixes. Genbyg Lazarus-pakken til target-arkitekturen først; den generelle port er dækket i HotPDF på Free Pascal og Lazarus Win64
Det første fix handler om pixels. En LCL TBitmap skrevet gennem scanlines kan opdatere sit raw-billede uden at genopfriske Windows bitmap-handle, så GetDIBits på den handle returnerer de gamle pixels. Symptomet var gådefuldt: tekst tegnet direkte på en bitmap blev genkendt, mens en side rendreret af HotPDF's PDF-renderer gav en tom ordliste. På FPC læser adapteren nu et formatbevidst snapshot gennem CreateIntfImage, som respekterer raw-billedets pixelformat og rækkefølge. Delphi-builden beholder GetDIBits-vejen på en privat 24-bit kopi. Ingen af buildene modificerer kalderens bitmap
Det andet fix hører til tesseract.exe-adapteren. FPC's TStringList gemmer ANSI-strenge, så at tildele dekodet UTF-8 TSV-tekst til Lines.Text droppede stille hvert kinesisk eller supplementary-plane-tegn, som systemets ANSI-kodepage ikke kunne repræsentere. FPC-vejen beholder nu TSV'en som UTF-8-bytes, stripper BOM'en på byte-niveau og dekoder hvert ord til UnicodeString hver for sig. DLL-adapteren havde aldrig dette problem, for den dekoder hvert ord direkte fra iteratoren
Hurtig reference
- Factory:
HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options])iHPDFTesseractRecognition, tilføjet i v2.772.0, FPC-understøttelse i v2.772.1 - Defaults:
tpsAuto,temDefault, 60.000 ms, 16.777.216 pixels; timeout-interval 1-3.600.000 ms, pixel-loft 67.108.864 - Match DLL-bitness til applikationen, og læg afhængigheds-DLL'er ved siden af Tesseract-DLL'en
- Behandl monitoren som opak; kopier aldrig
ETEXT_DESCind i en Pascal-record - Deklarér cancel-callbacket
cdeclmed et én-byteBoolean-resultat, og lad aldrig en exception undslippe det - Frigør iterator-tekst med
TessDeleteText; frigør aldrig side-iteratoren, der er hentet fra result iteratoren - Forvent, at deadlinen er kooperativ: model-initialisering og layout-analyse kan løbe over den
- Brug tesseract.exe-adapteren, når du behøver hård terminering eller crash-isolering
Tesseract-DLL-adapteren, procesadapterne og den indbyggede OCR-engine skiber alle med HotPDF Delphi PDF-komponenten til Delphi, C++Builder og Free Pascal; se HotPDF-produktsiden for udgaver og downloads