Uncial¶
Overview¶
Uncial is an on-device OCR SDK for Kotlin Multiplatform that turns a PDF or a raster
image into a structured document — pages, lines and words, each with a box and a
confidence, plus Heading and Paragraph blocks — rather than a blob of text.
Everything happens on the device. There is no networking in the recognition path, no camera, and no UI: Uncial is a library, not a scanner app.
It was built for Ukrainian, which is the reason it exists at all — ML Kit does not recognize it, so an off-the-shelf answer was not available.
val client = UncialClient { languages = listOf(OcrLanguage.Ukrainian, OcrLanguage.English) }
val document = client.extract(pdfBytes).getOrThrow()
println(document.text)
println("${document.pageCount} pages, ${document.lines.size} lines")
What you get¶
- Structure, not a string. Pages → lines → words, each carrying a bounding box and a
confidence.
DocumentStructureturns that into headings and paragraphs. - The digital fast path. A PDF that already carries its text layer is read directly,
roughly a thousand times faster than rasterizing and recognizing it — and exactly, not
approximately. A scan whose cover page was OCR'd by the scanner comes back as
Mixed. - Images, not just PDFs. Hand it a
Bitmap, aBufferedImageor aCGImageand skip the PDF machinery entirely. - One coordinate model. Top-down pixels, origin at the page's top-left, on every platform. Vision reports bottom-up normalized and PDF is bottom-up in points; each engine converts at its own edge so no caller ever has to.
- Progress and cancellation.
extractAsFlowreports per page and honours cancellation, which is what makes a 300-page document something a UI can host. - Honest capabilities. The platforms genuinely differ. Uncial reports the difference instead of hiding it.
Platform asymmetry is reported, not hidden¶
Read UncialClient.capabilities rather than assuming the platforms are uniform:
| words | confidence | per-line language | orientation | skew | |
|---|---|---|---|---|---|
| Tesseract (JVM) | yes | yes | yes | yes | yes |
| Tesseract (Android) | yes | yes | no | no | no |
| Vision (iOS) | yes¹ | yes | no | no | no |
¹ derived from boundingBoxForRange, not native.
The JVM column is richer because libtesseract's C API exposes things the Tesseract4Android Java wrapper does not — a real asymmetry between two bindings of the same engine, and precisely why the capability type exists.
See Capabilities for the full matrix, including the iOS version floors and why Vision's language list depends on the recognition level.
Getting started¶
- Installation — what to depend on, and the current publishing status
- Quick Start — the smallest thing that works, per platform
- Language Data — bundled, system, downloaded, or bring your own
Not published yet
Uncial is pre-release. Nothing is on Maven Central and nothing is in mavenLocal;
consuming it today means building from source. See the
Roadmap for what is and is not done.
Architecture in one paragraph¶
Rasterization and recognition are separate roles — PageRasterizer and TextRecognizer —
which is what makes image input, engine swapping and PNG-based tests possible at all. The
runtime has no compile-time dependency on any engine: engines announce themselves
through OcrEngineRegistry, automatically on Android via androidx.startup and explicitly
elsewhere. See the Architecture overview.