HotPDF fährt Tesseract innerhalb Ihres Delphi-Prozesses über HPDFCreateTesseractDLLOCREngine, eine in v2.772.0 hinzugekommene Factory, die dynamisch eine Tesseract-5-kompatible DLL lädt, ihre C-API antreibt (TessBaseAPIInit2, TessBaseAPIRecognize, den Result-Iterator) und ein IHPDFOCREngine zurückgibt. THotPDF.ApplyLoadedOCRTextLayer nutzt diese Engine, um gescannten PDF-Seiten eine unsichtbare, durchsuchbare Unicode-Textebene hinzuzufügen
Derselbe Recognizer war bereits über den externen tesseract.exe-Adapter, der ein BMP schreibt und TSV parst, erreichbar. Dieser Pfad funktioniert, aber jede Seite zahlt für einen Prozessstart, eine temporäre Bitmap-Datei und ein Textformat ohne Baselines und ohne Kontrolle über die Seitensegmentierung. Der DLL-Aufruf nimmt alle drei Kosten. Er nimmt auch die Prozesswand, was bedeutet, dass eine Pascal-Bindung direkt auf C-Strukturen, C-Booleans und C-allokierten Strings sitzt. Der größte Teil dessen, was an diesem Adapter wissenswert ist, betrifft die Stellen, an denen diese Bindung stillschweigend schiefgehen kann
Wie fährt man Tesseract in-process aus Delphi mit HotPDF?
Tesseract in-process mit HotPDF zu fahren braucht einen Factory-Aufruf in der Unit HPDFTesseractRecognition und denselben ApplyLoadedOCRTextLayer-Aufruf, den jede HotPDF-OCR-Engine benutzt. Die Factory validiert eifrig. Die DLL-Datei und das tessdata-Verzeichnis müssen existieren, der Sprachbezeichner darf nur ASCII-Buchstaben, Ziffern, _ und + enthalten, jedes Modell in einer Kombination wie chi_sim+eng muss eine passende .traineddata-Datei haben, und alle 21 benötigten Exporte müssen auflösbar sein, bevor die Engine zurückgegeben wird. Konfigurationsfehler werfen EArgumentException; eine DLL, die nicht lädt, wirft EOSError mit dem Windows-Fehlercode und dem Hinweis, Architektur und Abhängigkeiten zu prüfen
uses
SysUtils, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// Eine Win64-Anwendung braucht eine 64-Bit-DLL; Abhängigkeits-DLLs daneben
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
// Eine leere Seitenliste bedeutet jede Seite; Seiten mit vorhandenem Text werden übersprungen
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 setzt PageSegMode auf tpsAuto, EngineMode auf temDefault, TimeoutMilliseconds auf 60.000 und MaxPixels auf 16.777.216. Das Pixelbudget zählt mehr, als es aussieht. Eine US-Letter-Seite bei den Default-300 DPI rendert zu 2.550 × 3.300 Pixeln, rund 8,4 Millionen, das passt. Dieselbe Seite bei 600 DPI ist 5.100 × 6.600, rund 33,7 Millionen, und der Adapter weist sie ab, bevor Tesseract ein Pixel sieht. Heben Sie MaxPixels an (die Obergrenze ist 67.108.864) oder lassen Sie die DPI, wo sie ist; jede Seite ist zudem auf 32.767 Pixel begrenzt
Die DLL wird mit LoadLibraryEx geladen, mit den Suchflags für den eigenen Ordner der DLL plus die Standard-Safe-Verzeichnisse, die Bildbibliotheken, von denen Tesseract abhängt, können also neben ihr leben, ohne PATH oder das aktuelle Verzeichnis anzufassen. HotPDF bündelt oder lädt keinerlei OCR-Runtime oder Modell herunter; beides stellen Sie bereit
Was ändert sich gegenüber dem tesseract.exe-Adapter?
Der DLL-Adapter tauscht Prozessisolation gegen reichhaltigere Ausgabe und weniger Overhead pro Seite. Beide Adapter docken an dieselbe Textebenen-Pipeline an, Koordinatenmapping, Confidence-Filterung und der Alles-oder-Nichts-Commit sind also identisch; unterschiedlich ist, wie Pixel hinein- und Wörter herauskommen
| Aspekt | tesseract.exe-Adapter | Tesseract-DLL-Adapter |
|---|---|---|
| Factory | HPDFCreateTesseractOCREngine | HPDFCreateTesseractDLLOCREngine |
| Pixel hinein | BMP-Datei in einem privaten temporären Verzeichnis | 8-Bit-Graustufenpuffer im Speicher |
| Wörter heraus | Wortlevel-TSV, begrenzt auf 64 MiB | Result-Iterator, UTF-8 pro Wort |
| Baselines | Nicht verfügbar | Durchgereicht aus TessPageIteratorBaseline |
| Seitensegmentierung und Engine-Modus | Nur automatische Segmentierung | THPDFTesseractPageSegMode, THPDFTesseractEngineMode |
| Timeout | Hart: Der Kindprozess wird terminiert | Kooperativ: Tesseract muss es bemerken |
| Absturz- und Speicherisolation | Getrennter Prozess | Keine, teilt Ihren Adressraum |
Ein Kostenposten verschwindet nicht. Jeder Recognize-Aufruf erzeugt seine eigene API-Instanz und ruft TessBaseAPIInit2 auf, die Sprachmodelle werden also pro Seite initialisiert statt einmal pro Engine. Der Dateicache des Betriebssystems mildert das Nachladen, aber bei großen mehrsprachigen Modellsets bleibt es der dominierende Fixkostenposten pro Seite, und er zählt gegen die Erkennungsfrist. Die In-Process-RapidOCR-DLL-Engine nimmt das entgegengesetzte Design und hält ihre ONNX-Modelle für die Lebensdauer der Engine resident; die Grenzprobleme (C-ABI, geliehene Puffer, ununterbrechbare native Arbeit) sind dieselbe Familie
Warum kann Delphi die Tesseract-Monitor-Struktur nicht kopieren?
Delphi kann den Tesseract-Fortschrittsmonitor nicht sicher spiegeln, denn ETEXT_DESC enthält versionsabhängige interne Felder, ein von Hand kopierter Record legt den Cancel-Callback und die Frist auf manchen Builds also an die falschen Offsets. Nichts scheitert laut, wenn das passiert. Tesseract liest Ihren Callback-Zeiger schlicht aus einem Feld, das inzwischen etwas anderes hält, oder sieht die Frist überhaupt nie
HotPDF behandelt den Monitor deshalb als opaken Zeiger und fasst ihn nur über exportierte Funktionen an: TessMonitorCreate, TessMonitorSetCancelThis, TessMonitorSetCancelFunc, TessMonitorSetDeadlineMSecs und TessMonitorDelete. Binden Sie die C-API selbst für einen anderen Zweck, gilt dasselbe Muster. Die Skizze unten ist Ihr eigener Bindungscode, keine HotPDF-API, und spiegelt die Deklarationen, die HotPDF intern nutzt
type
// C: typedef bool (*TessCancelFunc)(void *cancel_this, int words);
TTessCancelFunc = function(CancelThis: Pointer; Words: Integer): Boolean; cdecl;
TTessMonitorCreate = function: Pointer; cdecl; // ETEXT_DESC*, nie dereferenziert
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
// Läuft auf Tesseracts Stack: Flags und die Uhr lesen, niemals werfen
Result := (CancelThis = nil) or POCRJob(CancelThis)^.CancelRequested or
(GetTickCount64 >= POCRJob(CancelThis)^.DeadlineTick);
end;
// Verwendung, mit den per GetProcAddress aufgelösten Funktionszeigern:
// Monitor := MonitorCreate();
// try
// MonitorSetCancelThis(Monitor, @Job);
// MonitorSetCancelFunc(Monitor, ShouldCancel);
// MonitorSetDeadlineMSecs(Monitor, RemainingMs);
// RC := BaseAPIRecognize(API, Monitor);
// finally
// MonitorDelete(Monitor);
// end;
Zwei Details in dieser Skizze sind Absicht. Der Callback gibt Boolean zurück, was in Delphi und Free Pascal ein Byte ist und zum C-bool in TessCancelFunc passt. Das vier Byte große Windows-BOOL oder das Delphi-LongBool sieht austauschbar aus und ist es nicht: Schreibt die eine Seite ein einzelnes Byte und liest die andere vier, sind die oberen Bytes des Rückgaberegisters, was auch immer dort lag, und ein False kann als True ankommen. Derselbe Header verkompliziert Dinge zusätzlich, denn Funktionen wie TessPageIteratorBoundingBox geben ein int zurück, das HotPDF als Integer deklariert. Lesen Sie den C-Typ jedes Rückgabewerts, statt eine Konvention für die ganze API anzunehmen
Das zweite Detail ist, dass der Callback nie wirft. Eine Delphi-Exception, die durch Tesseracts C++-Frames unwinde, ist undefiniertes Verhalten, HotPDFs Callback liest also nur das Abbruchtoken und einen monotonen GetTickCount64-Wert. Der Adapter macht aus dem Ergebnis nach der Rückkehr von TessBaseAPIRecognize eine Abbruch- oder Timeout-Diagnose, und er führt diese Prüfung unabhängig vom nativen Rückgabecode durch
Welche nativen Zeiger besitzt die Delphi-Seite?
Der HotPDF-Tesseract-DLL-Adapter besitzt drei native Objekte pro Anfrage — die API-Instanz, den Monitor und den Result-Iterator — und leiht alles andere. Jeder Recognize-Aufruf erzeugt sein eigenes Set und gibt es in einem finally-Block frei: TessResultIteratorDelete, dann TessMonitorDelete, dann TessBaseAPIDelete. Die Engine-Schnittstelle freizugeben entlädt die Bibliothek
TessResultIteratorGetPageIteratorliefert eine geliehene Ansicht in den Result-Iterator, kein neues Objekt. HotPDF nutzt sie fürTessPageIteratorBoundingBoxundTessPageIteratorBaselineund gibt sie nie frei; sie separat zu löschen würde denselben Speicher zweimal freigebenTessResultIteratorGetUTF8Textliefert einen String, der von der eigenen Runtime der DLL allokiert wurde. HotPDF kopiert ihn und reicht ihn überTessDeleteTextin einemfinally-Block zurück; Pascal-FreeMemwürde ihn auf dem falschen Heap freigeben- Worttext wird mit strenger UTF-8-Validierung dekodiert und vor der Konversion längengeprüft. Wörter mit Steuerzeichen, missgebildetem UTF-8, Boxen außerhalb des Bilds, invertierten Rechtecken oder Confidence außerhalb 0–100 lassen die Anfrage scheitern, statt stillschweigend geflickt zu werden
- Der Gesamttext pro Anfrage ist auf 1.048.576 UTF-16-Code-Einheiten begrenzt, und die Wortanzahl muss ins Anfragebudget passen, das
ApplyLoadedOCRTextLayerweiterreicht
Confidence kommt als 0–100 an und wird auf 0–1 skaliert, THPDFOCRTextLayerOptions.MinimumConfidence bedeutet also für jede Engine dasselbe. Meldet Tesseract eine Baseline, werden beide Endpunkte durchgereicht; andernfalls fällt die Textebenen-Pipeline auf ihre geometrische Schätzung zurück, exakt wie bei TSV-Eingabe
Warum ein Enum validieren, bevor er die DLL erreicht?
HotPDF kopiert den rohen Ordinalwert von PageSegMode und EngineMode in einen Integer, bevor es den Bereich prüft, denn ein Compiler darf annehmen, dass eine Enum-Variable stets einen deklarierten Wert hält, und Ord(X) > Ord(High(T)) zu einem konstanten False falten. Die Ordinalwerte sind keine Dekoration: THPDFTesseractPageSegMode folgt Tesseracts Seitensegmentierungsnummerierung von 0 bis 13, THPDFTesseractEngineMode folgt der Engine-Modus-Nummerierung von 0 bis 3, und beide gehen als schlichte Integer an die DLL. Ein mit FillChar gebauter Options-Record, aus einem Stream gefüllt oder aus C++Builder mit gecastetem Integer übergeben, kann ein Byte wie 200 tragen. Den kopierten Ordinalwert zu validieren macht daraus eine EArgumentException zur Factory-Zeit statt eines undefinierten Modus in nativem Code. Die Factory weist zudem tpsOSDOnly und tpsAutoOnly ab, die keine Wörter produzieren, und verlangt osd.traineddata für tpsAutoOSD und tpsSparseTextOSD
Was garantiert die Erkennungs-Timeout tatsächlich?
Das Tesseract-DLL-Timeout ist kooperativ: HotPDF kann seine eigene Arbeit stoppen und Tesseract bitten, zu stoppen, aber es kann nativen Code nicht zur Rückkehr zwingen. Die Uhr startet, wenn Recognize beginnt, Bitmap-Konversion und Modellinitialisierung verbrauchen also dasselbe Budget wie die Erkennung. HotPDF prüft verstrichene Zeit und das Abbruchtoken während der Graustufenkonversion und zwischen Wörtern beim Iterieren der Ergebnisse und reicht die verbleibenden Millisekunden an TessMonitorSetDeadlineMSecs, bevor es TessBaseAPIRecognize aufruft
Die Lücke liegt innerhalb des nativen Aufrufs. Tesseracts Monitor wird während der Wörterkennung konsultiert, nicht während TessBaseAPIInit2 oder der Seitenlayoutanalyse, ein langsames Modellladen oder ein pathologisches Layout kann also über die Frist hinauslaufen, bevor das Timeout gemeldet wird. Pixel- und Ausgabebudgets begrenzen zudem nicht den eigenen Speicherverbrauch der nativen Bibliothek. Brauchen Sie einen Worker, den Sie killen können, nehmen Sie den Prozessadapter; das ist der ehrliche Kompromiss, kein fehlendes Feature
Die Seitensegmentierung ist der Ort, an dem der DLL-Adapter sich bei schwieriger Eingabe bezahlt macht. Formulare, Labels und gescannte Tabellen mit verstreuten Feldern erkennen mit tpsSparseText oft besser als mit automatischer Segmentierung, die versucht, Spalten und Absätze zusammenzusetzen, die gar nicht da sind
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; // verstreute Felder, keine Spaltenzusammensetzung
TessOptions.EngineMode := temLSTMOnly; // braucht LSTM-Modelle im tessdata
TessOptions.TimeoutMilliseconds := 20000; // eingeschlossen Modellinitialisierung
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;
Ein Timeout zeigt sich als otlsEngineError mit der Diagnose Tesseract DLL OCR timed out, ein abgebrochenes Token als otlsCancelled. In beiden Fällen hat ApplyLoadedOCRTextLayer jede gewählte Seite erkannt, bevor es die Commit-Transaktion startet, ein Fehlschlag auf Seite 40 von 50 lässt das geladene Dokument also exakt so, wie es war. Beachten Sie, dass tpsSingleLine, tpsSingleBlock und tpsSparseText nur die Segmentierung ändern; keiner davon richtet einen schiefen Scan gerade
Free Pascal und Lazarus: veraltete Pixel und verlorenes Chinesisch
Beide Tesseract-Factorys funktionieren in Windows-Free-Pascal- und Lazarus-Win32- und Win64-Builds seit v2.772.1, nach zwei FPC-spezifischen Fixes. Bauen Sie das Lazarus-Paket zuerst für die Zielarchitektur neu; der allgemeine Port ist in HotPDF auf Free Pascal und Lazarus Win64 behandelt
Der erste Fix betrifft Pixel. Ein über Scanlines beschriebenes LCL-TBitmap kann sein rohes Bild aktualisieren, ohne das Windows-Bitmap-Handle aufzufrischen, GetDIBits auf diesem Handle liefert also die alten Pixel. Das Symptom war rätselhaft: Direkt auf eine Bitmap gezeichneter Text wurde erkannt, während eine vom PDF-Renderer von HotPDF gerenderte Seite eine leere Wortliste produzierte. Auf FPC liest der Adapter jetzt einen formatbewussten Snapshot über CreateIntfImage, der das Pixelformat und die Zeilenreihenfolge des rohen Bilds respektiert. Der Delphi-Build behält den GetDIBits-Pfad auf einer privaten 24-Bit-Kopie. Keiner der Builds verändert die Bitmap des Aufrufers
Der zweite Fix gehört zum tesseract.exe-Adapter. FPCs TStringList speichert ANSI-Strings, die Zuweisung dekodierten UTF-8-TSV-Texts an Lines.Text verlor also stillschweigend jedes chinesische oder Supplementary-Plane-Zeichen, das die System-ANSI-Codepage nicht darstellen konnte. Der FPC-Pfad hält den TSV jetzt als UTF-8-Bytes, entfernt die BOM auf Byteebene und dekodiert jedes Wort einzeln zu UnicodeString. Der DLL-Adapter hatte dieses Problem nie, denn er dekodiert jedes Wort direkt aus dem Iterator
Kurzreferenz
- Factory:
HPDFCreateTesseractDLLOCREngine(LibraryPath, TessDataDirectory, Language[, Options])inHPDFTesseractRecognition, hinzugekommen in v2.772.0, FPC-Unterstützung in v2.772.1 - Defaults:
tpsAuto,temDefault, 60.000 ms, 16.777.216 Pixel; Timeout-Bereich 1–3.600.000 ms, Pixel-Obergrenze 67.108.864 - Passen Sie die DLL-Bitness an die Anwendung an und legen Sie Abhängigkeits-DLLs neben die Tesseract-DLL
- Behandeln Sie den Monitor als opak; kopieren Sie
ETEXT_DESCnie in einen Pascal-Record - Deklarieren Sie den Cancel-Callback
cdeclmit einem Ein-Byte-Boolean-Ergebnis, und lassen Sie nie eine Exception aus ihm entkommen - Geben Sie Iteratortext mit
TessDeleteTextfrei; geben Sie den aus dem Result-Iterator gewonnenen Page-Iterator nie frei - Rechnen Sie damit, dass die Frist kooperativ ist: Modellinitialisierung und Layoutanalyse können über sie hinauslaufen
- Nehmen Sie den tesseract.exe-Adapter, wenn Sie harte Terminierung oder Absturzisolation brauchen
Der Tesseract-DLL-Adapter, die Prozessadapter und die eingebaute OCR-Engine kommen alle mit der HotPDF-Delphi-PDF-Komponente für Delphi, C++Builder und Free Pascal; Editionen und Downloads finden Sie auf der HotPDF-Produktseite