A powerful, plug-and-play Flutter widget that lets users select and crop any portion of your UI and automatically extracts text on-device with zero backend setup.
Built on the coordinate capture foundations of selectable_area, selectable_ocr integrates hardware-accelerated Optical Character Recognition directly into the selection lifecycle.
- ⚡ Zero Backend Required: Runs 100% on-device using Google ML Kit on Android & iOS. No servers, no API keys, and no extra configuration.
- 🔒 Privacy-First: Sensitive documents (passports, IDs, credit cards, medical forms) never leave the user's device.
- 🚀 Blazing Fast: Hardware-accelerated neural network inference finishes in ~50ms–120ms.
- 🎯 Wrap Any Widget: Works seamlessly over an
Image,ListView, custom document viewer, or any Flutter widget tree. - 🔄 In-Situ Visual Feedback: Displays an intuitive, floating loading badge over the active selection while text is being processed.
- 🔌 Pluggable Architecture: Need a custom cloud backend or server-side Python/Vercel microservice? Easily plug in
HttpOcrEngine. - 🌐 Multi-Language Scripts: Supports Latin, Devanagari, Japanese, Korean, and Chinese recognition scripts.
Add selectable_ocr to your pubspec.yaml:
dependencies:
selectable_ocr: ^1.0.0Then install dependencies:
flutter pub get- Android:
minSdkVersion 21(default for modern Flutter apps). - iOS: iOS 15.5+ (or iOS 13.0+).
No additional device permissions (like Camera or External Storage) are required, as the widget captures and processes in-memory widget pixels directly from the screen!
Wrap any widget with SelectableOcr and listen to onTextExtracted:
import 'package:flutter/material.dart';
import 'package:selectable_ocr/selectable_ocr.dart';
class SimpleOcrView extends StatelessWidget {
const SimpleOcrView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Snip & OCR')),
body: Center(
child: SelectableOcr(
// Any widget you want to capture from
child: Image.network('https://example.com/document.png'),
// Called when OCR extraction completes
onTextExtracted: (String text) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Extracted: $text')),
);
},
),
),
);
}
}If you need line-by-line breakdowns, processing duration, or raw recognition objects:
SelectableOcr(
child: myDocumentWidget,
onOcrResult: (OcrResult result) {
print('Consolidated Text: ${result.cleanText}');
print('Processing Time: ${result.processingTime.inMilliseconds} ms');
print('Lines detected: ${result.lines.length}');
for (final line in result.lines) {
print('• $line');
}
},
onImageCaptured: (Uint8List pngBytes) {
// Access the raw cropped image bytes if needed
},
)Customize the rectangle stroke, fill color, corner radius, and in-situ loading badge:
SelectableOcr(
style: const SelectionStyle(
strokeColor: Colors.purpleAccent,
fillColor: Color(0x33AB47BC),
strokeWidth: 2.5,
cornerRadius: 8.0,
showLoadingBadge: true,
loadingText: 'Scanning receipt...',
loadingBadgeColor: Colors.purpleAccent,
),
child: myReceiptWidget,
onTextExtracted: (text) => print(text),
)To recognize non-Latin scripts such as Japanese, Devanagari, Korean, or Chinese:
SelectableOcr(
engine: OnDeviceOcrEngine(
script: TextRecognitionScript.devanagari, // or .japanese, .korean, .chinese
),
child: myImageWidget,
onTextExtracted: (text) => print(text),
)If your organization prefers running OCR on a remote Python server (e.g. Flask, FastAPI, or Vercel):
SelectableOcr(
engine: HttpOcrEngine(
endpoint: Uri.parse('https://your-api.vercel.app/ocr'),
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
},
timeout: const Duration(seconds: 10),
),
child: myWidget,
onTextExtracted: (text) => print('Server extracted: $text'),
)Check out the example/ directory for a complete, dual-pane interactive passport and document auto-fill demo app.
Inspired by selectable_area and the article "Stop Screenshotting: A Better Way to Capture UI in Flutter" by Adhil Latheef.
MIT License. Feel free to use and contribute!