HotPDF omdanner scannede PDF-sider til søgbar PDF med Tesseract gennem HPDFCreateTesseractOCREngine, en factory, der pakker en lokalt installeret Tesseract-eksekverbar som en IHPDFOCREngine. Du giver den engine videre til ApplyLoadedOCRTextLayer, som renderer hver side, kører Tesseract én gang pr. side, parser dets word-level TSV-output og committer et usynligt Unicode-tekstlag for alle ønskede sider i én transaktion, eller for ingen af dem
Grunden til, at adapteren findes, er scope. Den indbyggede template-matching OCR-engine er bevidst snæver: maskinskrevne ASCII-bogstaver og cifre, intet andet. Fakturaer med accenter i navnene, kinesiske kontrakter og flersprogede arkiver behøver en rigtig recognizer med trænede sprogmodeller, og Tesseract er den oplagte kandidat, fordi det er et command-line-program, du kan provisionere ved siden af din applikation. At kalde et eksternt program fra et dokumentbibliotek lyder trivielt. Det er det ikke, og det meste af den interessante kode i adapteren handler om, hvad der sker, når programmet opfører sig dårligt, hænger, bliver annulleret eller arver ting, det aldrig burde se
Hvordan driver HotPDF Tesseract fra en Delphi-applikation?
HotPDF kører Tesseract som en skjult barneproces pr. side, føder den en renderet bitmap og læser en TSV-fil tilbage og eksponerer resultatet gennem samme IHPDFOCREngine-søm, som den indbyggede engine bruger. Intet downstream ændres: koordinat-mapping, rotation-håndtering, Unicode-validering, confidence-filtrering og det atomare commit er den tekstlags-pipeline, du allerede har. Factoryen bor i HPDFTesseractRecognition-uniten og validerer tidligt: den eksekverbare skal findes, tessdata-mappen skal findes, timeouten skal ligge mellem 1 og 3.600.000 millisekunder, og sprog-identifikatoren må kun indeholde ASCII-bogstaver, cifre, _ og +. Sidstnævnte tjek betyder noget, fordi sprogstrengen ender på en command line, og eng+chi_sim er en legitim Tesseract-værdi, mens alt med anførselstegn eller mellemrum ikke er
uses
SysUtils, HPDFTypes, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string;
Token: THPDFCancellationToken);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// rejser EArgumentException for manglende eksekverbar, manglende tessdata,
// en dårlig sprog-identifikator eller en timeout uden for 1..3600000 ms
Engine := HPDFCreateTesseractOCREngine(
'C:\OCR\Tesseract\tesseract.exe',
'C:\OCR\Tesseract\tessdata',
'eng+chi_sim', // flere modeller sat sammen med '+'
120000); // grænse pr. side, default er 60000
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
Options.CancellationToken := Token;
// en tom sideliste betyder alle sider; sider med tekst skippes som default
if Doc.ApplyLoadedOCRTextLayer([], Engine, Options, Info) then
begin
Writeln(string(Info.EngineName), ': ', Info.AcceptedWordCount,
' words accepted, ', Info.DroppedWordCount, ' dropped');
Doc.SaveLoadedDocument(TargetFile);
end
else
case Info.Status of
otlsCancelled: Writeln('Cancelled, document unchanged');
otlsEngineError: Writeln('Engine: ', string(Info.Diagnostic));
otlsBudgetExceeded: Writeln('Budget: ', string(Info.Diagnostic));
else
Writeln(string(Info.Diagnostic));
end;
finally
Doc.Free;
end;
end;
For hver side opretter Recognize en privat mappe under temp-stien kaldet HotPDF-OCR-{GUID}, gemmer den renderede bitmap som input.bmp og starter tesseract input.bmp output --tessdata-dir … -l … --dpi N --psm 3 -c tessedit_create_tsv=1, med hvert sti-argument i anførselstegn efter Windows' command-line escaping-regler for backslashes og indlejrede anførselstegn. --dpi-værdien er render-DPI'en fra THPDFOCRTextLayerOptions.DPI, så Tesseract aldrig behøver at gætte opløsningen ud fra billedmetadata, og --psm 3 beder om fuldt automatisk pagesegmentering. Enginen rapporterer sig selv som Tesseract (local CLI), hvilket er det, der lander i Info.EngineName. Tesseract og dets sprogmodeller følger ikke med HotPDF; at installere dem er applikationens job
Hvorfor er TSV-parseren så streng?
TSV-parseren i HotPDF fejler hele siden ved enhver misdannet række, for en delvist parseret ordliste producerer et tekstlag, der lydløst er uenigt med billedet. Tesseracts TSV-output har en fast tolv-kolonners header fra level til text, og HotPDF sammenligner første linje med netop den header efter at have strippet en eventuel byte order mark. Hver efterfølgende række skal splittes i præcis tolv felter, og splittelsen stopper efter den ellevte tab, så en tab inde i den genkendte tekst forbliver en del af ordet i stedet for at skabe en trettende kolonne. Kun level 5-rækker er ord; level 1 til 4 beskriver sider, blokke, afsnit og linjer, og de skippes. Level 5-rækker, hvis tekst er tom eller ren whitespace, skippes også, for et tomt ord har en boks, men intet at lokalisere eller søge i. Alt andet tjekkes hårdt: heltals-geometri, en confidence parset med et invariant en-US-format, så et tysk locale ikke læser 93.5 som skrald, en boks, der ligger fuldt inde i bitmappen, og en confidence mellem 0 og 100. Én enkelt fejl rejser, enginen returnerer False, og ord-arrayet ryddes. Regressionstestene inkluderer netop det tilfælde: ét gyldigt ord efterfulgt af en ødelagt række skal give nul ord, ikke ét
// komprimeret fra level-5-løkken i HPDFLocalTSVRecognition
if (Fields.Count <> 12) or not TryStrToInt(Fields[0], Level) then
raise EConvertError.Create('Invalid Local OCR TSV row');
if Level <> 5 then Continue; // page/block/paragraph/line-rækker
WordText := Fields[11];
if Trim(WordText) = '' then Continue; // whitespace-ord har ingen position
if not TryStrToInt(Fields[6], X) or not TryStrToInt(Fields[7], Y) or
not TryStrToInt(Fields[8], W) or not TryStrToInt(Fields[9], H) or
not TryStrToFloat(Fields[10], Confidence, Settings) then
raise EConvertError.Create('Invalid Local OCR word geometry');
if (X < 0) or (Y < 0) or (W <= 0) or (H <= 0) or
(Int64(X) + W > Request.Bitmap.Width) or
(Int64(Y) + H > Request.Bitmap.Height) or
not ((Confidence >= 0) and (Confidence <= 100)) then
raise EConvertError.Create('Local OCR word is outside the image');
Words[Count].Confidence := Confidence / 100; // pipelinen forventer 0..1
Den sidste linje interagerer med en default, du måske ikke forventer. Tesseract-confidence løber fra 0 til 100, pipelinen arbejder i 0 til 1, og THPDFOCRTextLayerOptions.MinimumConfidence default'er til 0.5, så ethvert Tesseract-ord under 50 tæller med i Info.DroppedWordCount og når aldrig frem til siden. På en ren 300 DPI-scanning er det et rimeligt gulv. På en støjende fax kan det droppe en overraskende andel af siden, og det rigtige træk er at kigge på dropped-antallet, før du sænker tærsklen, for lav-confidence-ord er præcis dem, der med størst sandsynlighed er forkerte
Hvad arver Tesseract-barneprocessen?
Tesseract-barneprocessen arver præcis to handles fra HotPDF: et NUL-handle til standard input og output og et filhandle til standard error. Den præcision er pointen. CreateProcess med bInheritHandles = True er måden at give standard-handles til et child på, men alene giver den videre hvert nedarvbare handle i host-processen, inklusive filer, pipes og events åbnet af urelateret kode i din applikation. Childet holder derefter de objekter i live, til det slutter, så en fil forbliver låst, eller en pipe aldrig ser sin ende, mens Tesseract knokler sig gennem en side. HotPDF lukker det hul med en extended startup record: STARTUPINFOEX, en attributliste, der bærer PROC_THREAD_ATTRIBUTE_HANDLE_LIST, og oprettelsesflagget EXTENDED_STARTUPINFO_PRESENT. Med handle-listen på plads skal bInheritHandles stadig være True, men kun de listede handles krydser grænsen. Samme indhegningstænkning driver isolering af PDF-image-codecs i worker-processer, hvor childet er utroværdig kode; her er childet troværdig, men hosten er ikke den eneste ejer af sit eget handle-table
// konstanter vist ved navn; kilden giver deres numeriske værdier videre
// begge handles oprettes med bInheritHandle = True
InheritedHandles[0] := NullHandle; // stdin og stdout
InheritedHandles[1] := ErrorHandle; // stderr.txt i den private mappe
InitializeProcThreadAttributeList(Startup.AttributeList, 1, 0, AttributeBytes);
UpdateProcThreadAttribute(Startup.AttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
@InheritedHandles[0], SizeOf(InheritedHandles), nil, nil);
CreateProcess(PChar(Executable), PChar(Command), nil, nil,
True, // krævet af handle-listen
CREATE_NO_WINDOW or EXTENDED_STARTUPINFO_PRESENT,
nil, PChar(DirectoryName), Startup.StartupInfo, ProcessInfo);
Hvorfor kan en annulleret OCR-kørsel ligne en engine-fejl?
En annulleret OCR-kørsel ligner en engine-fejl, fordi IHPDFOCREngine.Recognize returnerer én enkelt Boolean, og False betyder både "Tesseract fejlede" og "brugeren trykkede Annuller". Adapteren poller cancellation-token og timeouten hver 25. millisekund, mens childet kører, og når tokenet fyres, rejser den inde i Recognize, fanger sin egen exception, rydder op og returnerer False med en diagnose. Behandlede pipelinen det som en engine-fejl, ville calleren se otlsEngineError for et job, brugeren bevidst stoppede. ApplyLoadedOCRTextLayer tjekker derfor tokenet først, når som helst Recognize returnerer False, og konverterer kun resultatet til en engine-fejl, hvis tokenet ikke var sat. Den rækkefølge bevarer multi-side-kontrakten: genkendelse, validering, budget-regnskab og indholdsbygning kører for hver ønsket side, før graf-transaktionen åbnes, så en annullering på side 40 af 50 rapporterer otlsCancelled og efterlader dokumentet, inklusive de første 39 sider, urørt. Der er ingen delvist søgbar fil at forklare bagefter, og resten af fejlhåndteringen følger samme afgrænsede stil:
- Timeouten er pr.
Recognize-kald, målt fra dets start, så default 60.000 ms gælder hver side snarere end hele dokumentet - Et child, der stadig kører ved timeout eller annullering, termineres, ventes på i op til 5 sekunder, og dets private mappe slettes i en
finally-blok output.tsver begrænset til 64 MiB ogstderr.txttil 1 MiB, tjekket mens childet kører samt efter det slutter- Ordantal og UTF-16 code units begrænses pr. side af de resterende
MaxWordsPerPage-,MaxTotalWords- ogMaxTextCodeUnits-budgetter, og at overskride dem fejler kørslen i stedet for at afkorte ordlisten - Standard output går til
NUL, fordi Tesseract skriveroutput.tsv, mens standard error går til en fil, så en ikke-nul exitkode rapporteres med op til 4.096 tegn af enginens egen klage, normalt den hurtigste måde at lære, at en.traineddata-fil mangler
Hvordan de genkendte ord bliver til et usynligt tekstlag
HotPDF skriver Tesseract-ord som usynlig tekst med text rendering mode 3, den hverken-fyld-eller-streg-mode, der er defineret i ISO 32000-1 §9.3.6, så siden stadig viser det scannede billede, mens søgning og kopiering arbejder på de genkendte ord. Content streamen åbner BT med 3 Tr, og hvert ord får en Tm-matrix ved sin baseline, en fontstørrelse afledt af boksens højde i pixels ved render-DPI'en og en Tz horisontal skalering, der strækker glyph-run'en til den målte boksbredde, hvilket er grunden til, at et søge-highlight lander på ordet i billedet i stedet for at drive hen over det
Tesseracts TSV har bokse men ingen baselines, så adapteren rapporterer hvert ord uden én, og pipelinen estimerer baselinjen til en femtedel af boksens højde over den nederste kant. Teksten selv går gennem en delt unembedded Type0-font med Identity-H-encoding og en genereret ToUnicode-CMap, ét CID pr. distinkt Unicode-scalar på tværs af hele run'en, hvilket er hvordan kinesisk, accentueret latin og supplementary-plane-tegn alle overlever kopiering og søgning. Det design har to grænser, der er værd at nævne med det samme: ét run kan bære højst 65.535 distinkte scalars, og den unembeddede font opfylder ikke font-embedding-kravet i ISO 19005, så PDF/A-output behøver en separat embedded conforming font. At tjekke resultatet er simpelt og værd at automatisere: gem, reload, og kør den almindelige loaded-document tekst-sti fra at udtrække tekst fra en loadet PDF i Delphi; kommer ordene tilbage på de forventede sider, er laget ægte
RapidOCR og andre enginer på samme TSV-protokol
HotPDF genbruger samme process-runner og TSV-parser til RapidOCR gennem HPDFCreateRapidOCREngine(PythonExecutable, BridgeScript, ModelDirectory, TimeoutMilliseconds), som er det mere nyttige valg til forenklet kinesiske scanninger. Command line er identisk bortset fra, at bridge-script-stien indsættes efter Python-eksekverbaren, og sproget er fastlåst til chi_sim. HotPDF leverer broen som tools/OCR/rapidocr_tsv.py; den forventer rapidocr- og onnxruntime-pakkerne plus tre lokale ONNX-modeller, deaktiverer automatiske model-downloads og skriver Tesseract-formet TSV, så Delphi-siden ikke behøver en anden parser. Enginenavnet rapporteret i Info.EngineName er RapidOCR (local ONNX). Den form antyder den generelle opskrift: Enhver recognizer, du kan pakke ind i et lille script, der accepterer argumentlisten i Tesseract-stil og udsender den tolv-kolonners TSV, arver handle-isolation, timeouten, cancellation, output-budgetterne og all-or-nothing-committet gratis. Adapterne er Windows-only, kører én side ad gangen synkront og deskewer eller preprocesser ikke billedet ud over, hvad rendereren producerer, så billedkvaliteten ind stadig sætter loftet over, hvad der kommer ud
Tesseract- og RapidOCR-adapterne, den usynlige tekstlags-writer, side-rendereren, der føder dem, og tekst-udtrækningen, der verificerer resultatet, følger alle med i samme native VCL-komponent til Delphi og C++Builder. Tilføjer du OCR til en document capture- eller arkiveringsapplikation, giver HotPDF Delphi PDF-komponenten dig pipelinen med kun selve OCR-enginen tilbage at installere