Contents
In brief
The author published react-native-glyph-ocr on npm: on-device text recognition for React Native. iOS uses system Apple Vision; Android uses PaddleOCR.
On photos of Russian ingredient labels, the Android path reads 77–83% of ingredients. Wrappers around Google ML Kit managed 13–28% on the same set: Text Recognition v2 has no Cyrillic model, so Russian often becomes look-alike Latin junk.
The package needs the New Architecture; it was checked on React Native 0.85 and 0.87. Expo needs a development build — it will not run in Expo Go.
What happened
The product goal was an app that photographs a label and parses the ingredient list. Copy is Russian, often mixed with English, printed on foil, crumpled packs, thermal paper, or curved around a can. The default React Native path is ML Kit wrappers. They work on Latin scripts; on Cyrillic they fail quietly by substituting similar Latin letters until the word is unreadable.
On iOS, system Vision since iOS 16 already reads Russian and Ukrainian without shipping a custom model. So one library had to hide two engines behind one JavaScript-shaped result. The author first ran spikes on the same photos and a manual ground truth: Vision (revision 3, .accurate, ru-RU / en-US) versus PaddleOCR PP-OCRv5 mobile on ONNX Runtime with the East Slavic pack (eslav: ru, uk, be, en).
Spike numbers: cropped ingredients on an iPhone 13 Pro Max took about 0.15 s with up to 97% recall; PaddleOCR on a Pixel 6a took about 1.1 s at 77–83%. Paddle models are 12.7 MB once; ONNX Runtime adds roughly 33 MB per ABI. An existing TypeScript-over-ONNX pipeline was not enough: camera frames and an ML Kit-like result shape were required, so the Android pipeline was written natively in Kotlin and C++.
The API mirrors the familiar tree: blocks → lines → words, with confidence, boxes, and angle. Languages are fixed when you create an instance (createOcr / recognize); models are prepared once (prepare) and cached. Sources are local URIs, base64, or a VisionCamera frame; http(s) URLs are rejected on purpose. Errors are typed (OcrError) and cancellation uses AbortSignal. Models download from a checksummed manifest; you can point at your own CDN or embed packs in the build.
For the camera, useTextScanner waits for a sharp, still, bright enough frame via a ~1 ms native meter, then runs recognition once. Upside-down text on Android is handled by dual-reading long lines instead of a separate orientation classifier — that classifier was wrong too often on Russian labels.
Why it matters
Picture a self-checkout that “reads” a crumpled receipt in a language its eyes never learned. It does not stay silent — it confidently invents Latin look-alikes. That is ML Kit on Cyrillic without a real model: you get text, not ingredients.
For mobile apps that must parse Russian, Ukrainian, or mixed labels on-device, this is not a polish issue. It is a hard accuracy ceiling. Cloud OCR or living with 13–28% keeps food, pharmacy, and document flows out of production.
One wrapper over two engines removes the false choice between “write native iOS” and “ship a weak Android path.” A single JS API, an ML Kit-shaped result for migrants, and honest size/speed numbers make architecture debates measurable.
The costs are visible too: Android pays for ONNX Runtime, the benchmark set is narrow (food labels), and budget phones are only estimated from slow cores so far. An ExecuTorch path (~1.7 MB) already exists as a dev entry — a lighter default is on the roadmap.
In practice
The first npm release is 0.3.1; the API may still move before 1.0. Run your own photo set before trusting someone else’s percentages.
- Install
react-native-glyph-ocrandreact-native-nitro-modules; runpod installon iOS. You need the New Architecture, iOS 16+, Android 7+ (camera scanner needs Android 10+). - In Expo, add the config plugin and ship a development build;
embedPacks(for example["eslav"]) bakes models into the APK so the first launch does not download them. - Set languages once with
createOcr({ languages: ['ru', 'en'] }), callprepare, thenrecognizewith anroiwhen the ingredients occupy only part of the frame — that cut is material on Android. - For live camera, wire VisionCamera 5 and
useTextScanner; do not call heavy recognition from everyonFramecallback or Android will stall the frame pipeline for about a second. - If you are on
@react-native-ml-kit/text-recognition, use the migration guide in the repo: the block/line shape is familiar; the engine and model prep change. - Stress your own weak spots: E-codes land around ~60% for both engines; the author’s set is 32 food-label photos — other document types will score differently.
- Watch issues and
docs/benchmarks.md: budget-device timings, scanner threshold calibration from real camera clips, and a possible ExecuTorch default are next.
Takeaway
react-native-glyph-ocr fills the Cyrillic gap ML Kit leaves open: Vision on iOS, PaddleOCR on Android, one JS-facing result, already on npm.
If your app reads Russian labels on-device, compare your photo set against an ML Kit wrapper — a multi-fold accuracy gap usually matters more than a few dozen megabytes of native runtime.



Comments