core¶
The engine contracts — the module a consumer only opens when writing their own engine, rasterizer or language-data provider.
Features¶
- Roles, not an engine: rasterization (
PageRasterizer) and recognition (TextRecognizer) are separate interfaces, which is what makes image input, PNG-based tests and engine swapping possible. - Engine discovery without a dependency:
OcrEngineRegistrylets engine modules announce themselves, soruntimenever names Tesseract or Vision. - Honest capabilities:
OcrCapabilitiesstates what an engine can actually do here, instead of pretending the platforms are equal. - All targets: android, jvm, iosArm64, iosSimulatorArm64, iosX64.
- Depends on
modelandkotlinx-coroutines-core(bothapi), plusandroidx.startupon Android.
Core Components¶
Engine (…uncial.core.engine)¶
OcrEngineFactory:id,isAvailable(),capabilities(options), andsuspend create(options, logger, languageData).isAvailable()is public and honest on purpose — the JVM engine binds to a systemlibtesseractthat may simply not be installed.OcrCapabilities:engineId,engineVersion,languages,wordLevel,confidence,perLineLanguage,orientationDetection,skewDetection, plusdroppedAnyOf(requested).OcrEngineRegistry: an object withregister(factory)(idempotent, keyed byid),unregister(id),engines(),firstAvailable()andlastProbeFailure(). Registration is lock-free because on Android an initializer registers while application code may already be reading.DigitalTextExtractor:idandsuspend extract(bytes, options): OcrDocument?. Declared here rather than inpdf-textso the runtime can use the digital path without depending on the module that implements it.
Recognition and rasterization¶
TextRecognizer(AutoCloseable):capabilitiesandsuspend recognize(raster, pageIndex, options): OcrPage.PageRasterizer:suspend open(bytes): RasterizedDocument. Opening is separate from rasterizing so the page count is known before any pixels are allocated.RasterizedDocument(AutoCloseable):pageCountandsuspend rasterize(pageIndex, options): Raster.Raster: anexpect classwithwidth,height,sourceDpiandrelease(). Each platform's actual exposes and accepts the native image —Raster(bitmap)on Android,Raster(bufferedImage)on the JVM,Raster(cgImage)on iOS.placeholderRaster(width, height): a raster with a size and no pixels, for fakes.
Language data and logging¶
LanguageDataProvider:suspend available(requested)andsuspend materialize(languages): String. This is the bring-your-own-.traineddataseam.OcrLogger: afun interfacetaking(LogLevel, message, throwable), withOcrLogger.Noneas the default, plus thedebug/info/warn/errorextensions. Uncial logs nothing unless a logger is supplied.
Android plumbing¶
UncialContextInitializer: anandroidx.startupInitializerthat captures the application context beforeApplication.onCreate, so no public API has to ask for aContext.UncialContext:get()andinstall(applicationContext)— the escape hatch for apps that stripInitializationProviderfrom their merged manifest.
The AAR ships a consumer rule keeping UncialContextInitializer, which R8 would otherwise
remove: it is only ever named as a string in the merged manifest.
Usage¶
Implement OcrEngineFactory and register it, or hand it to UncialClient's builder directly:
materialize returns the parent of tessdata/
If it returns /data/user/0/app/files, then /data/user/0/app/files/tessdata/ukr.traineddata
must exist. That mirrors Tesseract's own datapath convention, quirk included — and note
that the two bindings disagree downstream: the Tesseract4Android wrapper appends
tessdata/ itself, while Tess4J/libtesseract wants the directory, so the JVM engine
descends one level.
One recognizer serves concurrent pages
UncialClient creates a TextRecognizer once and hands the same instance to every
concurrent extract. An implementation must tolerate re-entrant recognize calls; all
three bundled recognizers hold a single native handle and serialize internally.
Raster.sourceDpi is not OcrOptions.renderDpi
Rasterizers clamp the requested dpi whenever a page would exceed maxPageSide, so
sourceDpi is the resolution the pixels were really rendered at. It is null for a
raster the caller built from their own image — in which case say nothing to the engine
rather than asserting a number nobody rendered at.