runtime¶
What a consumer actually calls: UncialClient, its builder, and the per-page progress it emits.
Features¶
- No compile-time dependency on any engine. Its dependencies are
modelandcore(api) plusraster(implementation); engines are reached only throughOcrEngineFactory, resolved fromOcrEngineRegistryat first use. - Digital layer first, OCR for the rest: when a
DigitalTextExtractoris wired in andpreferDigitalLayeris set, pages that already carry text skip OCR entirely and the document reportsExtractionSource.Mixedif only some did. - Progress and cancellation:
extractAsFlowemits per page and checks the coroutine's state at every page boundary. - Typed failures: every
Resultfailure is anOcrError, never a raw platform exception. - All targets: android, jvm, iosArm64, iosSimulatorArm64, iosX64.
Core Components¶
UncialClient¶
extract(bytes)/extract(raster):Result<OcrDocument>in one call.extractAsFlow(bytes)/extractAsFlow(raster): a coldFlow<OcrProgress>that throws on failure rather than emitting it.extractOrThrow(bytes)/extractOrThrow(raster): the same work as throwing functions annotated for Swift, because Kotlin'sResultdoes not survive Obj-C export.capabilities: the engine'sOcrCapabilities, ornullwhen no engine is available;isAvailableis the boolean form.hasDigitalTextLayerSupport,options.close(): releases the recognizer. Idempotent, and safe to call while an extraction is running.
UncialClientBuilder¶
Created through the UncialClient { … } function. It mirrors OcrOptions one for one — languages, renderDpi, maxPageSide, preferDigitalLayer, recognitionQuality, rasterColor, includeWords, pageRange — or takes a whole options object, which overrides the individual properties.
The rest is the composition root, hand-written because a DI framework inside a library is a dependency forced on the consumer: engine, rasterizer, digitalTextExtractor, languageData, logger. All default to null (or OcrLogger.None), meaning "resolve from the registry / use the platform default / do nothing".
Building never throws: a missing or unusable engine surfaces at the first extract as OcrError.Unsupported, so a client is safe to construct in an initializer or a DI graph.
OcrProgress¶
Started(pageCount, source): emitted once, before any page, so a progress bar has a total.Page(index, completed, of, page): emitted per page, withfractionfor a progress bar and the recognized page for incremental rendering.Done(document): emitted last.
Usage¶
val ocr = UncialClient {
languages = listOf(OcrLanguage.Ukrainian, OcrLanguage.English)
renderDpi = 200
digitalTextExtractor = pdfTextExtractor() // needs uncial-pdf-text
}
ocr.extractAsFlow(pdfBytes).collect { progress ->
when (progress) {
is OcrProgress.Started -> ui.showTotal(progress.pageCount)
is OcrProgress.Page -> ui.update(progress.fraction)
is OcrProgress.Done -> render(progress.document)
else -> Unit
}
}
On Android the engine registers itself through androidx.startup, so nothing has to be set. Everywhere else, call installTesseractEngine() or installVisionEngine() once before the first extraction.
close() blocks the calling thread
The bundled engines take a blocking native lock in close(), because freeing a handle
underneath a running recognition is a SIGSEGV rather than an exception. Closing therefore
waits out the work in flight — on Android a whole page, on the JVM the native call in
progress. Do not call it from onDestroy or a main-thread use { } and expect it to
return instantly.
The digital fast path is opt-in
preferDigitalLayer is inert without a digitalTextExtractor. It is not wired
automatically because uncial-pdf-text pulls several MB of PDFBox-Android into an app
that may only ever OCR photographs.