Searchable OCR Text Layers

HotPDF can render selected loaded pages for an application-supplied OCR engine and atomically add searchable invisible Unicode text aligned with each recognised word

The SearchableOCR console demo provides a runnable workflow: generate a three-page image-only sample with --create-sample, choose built-in OCR, RapidOCR, or Tesseract DLL/CLI recognition, select pages, configure confidence and resource limits, and save a searchable copy with conversion statistics

Engine integration

Implement IHPDFOCREngine with any synchronous OCR provider available to the application

The engine receives a borrowed TBitmap, requested DPI, normalised page rotation, media-box coordinates, remaining word and UTF-16 budgets, and the effective cancellation token through THPDFOCRRequest

Word boxes and optional baselines use top-left bitmap pixel coordinates, and the engine must not retain or free the borrowed bitmap after Recognize returns

Optional local engines

Version 2.754.0 adds explicit Tesseract and RapidOCR factories returning IHPDFOCREngine on Windows; the overload without an engine still selects the existing built-in engine

Install and provision the chosen engine locally before creating its adapter, then pass that adapter to the engine overload of ApplyLoadedOCRTextLayer; executables, Python packages, and OCR models are optional external dependencies and are not bundled with HotPDF

Tesseract

The optional native Tesseract DLL adapter brings configurable page segmentation and engine modes, multilingual recognition and native word baselines via HPDFCreateTesseractDLLOCREngine and THPDFTesseractOptions

function HPDFCreateTesseractOCREngine(const ExecutablePath,
  TessDataDirectory, Language: string;
  TimeoutMilliseconds: Cardinal = 60000): IHPDFOCREngine; overload;
function HPDFCreateTesseractOCREngine(const ExecutablePath,
  TessDataDirectory, Language: string;
  const Options: THPDFTesseractOptions): IHPDFOCREngine; overload;

The declaration is in HPDFTesseractRecognition; provide a Tesseract executable capable of TSV output and a data directory containing the requested language model, such as chi_sim.traineddata for chi_sim

The options overload accepts THPDFTesseractOptions.Default with explicit page segmentation, recognition engine mode, timeout, and input pixel limit; select tpsSingleLine, tpsSingleWord, or tpsSparseText to match the input layout, as shown in Tesseract OCR Adapters

This example assumes the executable and data have already been installed at the shown paths and PDF is a loaded THotPDF instance

uses SysUtils, HPDFDoc, HPDFTesseractRecognition;

procedure AddTesseractText(PDF: THotPDF);
var
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  Engine := HPDFCreateTesseractOCREngine(
    'C:\OCR\Tesseract\tesseract.exe',
    'C:\OCR\Tesseract\tessdata', 'chi_sim', 60000);
  Options := THPDFOCRTextLayerOptions.Default;
  if not PDF.ApplyLoadedOCRTextLayer([0], Engine, Options, Info) then
    raise Exception.Create('OCR text layer was not added');
end;

RapidOCR

The native RapidOCR DLL adapter recognises memory snapshots using persistent in-process CPU ONNX models via HPDFCreateRapidOCRDLLOCREngine, with optional angle classification and cooperative cancellation in Delphi, C++Builder and Windows FPC/Lazarus builds

The RapidOCR local model adapter additionally offers a THPDFRapidOCROptions overload covering explicit ONNX model paths, optional angle classification, local character dictionaries and fonts, CPU thread limits and an input pixel budget in Delphi, C++Builder and FPC/Lazarus builds

function HPDFCreateRapidOCREngine(const PythonExecutable, BridgeScript,
  ModelDirectory: string; TimeoutMilliseconds: Cardinal = 60000): IHPDFOCREngine;

The declaration is in HPDFRapidOCRRecognition; provision Python with rapidocr and onnxruntime, the supplied tools/OCR/rapidocr_tsv.py bridge, and these three local models

The bridge also requires %WINDIR%\Fonts\arial.ttf for RapidOCR's result container, disables automatic downloads, and uses one ONNX thread per configured execution pool; missing models, packages, or the local font cause recognition to fail

The current bridge uses the Simplified Chinese model configuration and preserves recognised punctuation without fullwidth or halfwidth substitution

uses SysUtils, HPDFDoc, HPDFRapidOCRRecognition;

procedure AddRapidOCRText(PDF: THotPDF);
var
  Engine: IHPDFOCREngine;
  Options: THPDFOCRTextLayerOptions;
  Info: THPDFOCRTextLayerInfo;
begin
  Engine := HPDFCreateRapidOCREngine(
    'C:\OCR\Python\python.exe',
    'C:\HotPDF\tools\OCR\rapidocr_tsv.py',
    'C:\OCR\RapidOCR\models', 60000);
  Options := THPDFOCRTextLayerOptions.Default;
  if not PDF.ApplyLoadedOCRTextLayer([0], Engine, Options, Info) then
    raise Exception.Create('OCR text layer was not added');
end;

Both factories validate existing executable and data paths and accept a timeout from 1 through 3,600,000 milliseconds, defaulting to 60,000; invalid configuration raises EArgumentException

