HotPDF pretvara skenirane stranice PDF-a u pretraživi PDF sa Tesseractom kroz HPDFCreateTesseractOCREngine, fabriku koja lokalno instalirani Tesseract izvršni fajl omota kao IHPDFOCREngine. Taj engine predajete ApplyLoadedOCRTextLayer-u, koji renderuje svaku stranicu, pokreće Tesseract jednom po stranici, parsira njegov word-level TSV izlaz, i posvećuje nevidljivi Unicode sloj teksta za sve zatražene stranice u jednoj transakciji, ili za nijednu
Razlog što ovaj adapter postoji je obuhvat. Ugrađeni OCR engine sa pretragom po šablonu je namerno uzak: mašinski štampana ASCII slova i cifre, ništa drugo. Fakture sa imenima sa dijakritikama, kineski ugovori i višejezične arhive trebaju pravi prepoznavac sa istreniranim jezičkim modelima, a Tesseract je očigledan kandidat jer je komandno-linijski program koji možete obezbediti pored svoje aplikacije. Zvanje spoljnog programa iz biblioteke dokumenata zvuči trivijalno. Nije, i najveći deo zanimljivog koda u adapteru je o tome šta se dešava kad se program loše ponaša, zaglavi, bude otkazan, ili nasledi stvari koje nikada ne bi trebalo da vidi
Kako HotPDF pokreće Tesseract iz Delphi aplikacije?
HotPDF pokreće Tesseract kao skriven child proces po stranici, hraneći ga renderovanom bitmapom i čitajući nazad TSV fajl, i izlaže rezultat kroz isti IHPDFOCREngine šav koji koristi ugrađeni engine. Ništa nizvodno se ne menja: mapiranje koordinata, rukovanje rotacijom, Unicode validacija, filtriranje po poverenju i atomska posveta su text-layer pipeline koji već imate. Fabrika živi u HPDFTesseractRecognition jedinici i validira unapred: izvršni fajl mora postojati, tessdata direktorijum mora postojati, tajm-aut mora biti između 1 i 3.600.000 milisekundi, a jezički identifikator sme sadržati samo ASCII slova, cifre, _ i +. Ta poslednja provera je bitna jer string jezika završi na komandnoj liniji, a eng+chi_sim je legitimna Tesseract vrednost dok sve sa navodnicima ili razmacima nije
uses
SysUtils, HPDFTypes, HPDFDoc, HPDFTesseractRecognition;
procedure MakeSearchable(const SourceFile, TargetFile: string;
Token: THPDFCancellationToken);
var
Doc: THotPDF;
Engine: IHPDFOCREngine;
Options: THPDFOCRTextLayerOptions;
Info: THPDFOCRTextLayerInfo;
begin
// podiže EArgumentException za nedostajući izvršni fajl, nedostajući tessdata,
// loš jezički identifikator ili tajm-aut van 1..3600000 ms
Engine := HPDFCreateTesseractOCREngine(
'C:\OCR\Tesseract\tesseract.exe',
'C:\OCR\Tesseract\tessdata',
'eng+chi_sim', // više modelova spojenih sa '+'
120000); // ograničenje po stranici, podrazumevano je 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;
// prazna lista stranica znači svaku stranicu; stranice sa tekstom se preskaču po podrazumevanu
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;
Za svaku stranicu, Recognize pravi privatni direktorijum pod temp putanjom imenovan HotPDF-OCR-{GUID}, čuva renderovanu bitmapu kao input.bmp, i pokreće tesseract input.bmp output --tessdata-dir … -l … --dpi N --psm 3 -c tessedit_create_tsv=1, sa svakim argumentom putanje u navodnicima po Windows pravilima bekstva komandne linije za obrnute kose crte i ugrađene navodnike. --dpi vrednost je render DPI iz THPDFOCRTextLayerOptions.DPI, pa Tesseract nikada ne mora da pogađa rezoluciju iz metapodataka slike, a --psm 3 traži potpuno automatsku segmentaciju stranice. Engine se prijavljuje kao Tesseract (local CLI), što je ono što padne u Info.EngineName. Tesseract i njegovi jezički modeli nisu upakovani sa HotPDF-om; njihova instalacija je posao aplikacije
Zašto je TSV parser tako strog?
TSV parser u HotPDF-u obara celu stranicu na svakom nepravilnom redu, jer delimično parsirana lista reči proizvodi sloj teksta koji tiho odstupa od slike. Tesseract TSV izlaz ima fiksni dvanaestokoloni header, od level do text, i HotPDF poredi prvu liniju sa tim tačnim headerom posle skidanja opcionog byte order marka. Svaki sledeći red mora da se podeli na tačno dvanaest polja, i podela staje posle jedanaestog tabulatora da tabulator unutar prepoznatog teksta ostane deo reči umesto da napravi trinaestu kolonu. Samo level 5 redovi su reči; nivoi 1 do 4 opisuju stranice, blokove, pasuse i linije, i preskaču se. Level 5 redovi čiji je tekst prazan ili čist prazan znak takođe se preskaču, jer prazna reč ima kutiju ali ništa za lociranje ili pretragu. Sve ostalo se proverava strogo: celobrojna geometrija, poverenje parsirano sa invariant en-US formatom da nemačka lokalizacija ne pročita 93.5 kao đubre, kutija koja leži potpuno unutar bitmape, i poverenje između 0 i 100. Jedan jedini pad podiže izuzetak, engine vraća False, i niz reči se briše. Regresioni testovi uključuju baš taj slučaj: jedna validna reč pa pokvaren red mora dati nula reči, ne jednu
// zbijeno iz level-5 petlje u 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; // redovi stranice/bloka/pasusa/linije
WordText := Fields[11];
if Trim(WordText) = '' then Continue; // reči od praznih znakova nemaju poziciju
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; // pipeline očekuje 0..1
Ta poslednja linija stupa u interakciju sa podrazumevanom vrednošću koju možda ne očekujete. Tesseract poverenje ide od 0 do 100, pipeline radi u 0 do 1, a THPDFOCRTextLayerOptions.MinimumConfidence podrazumeva 0,5, pa se svaka Tesseract reč ispod 50 broji u Info.DroppedWordCount i nikada ne stiže do stranice. Na čistom skenu od 300 DPI to je razumna donja granica. Na bučnom faksu može odbaciti iznenađujući deo stranice, i pravi potez je pogledati broj odbačenih pre spuštanja praga, jer su reči niskog poverenja upravo one najverovatnije pogrešne
Šta Tesseract child proces nasleđuje?
Tesseract child proces nasleđuje tačno dva handle-a od HotPDF-a: NUL handle za standardni ulaz i izlaz, i fajl handle za standardnu grešku. Ta preciznost je suština. CreateProcess sa bInheritHandles = True je način da standardne handle-ove predate detetu, ali sam po sebi predaje svaki nasledivi handle u domaćinskom procesu, uključujući fajlove, pipe-ove i događaje otvorene od strane nevezanog koda u vašoj aplikaciji. Dete zatim drži te objekte živima dok ne izađe, pa fajl ostaje zaključan ili pipe nikada ne vidi svoj kraj dok Tesseract muči stranicu. HotPDF zatvara tu rupu proširenim startup zapisom: STARTUPINFOEX, lista atributa koja nosi PROC_THREAD_ATTRIBUTE_HANDLE_LIST, i EXTENDED_STARTUPINFO_PRESENT creation flag. Sa listom handle-ova na mestu, bInheritHandles i dalje mora biti True, ali samo navedeni handle-ovi prelaze granicu. Isto razmišljanje o ograničavanju pokreće izolaciju PDF image kodeka u radnim procesima, gde je dete kod kojem se ne veruje; ovde je dete kome se veruje, ali domaćin nije jedini vlasnik svoje handle tabele
// konstante prikazane po imenu; izvor predaje njihove numeričke vrednosti
// oba handle-a su napravljena sa bInheritHandle = True
InheritedHandles[0] := NullHandle; // stdin i stdout
InheritedHandles[1] := ErrorHandle; // stderr.txt u privatnom direktorijumu
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, // obavezno zbog liste handle-ova
CREATE_NO_WINDOW or EXTENDED_STARTUPINFO_PRESENT,
nil, PChar(DirectoryName), Startup.StartupInfo, ProcessInfo);
Zašto otkazani OCR prolaz može ličiti na kvar engine-a?
Otkazani OCR prolaz liči na kvar engine-a jer IHPDFOCREngine.Recognize vraća jedan Boolean, i False znači i „Tesseract je pao” i „korisnik je pritisnuo Otkaži”. Adapter uzorkuje token otkazivanja i tajm-aut na svakih 25 milisekundi dok dete radi, i kada token okine, podiže izuzetak unutar Recognize-a, uhvati sopstveni izuzetak, počisti, i vraća False sa dijagnostikom. Da je pipeline to tretirao kao kvar engine-a, pozivalac bi video otlsEngineError za posao koji je korisnik namerno zaustavio. ApplyLoadedOCRTextLayer zato prvo proverava token kad god Recognize vrati False, i u kvar engine-a pretvara rezultat samo ako token nije bio postavljen. Taj redosled čini višestranični ugovor: prepoznavanje, validacija, računovanje budžeta i izgradnja sadržaja rade za svaku zatraženu stranicu pre nego što se grafička transakcija otvori, pa otkazivanje na stranici 40 od 50 prijavljuje otlsCancelled i ostavlja dokument, uključujući prvih 39 stranica, netaknutim. Nema delimično pretraživog fajla koji treba objašnjavati kasnije, a ostatak rukovanja kvarovima prati isti ograničeni stil:
- Tajm-aut je po
Recognizepozivu, meren od njegovog starta, pa podrazumevanih 60.000 ms važi za svaku stranicu, a ne za ceo dokument - Dete koje i dalje radi na tajm-autu ili otkazivanju se terminira, čeka se do 5 sekundi, i njegov privatni direktorijum se briše u
finallybloku output.tsvje ograničen na 64 MiB astderr.txtna 1 MiB, proverava se dok dete radi kao i posle njegovog izlaska- Broj reči i UTF-16 code jedinica ograničavaju se po stranici preostalim budžetima
MaxWordsPerPage,MaxTotalWordsiMaxTextCodeUnits, i njihovo prekoračenje obara prolaz umesto da skrati listu reči - Standardni izlaz ide u
NULjer Tesseract pišeoutput.tsv, dok standardna greška ide u fajl pa se ne-nulti exit kod prijavljuje sa do 4.096 znakova sopstvene pritužbe engine-a, obično najbrži način da saznate da.traineddatafajl nedostaje
Kako prepoznate reči postaju nevidljivi sloj teksta
HotPDF upisuje Tesseract reči kao nevidljivi tekst korišćenjem text rendering mode 3, režima ni-popuna-ni-obruba iz ISO 32000-1 §9.3.6, pa stranica i dalje pokazuje skeniranu sliku dok pretraga i kopiranje rade na prepoznatim rečima. Content stream otvara BT sa 3 Tr, i svaka reč dobija Tm matricu na svojoj baseline-i, veličinu fonta izvedenu iz visine kutije u pikselima na render DPI, i Tz horizontalno razmeranje koje razvlači niz glifova do izmerene širine kutije, pa pretragom istaknuto sedi na reči u slici umesto da luta preko nje
Tesseract TSV ima kutije ali nema baseline-e, pa adapter prijavljuje svaku reč bez nje i pipeline procenjuje baseline na petinu visine kutije iznad donje ivice. Sam tekst prolazi kroz deljeni neugrađeni Type0 font sa Identity-H enkodovanjem i generisanim ToUnicode CMap-om, jedan CID po različitom Unicode skalaru kroz ceo prolaz, po čemu kineski, latinični sa dijakritikama i karakteri dopunskog plana svi prežive kopiranje i pretragu. Taj dizajn ima dve granice koje vredi reći unapred: jedan prolaz može nositi najviše 65.535 različitih skalara, i neugrađeni font ne zadovoljava zahtev ugrađivanja fontova iz ISO 19005, pa PDF/A izlaz treba posebno ugrađen usaglašen font. Provera rezultata je prosta i vredi je automatizovati: sačuvajte, ponovo učitajte, i pokrenite običan text put učitanog dokumenta iz izvlačenja teksta iz učitanog PDF-a u Delphi-ju; ako se reči vrate na očekivanim stranicama, sloj je pravi
RapidOCR i drugi engine-ovi na istom TSV protokolu
HotPDF koristi isti pokretač procesa i TSV parser i za RapidOCR kroz HPDFCreateRapidOCREngine(PythonExecutable, BridgeScript, ModelDirectory, TimeoutMilliseconds), što je korisniji izbor za skenove na pojednostavljenom kineskom. Komandna linija je identična osim što se putanja bridge skripte ubacuje posle Python izvršnog fajla, a jezik je fiksiran na chi_sim. HotPDF isporučuje most kao tools/OCR/rapidocr_tsv.py; očekuje pakete rapidocr i onnxruntime plus tri lokalna ONNX modela, isključuje automatsko preuzimanje modela, i piše Tesseract-oblikovan TSV da Delphi strani ne treba drugi parser. Ime engine-a prijavljeno u Info.EngineName je RapidOCR (local ONNX). Taj oblik sugeriše opšti recept: svaki prepoznavac koga možete umotati u malu skriptu koja prima Tesseract-stilsku listu argumenata i emituje dvanaestokoloni TSV nasleđuje izolaciju handle-ova, tajm-aut, otkazivanje, budžete izlaza i sve-ili-ništa posvetu besplatno. Adapteri su samo za Windows, rade jednu stranicu istovremeno sinhrono, i ne ispravljaju naginjanje ni ne pripremaju sliku dalje od onoga što renderer proizvede, pa kvalitet slike na ulazu i dalje postavlja plafon onome što izlazi
Tesseract i RapidOCR adapteri, pisac nevidljivog text-layer-a, renderer stranica koji ih hrani i izvlačenje teksta koje verifikuje rezultat isporučuju se svi u istoj nativnoj VCL komponenti za Delphi i C++Builder. Ako dodajete OCR aplikaciji za snimanje ili arhiviranje dokumenata, HotPDF Delphi PDF component vam daje pipeline, uz ostalo samo da instalirate sam OCR engine