HotPDF pretvara skenirane PDF stranice u pretraživi PDF s Tesseractom kroz HPDFCreateTesseractOCREngine, tvornicu koja lokalno instaliranu Tesseract executivu zamota kao IHPDFOCREngine. Taj engine predajete ApplyLoadedOCRTextLayer, koji renderira svaku stranicu, pokreće Tesseract jednom po stranici, parsira njegov TSV izlaz na razini riječi, i potvrđuje nevidljivi Unicode tekstualni sloj za sve zatražene stranice u jednoj transakciji, ili ni za jednu
Razlog postojanja ovog adaptera je opseg. Ugrađeni engine OCR-a s usporedbom predložaka namjerno je uzak: strojem tiskana ASCII slova i znamenke, ništa više. Računi s naglašenim imenima, kineski ugovori i višejezični arhivi trebaju pravi prepoznavatelj s istreniranim jezičnim modelima, a Tesseract je očiti kandidat jer je program za naredbeni redak koji možete opskrbiti uz svoju aplikaciju. Zvati vanjski program iz biblioteke za dokumente zvuči trivijalno. Nije, i najveći dio zanimljivog koda u adapteru bavi se time što se dogodi kad se program ponaša pogrešno, zaglavi, otkaze ili naslijedi stvari koje nikad ne bi smio vidjeti
Kako HotPDF upravlja Tesseractom iz Delphi aplikacije?
HotPDF pokreće Tesseract kao skriveni dijete-proces po stranici, hrani ga renderiranom bitmapom i čita natrag TSV datoteku, a rezultat izlaže kroz isti IHPDFOCREngine šav koji koristi ugrađeni engine. Ništa nizvodno se ne mijenja: preslikavanje koordinata, rukovanje rotacijom, Unicode validacija, filtriranje po confidenceu i atomična potvrda to je tekstualno-slojni cjevovod koji već imate. Tvornica živi u uniti HPDFTesseractRecognition i validira unaprijed: executable mora postojati, tessdata direktorij mora postojati, timeout mora biti između 1 i 3.600.000 milisekundi, a identifikator jezika smije sadržavati samo ASCII slova, znamenke, _ i +. Ta zadnja provjera ima veze jer string jezika završi na naredbenom retku, i eng+chi_sim valjana je Tesseract vrijednost dok bilo što s 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
// baca EArgumentException za nedostajući executable, nedostajući tessdata,
// loš identifikator jezika ili timeout izvan 1..3600000 ms
Engine := HPDFCreateTesseractOCREngine(
'C:\OCR\Tesseract\tesseract.exe',
'C:\OCR\Tesseract\tessdata',
'eng+chi_sim', // više modelova spojenih s '+'
120000); // limit po stranici, zadano 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 s tekstom se po zadanom preskaču
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 stvara privatni direktorij pod temp putanjom imena HotPDF-OCR-{GUID}, sprema renderiranu 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 escapiranja naredbenog retka za obrnute kose crte i ugrađene navodnike. Vrijednost --dpi je render DPI iz THPDFOCRTextLayerOptions.DPI, pa Tesseract nikad ne mora pogađati rezoluciju iz metapodataka slike, a --psm 3 traži potpuno automatsku segmentaciju stranice. Engine se prijavljuje kao Tesseract (local CLI), što je ono što završi u Info.EngineName. Tesseract i njegovi jezični modeli ne isporučuju se s HotPDF-om; njihova instalacija posao je aplikacije
Zašto je TSV parser tako strog?
TSV parser u HotPDF-u baca cijelu stranicu na svakom neispravnom retku, jer djelomično parsirana lista riječi proizvodi tekstualni sloj koji tiho ne slaže sa slikom. Tesseractov TSV izlaz ima fiksno dvanaest-stupčano zaglavlje, od level do text, i HotPDF usporedi prvi redak s tim točnim zaglavljem nakon skidanja opcionalnog byte order marka. Svaki sljedeći redak mora se rasprsnuti u točno dvanaest polja, i rasprsavanje staje nakon jedanaestog taba tako da tab unutar prepoznatog teksta ostane dio riječi umjesto da stvori trinaesti stupac. Samo su retci razine 5 riječi; razine 1 do 4 opisuju stranice, blokove, odlomke i retke, i preskaču se. Retci razine 5 čiji je tekst prazan ili čist whitespace preskaču se također, jer prazna riječ ima box ali nema što locirati ni tražiti. Sve ostalo provjerava se strogo: cjelobrojna geometrija, confidence parsiran s invariant en-US formatom da njemačka locale ne pročita 93.5 kao smeće, box koji leži posve unutar bitmape, i confidence između 0 i 100. Jedan jedini pad baci iznimku, engine vrati False, i lista riječi se očisti. Regresijski testovi uključuju upravo taj slučaj: jedna valjana riječ iza koje slijedi pokvaren redak mora dati nula riječi, a ne jednu
// sažeto iz petlje razine 5 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; // retci stranice/bloka/odlomka/retka
WordText := Fields[11];
if Trim(WordText) = '' then Continue; // whitespace riječi 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; // cjevovod očekuje 0..1
Zadnji redak komunicira sa zadanim kojeg možda ne očekujete. Tesseract confidence ide od 0 do 100, cjevovod radi u 0 do 1, a THPDFOCRTextLayerOptions.MinimumConfidence po zadanom je 0.5, pa svaka Tesseract riječ ispod 50 broji se u Info.DroppedWordCount i nikad ne stiže do stranice. Na čistom skenu od 300 DPI to je razumna granica. Na bučnom faksu može izbaciti iznenađujući udio stranice, i pravi potez je pogledati brojač izbačenih prije nego spustite prag, jer su nisko-pouzdane riječi upravo one koje su najvjerojatnije krive
Što dijete-proces Tesseracta nasljeđuje?
Dijete-proces Tesseracta nasljeđuje točno dva handlea iz HotPDF-a: NUL handle za standardni ulaz i izlaz, i file handle za standardnu grešku. Ta preciznost jest poanta. CreateProcess s bInheritHandles = True način je da standardnim handleovima dođete do djeteta, ali sam po sebi prenosi svaki nasljedivi handle u procesu domaćina, uključujući datoteke, pipeove i eventove otvorene od nevezanog koda u vašoj aplikaciji. Dijete zatim drži te objekte živima dok ne izađe, pa datoteka ostaje zaključana ili pipe nikad ne vidi kraj dok Tesseract melje kroz stranicu. HotPDF taj procjep zatvara proširenim startup zapisom: STARTUPINFOEX, lista atributa koja nosi PROC_THREAD_ATTRIBUTE_HANDLE_LIST, i creation flag EXTENDED_STARTUPINFO_PRESENT. S listom handleova na mjestu, bInheritHandles i dalje mora biti True, ali granicu prelaze samo nabrani handleovi. Isto razmišljanje o zatvaranju pogoni i izolaciju PDF image kodeka u worker procesima, gdje je dijete nepouzdani kod; ovdje je dijete pouzdano, ali domaćin nije jedini vlasnik vlastite tabele handleova
// konstante prikazane imenom; izvor predaje njihove numeričke vrijednosti
// oba handlea stvaraju se s bInheritHandle = True
InheritedHandles[0] := NullHandle; // stdin i stdout
InheritedHandles[1] := ErrorHandle; // stderr.txt u privatnom direktoriju
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, // traži handle lista
CREATE_NO_WINDOW or EXTENDED_STARTUPINFO_PRESENT,
nil, PChar(DirectoryName), Startup.StartupInfo, ProcessInfo);
Zašto otkazani OCR run može izgledati kao kvar enginea?
Otkazani OCR run izgleda kao kvar enginea jer IHPDFOCREngine.Recognize vraća jedan Boolean, i False znači i "Tesseract je pao" i "korisnik je pritisnuo Cancel". Adapter polla cancellation token i timeout svakih 25 milisekundi dok dijete radi, i kad token opali, baca unutar Recognize, uhvati vlastitu iznimku, počisti, i vrati False s dijagnostikom. Da je cjevovod to tretirao kao engine grešku, pozivatelj bi vidio otlsEngineError za posao koji je korisnik namjerno zaustavio. ApplyLoadedOCRTextLayer zato prvo provjerava token svaki put kad Recognize vrati False, i rezultat pretvara u engine kvar samo ako token nije bio postavljen. Taj redoslijed čuva višestanični ugovor: prepoznavanje, validacija, obračun budžeta i gradnja sadržaja rade za svaku zatraženu stranicu prije nego se otvori grafička transakcija, pa otkazivanje na stranici 40 od 50 javlja otlsCancelled i ostavlja dokument, uključujući prvih 39 stranica, netaknutim. Nema djelomično pretražive datoteke za objašnjavanje kasnije, a ostatak rukovanja kvarovima slijedi isti ograničeni stil:
- Timeout je po pozivu
Recognize, mjeri se od njegovog početka, pa zadanih 60.000 ms vrijedi za svaku stranicu, a ne za cijeli dokument - Dijete koje i dalje radi na timeoutu ili otkazivanju terminira se, čeka se do 5 sekundi, a njegov privatni direktorij briše se u
finallybloku output.tsvima plafon 64 MiB istderr.txt1 MiB, provjeravano dok dijete radi kao i nakon njegovog izlaska- Broj riječi i UTF-16 code jedinica ima plafon po stranici kroz preostale budžete
MaxWordsPerPage,MaxTotalWordsiMaxTextCodeUnits, i njihovo prekoračenje baca run, umjesto da skraćuje listu riječi - Standardni izlaz ide u
NULjer Tesseract pišeoutput.tsv, dok standardna greška ide u datoteku pa se ne-nulti exit code javlja s do 4.096 znakova engineova vlastitog prigovora, obično najbrži način da saznate da.traineddatadatoteka nedostaje
Kako prepoznate riječi postaju nevidljivi tekstualni sloj
HotPDF piše Tesseractove riječi kao nevidljivi tekst koristeći text rendering mod 3, mod ni-ispune-ni-poteza definiran u ISO 32000-1 §9.3.6, pa stranica i dalje pokazuje skeniranu sliku dok pretraga i kopiranje rade nad prepoznatim riječima. Content stream otvara BT s 3 Tr, i svaka riječ dobiva Tm matricu na svojem baselineu, veličinu fonta izvedenu iz visine boxa u pikselima na render DPI, i Tz horizontalni scale koji razvlači niz glifova na izmjerenu širinu boxa, pa highlight pretrage sleti na riječ u slici umjesto da drifta preko nje
Tesseractov TSV ima boxove ali nema baselinea, pa adapter javlja svaku riječ bez nje, i cjevovod procijeni baseline na petinu visine boxa iznad donjeg ruba. Sam tekst ide kroz zajednički neugrađeni Type0 font s Identity-H kodiranjem i generiranim ToUnicode CMapom, jedan CID po različitom Unicode scalaru kroz cijeli run, pa tako kineski, naglašeni latinski i znakovi dopunskih ravnina svi prežive kopiranje i pretragu. Taj dizajn ima dva ograničenja vrijedna ranog izgovaranja: jedan run može nositi najviše 65.535 različitih scalara, i neugrađeni font ne zadovoljava zahtjev ugrađivanja fontova iz ISO 19005, pa PDF/A izlaz treba zasebno ugrađen sukladan font. Provjera rezultata jednostavna je i vrijedi je automatizirati: spremite, ponovno učitajte, i pokrenite običnu putanju teksta učitanog dokumenta iz članka o izvlačenju teksta iz učitanog PDF-a u Delphiju; ako riječi dođu natrag na očekivanim stranicama, sloj je stvaran
RapidOCR i drugi enginei na istom TSV protokolu
HotPDF ponovno koristi isti pokretač procesa i TSV parser za RapidOCR kroz HPDFCreateRapidOCREngine(PythonExecutable, BridgeScript, ModelDirectory, TimeoutMilliseconds), što je korisniji izbor za skenove pojednostavljenog kineskog. Naredbeni redak identičan je osim što se putanja bridge skripte umeće iza Python executiva, 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 automatska preuzimanja modelova, i piše TSV oblika Tesseracta pa Delphi strana ne treba drugi parser. Ime enginea javljeno u Info.EngineName jest RapidOCR (local ONNX). Taj oblik nudi opći recept: svaki prepoznavatelj kojeg možete zamotati u malu skriptu koja prima argument listu u stilu Tesseracta i ispisuje dvanaest-stupčani TSV nasljeđuje izolaciju handleova, timeout, otkazivanje, budžete izlaza i sve-ili-ništa potvrdu besplatno. Adapteri su samo za Windows, rade jednu stranicu istovremeno sinkrono, i ne ispravljaju kosinu ni prethodno obrađuju sliku izvan onoga što renderer proizvede, pa kvaliteta slike na ulazu i dalje postavlja plafon onome što izlazi
Tesseract i RapidOCR adapteri, pisac nevidljivog tekstualnog sloja, renderer stranica koji ih hrani, i izvlačenje teksta koje verificira rezultat svi isporučuju se u istoj nativnoj VCL komponenti za Delphi i C++Builder. Ako OCR dodajete u aplikaciju za hvatanje ili arhiviranje dokumenata, HotPDF Delphi PDF komponenta daje vam cjevovod uz samo OCR engine koji još treba instalirati