The shared adapter launches a hidden local process with quoted paths and restricted inherited handles, polls cancellation, timeout, and output sizes every 25 milliseconds, and terminates the process on failure before cleaning up temporary files

TSV output is limited to 64 MiB and diagnostic-file output to 1 MiB, with returned diagnostics truncated to 4,096 characters; recognised words must also fit the request's word and UTF-16 budgets and valid bitmap bounds

TSV recognition output must be valid UTF-8 and contain no NUL bytes or C0/C1 control characters inside recognised words; malformed output fails the entire recognition request even when valid words precede the error

These are output and request limits, not a hard cap on the external engine's memory use; cancellation and engine failures return through the text-layer status without publishing partial pages

Recognition evidence and limits

The local RapidOCR 3.8.4 baseline passed all 15 repetitions across five fixed Chinese scan regions: two clear regions at 300 DPI and three low-resolution pressure regions at 150 DPI, evaluated against the current reference transcripts with character error rate at most 5% and deletion rate at most 2%

All repetitions passed reading-order checks, 5,190 word or character coordinate checks in total, and visible-pixel equality at 72 DPI; the two clear regions had zero character errors against those references

Two AI visual reviews agree on the Chinese text and numbers, but some punctuation code points in the raster remain ambiguous and human adjudication is pending; punctuation differences still count as errors and these results are corpus evidence, not a general accuracy guarantee

Geometry and search text

HotPDF inverts the renderer page transform so word baselines remain aligned on pages rotated by 0, 90, 180, or 270 degrees

Each accepted word is written with text rendering mode 3 Tr, a fitted horizontal text scale, and a rotation-aware text matrix, leaving the page raster unchanged whilst preserving selectable content order

A shared Type 0 Identity-H font assigns bounded document-local CIDs to Unicode scalars and writes a complete ToUnicode map, including UTF-16 surrogate targets for supplementary characters

Adjacent Latin and Cyrillic words on the same baseline receive an explicit Unicode space when their geometry indicates a word gap; adjacent CJK words retain their original text without inserted spaces

Processing a scanned PDF

The Demo/Delphi/SearchableOCR/SearchableOCR.dpr console example loads a scanned PDF, runs native RapidOCR with an explicit local language profile, publishes invisible Unicode text on all eligible pages and saves a new PDF without opening a viewer

SearchableOCR.exe scan.pdf searchable.pdf --dll C:\HotPDF\Lib\Native\RapidOCR\Win64\HotPDFRapidOCR.dll --models C:\HotPDF\Lib\Native\RapidOCR\Models --language ru --dpi 300

Use --language ch for Simplified Chinese or --language chinese_cht for Traditional Chinese; the DLL must match the executable's architecture and the selected local model package must be installed

The example refuses to overwrite an existing output file and accepts --replace-ocr when updating a marked HotPDF OCR layer

Windows FPC JPEG scan decoding draws into the final bitmap pixel format before recognition so the LCL format conversion retains the decoded image

Updating an existing OCR layer

Set THPDFOCRTextLayerOptions.ReplaceExistingOCRTextLayer to True to replace previously generated, marked HotPDF OCR streams on recognised pages in the same transaction as the new text

This bypasses SkipPagesWithText on marked pages only and works after saving and reloading; THPDFOCRTextLayerInfo.ReplacedPageCount reports the number of replaced pages

Recognition failures and pages with no accepted words preserve the old text layer; unmarked streams, including third-party OCR and layers generated by earlier HotPDF releases, are preserved

Replacement disconnects the old OCR content streams but does not remove font resources or optional-content groups that may still be referenced elsewhere; incremental saving may retain superseded objects from previous revisions

The default remains to skip any page with extractable text, including a page number or header over a scan; turning off SkipPagesWithText recognises the whole page and can duplicate existing native text, so mixed pages require application-selected processing

Bounds, cancellation, and atomicity

THPDFOCRTextLayerOptions.Default enables 300 DPI recognition, skips pages that already expose text, and limits page count, pixels, words, UTF-16 units, and generated content bytes

HotPDF validates every engine result and builds all page content before starting one copy-on-write graph transaction, so engine failures, invalid geometry, exhausted budgets, cancellation, or commit errors leave the loaded object graph unchanged

An empty page-index array selects every loaded page, whilst duplicate page indices are recognised once in first-seen order

MaxTotalWords counts all received words, including low-confidence or invalid words that are later dropped, and each page request receives only the remaining allowance

If the word or UTF-16 budget is exhausted and another eligible page remains, processing returns otlsBudgetExceeded before rendering or recognising that page and publishes none of the planned text layers; pages skipped for existing text require no remaining recognition budget

Optional-content grouping

Set UseOptionalContentGroup to bind all generated text to one named layer through each page Resources/Properties dictionary

This option requires PDF 1.5 and follows StrictVersionLock; the default keeps the invisible text outside an optional-content group for widest compatibility

Conformance note

The generated searchable layer intentionally uses an unembedded synthetic font because rendering mode 3 never paints glyphs

This API does not by itself produce PDF/A-conforming OCR output, so a PDF/A workflow should use an embedded-font text-layer path and run the requested conformance validation before publication

Primary APIs

Progressive Rendering and Cancellation ยท ExtractLoadedPageText Method