react-native-text-reader 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import Vision
2
+ import UIKit
2
3
 
3
4
  extension String {
4
5
  func stripPrefix(_ prefix: String) -> String {
@@ -7,67 +8,226 @@ extension String {
7
8
  }
8
9
  }
9
10
 
11
+ private struct RecognizedLine {
12
+ let text: String
13
+ let confidence: Float
14
+ let frame: CGRect
15
+ }
16
+
17
+ private struct TextCandidate {
18
+ let text: String
19
+ let confidence: Float
20
+ let box: CGRect
21
+ let midY: CGFloat
22
+ let minX: CGFloat
23
+ }
24
+
10
25
  @objc(TextReader)
11
26
  class TextReader: NSObject {
12
- @objc static func requiresMainQueueSetup() -> Bool { return true }
27
+ @objc static func requiresMainQueueSetup() -> Bool { return false }
13
28
 
14
29
  @objc(read:withOptions:withResolver:withRejecter:)
15
- func read(imgPath: String, options: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
30
+ func read(
31
+ imgPath: String,
32
+ options: [String: Any],
33
+ resolve: @escaping RCTPromiseResolveBlock,
34
+ reject: @escaping RCTPromiseRejectBlock
35
+ ) {
36
+ recognizeText(imgPath: imgPath, options: options) { result in
37
+ switch result {
38
+ case .success(let lines):
39
+ resolve(lines.map { $0.text })
40
+ case .failure(let error):
41
+ reject(error.code, error.message, error.underlying)
42
+ }
43
+ }
44
+ }
45
+
46
+ @objc(readDetailed:withOptions:withResolver:withRejecter:)
47
+ func readDetailed(
48
+ imgPath: String,
49
+ options: [String: Any],
50
+ resolve: @escaping RCTPromiseResolveBlock,
51
+ reject: @escaping RCTPromiseRejectBlock
52
+ ) {
53
+ recognizeText(imgPath: imgPath, options: options) { result in
54
+ switch result {
55
+ case .success(let lines):
56
+ let lineTexts = lines.map { $0.text }
57
+ let details: [[String: Any]] = lines.map { line in
58
+ var detail: [String: Any] = [
59
+ "text": line.text,
60
+ "confidence": line.confidence,
61
+ ]
62
+ detail["frame"] = [
63
+ "top": Int(line.frame.origin.y * 1000),
64
+ "left": Int(line.frame.origin.x * 1000),
65
+ "width": Int(line.frame.width * 1000),
66
+ "height": Int(line.frame.height * 1000),
67
+ ]
68
+ return detail
69
+ }
70
+ resolve([
71
+ "fullText": lineTexts.joined(separator: "\n"),
72
+ "lines": lineTexts,
73
+ "details": details,
74
+ ])
75
+ case .failure(let error):
76
+ reject(error.code, error.message, error.underlying)
77
+ }
78
+ }
79
+ }
80
+
81
+ private struct ReaderError: Error {
82
+ let code: String
83
+ let message: String
84
+ let underlying: Error?
85
+ }
86
+
87
+ private func recognizeText(
88
+ imgPath: String,
89
+ options: [String: Any],
90
+ completion: @escaping (Result<[RecognizedLine], ReaderError>) -> Void
91
+ ) {
16
92
  guard !imgPath.isEmpty else {
17
- reject("ERR_EMPTY_PATH", "Image path cannot be empty.", nil)
93
+ completion(.failure(ReaderError(code: "ERR_EMPTY_PATH", message: "Image path cannot be empty.", underlying: nil)))
18
94
  return
19
95
  }
20
96
 
21
97
  let formattedImgPath = imgPath.stripPrefix("file://")
22
98
  let threshold = (options["visionIgnoreThreshold"] as? NSNumber)?.floatValue ?? 0.0
23
99
 
24
- do {
25
- let imgData = try Data(contentsOf: URL(fileURLWithPath: formattedImgPath))
26
- guard let image = UIImage(data: imgData), let cgImage = image.cgImage else {
27
- reject("ERR_IMAGE_PROCESSING", "Failed to load image from the provided path.", nil)
28
- return
29
- }
100
+ DispatchQueue.global(qos: .userInitiated).async {
101
+ do {
102
+ let imgData = try Data(contentsOf: URL(fileURLWithPath: formattedImgPath))
103
+ guard let image = self.fixedOrientationImage(from: imgData),
104
+ let cgImage = image.cgImage else {
105
+ completion(.failure(ReaderError(code: "ERR_IMAGE_PROCESSING", message: "Failed to load image from the provided path.", underlying: nil)))
106
+ return
107
+ }
30
108
 
31
- let requestHandler = VNImageRequestHandler(cgImage: cgImage)
32
- let ocrRequest = VNRecognizeTextRequest { request, error in
33
- self.handleTextRecognitionResult(request: request, threshold: threshold, error: error, resolve: resolve, reject: reject)
34
- }
109
+ let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
110
+ let ocrRequest = VNRecognizeTextRequest { request, error in
111
+ if let error = error {
112
+ completion(.failure(ReaderError(code: "ERR_OCR", message: "Error in text recognition: \(error.localizedDescription)", underlying: error)))
113
+ return
114
+ }
35
115
 
36
- if #available(iOS 16.0, *) {
37
- ocrRequest.automaticallyDetectsLanguage = true
38
- }
39
- ocrRequest.recognitionLevel = .accurate
116
+ guard let observations = request.results as? [VNRecognizedTextObservation] else {
117
+ completion(.failure(ReaderError(code: "ERR_OCR_RESULTS", message: "Failed to read text from the image.", underlying: nil)))
118
+ return
119
+ }
40
120
 
41
- try requestHandler.perform([ocrRequest])
42
- } catch {
43
- reject("ERR_IMAGE_LOADING", "Failed to load or process the image: \(error.localizedDescription)", nil)
121
+ let lines = self.groupObservationsIntoLines(observations: observations, threshold: threshold)
122
+ completion(.success(lines))
123
+ }
124
+
125
+ self.applyOptions(to: ocrRequest, from: options)
126
+
127
+ try requestHandler.perform([ocrRequest])
128
+ } catch {
129
+ completion(.failure(ReaderError(code: "ERR_IMAGE_LOADING", message: "Failed to load or process the image: \(error.localizedDescription)", underlying: error)))
130
+ }
44
131
  }
45
132
  }
46
133
 
47
- private func handleTextRecognitionResult(request: VNRequest, threshold: Float, error: Error?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
48
- if let error = error {
49
- reject("ERR_OCR", "Error in text recognition: \(error.localizedDescription)", nil)
50
- return
134
+ private func applyOptions(to request: VNRecognizeTextRequest, from options: [String: Any]) {
135
+ if #available(iOS 16.0, *) {
136
+ request.automaticallyDetectsLanguage = true
51
137
  }
52
138
 
53
- guard let observations = request.results as? [VNRecognizedTextObservation] else {
54
- reject("ERR_OCR_RESULTS", "Failed to read text from the image.", nil)
55
- return
139
+ if let level = options["recognitionLevel"] as? String {
140
+ request.recognitionLevel = level == "fast" ? .fast : .accurate
141
+ } else {
142
+ request.recognitionLevel = .accurate
56
143
  }
57
144
 
58
- if observations.isEmpty {
59
- resolve([])
60
- return
145
+ if let useCorrection = options["useLanguageCorrection"] as? Bool {
146
+ request.usesLanguageCorrection = useCorrection
147
+ }
148
+
149
+ if let languages = options["recognitionLanguages"] as? [String], !languages.isEmpty {
150
+ request.recognitionLanguages = languages
61
151
  }
62
152
 
63
- let extractedStrings = observations.compactMap { observation -> String? in
153
+ if let customWords = options["customWords"] as? [String], !customWords.isEmpty {
154
+ request.customWords = customWords
155
+ }
156
+
157
+ if let minHeight = options["minimumTextHeight"] as? NSNumber {
158
+ request.minimumTextHeight = minHeight.floatValue
159
+ }
160
+ }
161
+
162
+ private func fixedOrientationImage(from data: Data) -> UIImage? {
163
+ guard let image = UIImage(data: data) else { return nil }
164
+ if image.imageOrientation == .up { return image }
165
+
166
+ UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
167
+ image.draw(in: CGRect(origin: .zero, size: image.size))
168
+ let normalized = UIGraphicsGetImageFromCurrentImageContext()
169
+ UIGraphicsEndImageContext()
170
+ return normalized
171
+ }
172
+
173
+ private func groupObservationsIntoLines(
174
+ observations: [VNRecognizedTextObservation],
175
+ threshold: Float
176
+ ) -> [RecognizedLine] {
177
+ let candidates: [TextCandidate] = observations.compactMap { observation in
64
178
  guard let topCandidate = observation.topCandidates(1).first else { return nil }
65
- if topCandidate.confidence >= threshold {
66
- return topCandidate.string
179
+ guard topCandidate.confidence >= threshold else { return nil }
180
+ let box = observation.boundingBox
181
+ return TextCandidate(
182
+ text: topCandidate.string,
183
+ confidence: topCandidate.confidence,
184
+ box: box,
185
+ midY: box.midY,
186
+ minX: box.minX
187
+ )
188
+ }
189
+
190
+ guard !candidates.isEmpty else { return [] }
191
+
192
+ let sorted = candidates.sorted { lhs, rhs in
193
+ if abs(lhs.midY - rhs.midY) > 0.02 {
194
+ return lhs.midY > rhs.midY
67
195
  }
68
- return nil
196
+ return lhs.minX < rhs.minX
69
197
  }
70
198
 
71
- resolve(extractedStrings.isEmpty ? [] : extractedStrings)
199
+ var lines: [RecognizedLine] = []
200
+ var currentGroup: [TextCandidate] = []
201
+ var currentMidY: CGFloat?
202
+
203
+ for candidate in sorted {
204
+ if let midY = currentMidY, abs(candidate.midY - midY) <= 0.02 {
205
+ currentGroup.append(candidate)
206
+ } else {
207
+ if !currentGroup.isEmpty {
208
+ lines.append(self.mergeGroup(currentGroup))
209
+ }
210
+ currentGroup = [candidate]
211
+ currentMidY = candidate.midY
212
+ }
213
+ }
214
+
215
+ if !currentGroup.isEmpty {
216
+ lines.append(self.mergeGroup(currentGroup))
217
+ }
218
+
219
+ return lines
220
+ }
221
+
222
+ private func mergeGroup(_ group: [TextCandidate]) -> RecognizedLine {
223
+ let sortedGroup = group.sorted { $0.minX < $1.minX }
224
+ let text = sortedGroup.map { $0.text }.joined(separator: " ")
225
+ let confidence = sortedGroup.map { $0.confidence }.max() ?? 0
226
+ let minX = sortedGroup.map { $0.box.minX }.min() ?? 0
227
+ let minY = sortedGroup.map { $0.box.minY }.min() ?? 0
228
+ let maxX = sortedGroup.map { $0.box.maxX }.max() ?? 0
229
+ let maxY = sortedGroup.map { $0.box.maxY }.max() ?? 0
230
+ let frame = CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
231
+ return RecognizedLine(text: text, confidence: confidence, frame: frame)
72
232
  }
73
233
  }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _reactNative = require("react-native");
8
+ var _default = exports.default = _reactNative.TurboModuleRegistry.get('TextReader');
9
+ //# sourceMappingURL=NativeTextReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeTextReader.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAuCpCC,gCAAmB,CAACC,GAAG,CAAO,YAAY,CAAC","ignoreList":[]}
@@ -5,11 +5,14 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = exports.ScriptOptions = void 0;
7
7
  var _reactNative = require("react-native");
8
+ var _NativeTextReader = _interopRequireDefault(require("./NativeTextReader.js"));
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
10
  const LINKING_ERROR = `The package 'react-native-text-reader' doesn't seem to be linked. Make sure: \n\n` + _reactNative.Platform.select({
9
11
  ios: "- You have run 'pod install'\n",
10
12
  default: ''
11
- }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
12
- const TextReader = _reactNative.NativeModules.TextReader ? _reactNative.NativeModules.TextReader : new Proxy({}, {
13
+ }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go (use a development build instead)\n';
14
+ const TextReaderModule = _NativeTextReader.default ?? _reactNative.NativeModules.TextReader;
15
+ const TextReader = TextReaderModule ? TextReaderModule : new Proxy({}, {
13
16
  get() {
14
17
  throw new Error(LINKING_ERROR);
15
18
  }
@@ -21,20 +24,40 @@ let ScriptOptions = exports.ScriptOptions = /*#__PURE__*/function (ScriptOptions
21
24
  ScriptOptions["JAPANESE"] = "Japanese";
22
25
  ScriptOptions["KOREAN"] = "Korean";
23
26
  return ScriptOptions;
24
- }({}); // Options
27
+ }({});
28
+ const DEFAULT_OPTIONS = {
29
+ script: ScriptOptions.LATIN
30
+ };
31
+ function normalizeDetailedResult(result) {
32
+ return {
33
+ fullText: result.fullText ?? '',
34
+ lines: result.lines ?? [],
35
+ details: (result.details ?? []).map(detail => ({
36
+ text: detail.text,
37
+ confidence: detail.confidence,
38
+ frame: detail.frame,
39
+ recognizedLanguages: detail.recognizedLanguages
40
+ }))
41
+ };
42
+ }
43
+
25
44
  /**
26
- * Extracts text from an image.
27
- * @param imagePath - Image path
28
- * @param options - Additional options
29
- * @param options.visionIgnoreThreshold - Vision ignore threshold(iOS)
30
- * @param options.script - Language script (Android)
45
+ * Extracts text lines from an image.
31
46
  */
32
47
  async function read(imagePath, options) {
33
- return await TextReader.read(imagePath, options || {
34
- script: ScriptOptions.LATIN
35
- });
48
+ const detailed = await readDetailed(imagePath, options);
49
+ return detailed.lines;
50
+ }
51
+
52
+ /**
53
+ * Extracts text with confidence, bounding boxes, and language metadata.
54
+ */
55
+ async function readDetailed(imagePath, options) {
56
+ const result = await TextReader.readDetailed(imagePath, options ?? DEFAULT_OPTIONS);
57
+ return normalizeDetailedResult(result);
36
58
  }
37
59
  var _default = exports.default = {
38
- read
60
+ read,
61
+ readDetailed
39
62
  };
40
63
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","TextReader","NativeModules","Proxy","get","Error","ScriptOptions","exports","read","imagePath","options","script","LATIN","_default"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA,MAAMC,aAAa,GACjB,mFAAmF,GACnFC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,UAAU,GAAGC,0BAAa,CAACD,UAAU,GACvCC,0BAAa,CAACD,UAAU,GACxB,IAAIE,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACT,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAAC,IAEMU,aAAa,GAAAC,OAAA,CAAAD,aAAA,0BAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAA,OAAbA,aAAa;AAAA,OAQzB;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAeE,IAAIA,CAACC,SAAiB,EAAEC,OAAgB,EAAqB;EAC1E,OAAO,MAAMT,UAAU,CAACO,IAAI,CAC1BC,SAAS,EACTC,OAAO,IAAI;IACTC,MAAM,EAAEL,aAAa,CAACM;EACxB,CACF,CAAC;AACH;AAAC,IAAAC,QAAA,GAAAN,OAAA,CAAAP,OAAA,GAEc;EAAEQ;AAAK,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["_reactNative","require","_NativeTextReader","_interopRequireDefault","e","__esModule","default","LINKING_ERROR","Platform","select","ios","TextReaderModule","NativeTextReaderModule","NativeModules","TextReader","Proxy","get","Error","ScriptOptions","exports","DEFAULT_OPTIONS","script","LATIN","normalizeDetailedResult","result","fullText","lines","details","map","detail","text","confidence","frame","recognizedLanguages","read","imagePath","options","detailed","readDetailed","_default"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AACA,IAAAC,iBAAA,GAAAC,sBAAA,CAAAF,OAAA;AAAwD,SAAAE,uBAAAC,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAExD,MAAMG,aAAa,GACjB,mFAAmF,GACnFC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEJ,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,iEAAiE;AAEnE,MAAMK,gBAAgB,GAAGC,yBAAsB,IAAIC,0BAAa,CAACC,UAAU;AAE3E,MAAMA,UAAU,GAAGH,gBAAgB,GAC/BA,gBAAgB,GAChB,IAAII,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACV,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAAC,IAEMW,aAAa,GAAAC,OAAA,CAAAD,aAAA,0BAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAA,OAAbA,aAAa;AAAA;AA8CzB,MAAME,eAAwB,GAAG;EAC/BC,MAAM,EAAEH,aAAa,CAACI;AACxB,CAAC;AAED,SAASC,uBAAuBA,CAACC,MAAsB,EAAkB;EACvE,OAAO;IACLC,QAAQ,EAAED,MAAM,CAACC,QAAQ,IAAI,EAAE;IAC/BC,KAAK,EAAEF,MAAM,CAACE,KAAK,IAAI,EAAE;IACzBC,OAAO,EAAE,CAACH,MAAM,CAACG,OAAO,IAAI,EAAE,EAAEC,GAAG,CAAEC,MAAM,KAAM;MAC/CC,IAAI,EAAED,MAAM,CAACC,IAAI;MACjBC,UAAU,EAAEF,MAAM,CAACE,UAAU;MAC7BC,KAAK,EAAEH,MAAM,CAACG,KAAK;MACnBC,mBAAmB,EAAEJ,MAAM,CAACI;IAC9B,CAAC,CAAC;EACJ,CAAC;AACH;;AAEA;AACA;AACA;AACA,eAAeC,IAAIA,CAACC,SAAiB,EAAEC,OAAiB,EAAqB;EAC3E,MAAMC,QAAQ,GAAG,MAAMC,YAAY,CAACH,SAAS,EAAEC,OAAO,CAAC;EACvD,OAAOC,QAAQ,CAACX,KAAK;AACvB;;AAEA;AACA;AACA;AACA,eAAeY,YAAYA,CACzBH,SAAiB,EACjBC,OAAiB,EACQ;EACzB,MAAMZ,MAAM,GAAG,MAAMV,UAAU,CAACwB,YAAY,CAC1CH,SAAS,EACTC,OAAO,IAAIhB,eACb,CAAC;EACD,OAAOG,uBAAuB,CAACC,MAAM,CAAC;AACxC;AAAC,IAAAe,QAAA,GAAApB,OAAA,CAAAb,OAAA,GAEc;EAAE4B,IAAI;EAAEI;AAAa,CAAC","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.get('TextReader');
5
+ //# sourceMappingURL=NativeTextReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeTextReader.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAuClD,eAAeA,mBAAmB,CAACC,GAAG,CAAO,YAAY,CAAC","ignoreList":[]}
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
 
3
3
  import { NativeModules, Platform } from 'react-native';
4
+ import NativeTextReaderModule from "./NativeTextReader.js";
4
5
  const LINKING_ERROR = `The package 'react-native-text-reader' doesn't seem to be linked. Make sure: \n\n` + Platform.select({
5
6
  ios: "- You have run 'pod install'\n",
6
7
  default: ''
7
- }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
8
- const TextReader = NativeModules.TextReader ? NativeModules.TextReader : new Proxy({}, {
8
+ }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go (use a development build instead)\n';
9
+ const TextReaderModule = NativeTextReaderModule ?? NativeModules.TextReader;
10
+ const TextReader = TextReaderModule ? TextReaderModule : new Proxy({}, {
9
11
  get() {
10
12
  throw new Error(LINKING_ERROR);
11
13
  }
@@ -18,22 +20,39 @@ export let ScriptOptions = /*#__PURE__*/function (ScriptOptions) {
18
20
  ScriptOptions["KOREAN"] = "Korean";
19
21
  return ScriptOptions;
20
22
  }({});
21
-
22
- // Options
23
+ const DEFAULT_OPTIONS = {
24
+ script: ScriptOptions.LATIN
25
+ };
26
+ function normalizeDetailedResult(result) {
27
+ return {
28
+ fullText: result.fullText ?? '',
29
+ lines: result.lines ?? [],
30
+ details: (result.details ?? []).map(detail => ({
31
+ text: detail.text,
32
+ confidence: detail.confidence,
33
+ frame: detail.frame,
34
+ recognizedLanguages: detail.recognizedLanguages
35
+ }))
36
+ };
37
+ }
23
38
 
24
39
  /**
25
- * Extracts text from an image.
26
- * @param imagePath - Image path
27
- * @param options - Additional options
28
- * @param options.visionIgnoreThreshold - Vision ignore threshold(iOS)
29
- * @param options.script - Language script (Android)
40
+ * Extracts text lines from an image.
30
41
  */
31
42
  async function read(imagePath, options) {
32
- return await TextReader.read(imagePath, options || {
33
- script: ScriptOptions.LATIN
34
- });
43
+ const detailed = await readDetailed(imagePath, options);
44
+ return detailed.lines;
45
+ }
46
+
47
+ /**
48
+ * Extracts text with confidence, bounding boxes, and language metadata.
49
+ */
50
+ async function readDetailed(imagePath, options) {
51
+ const result = await TextReader.readDetailed(imagePath, options ?? DEFAULT_OPTIONS);
52
+ return normalizeDetailedResult(result);
35
53
  }
36
54
  export default {
37
- read
55
+ read,
56
+ readDetailed
38
57
  };
39
58
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["NativeModules","Platform","LINKING_ERROR","select","ios","default","TextReader","Proxy","get","Error","ScriptOptions","read","imagePath","options","script","LATIN"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,aAAa,EAAEC,QAAQ,QAAQ,cAAc;AAEtD,MAAMC,aAAa,GACjB,mFAAmF,GACnFD,QAAQ,CAACE,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,UAAU,GAAGN,aAAa,CAACM,UAAU,GACvCN,aAAa,CAACM,UAAU,GACxB,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAEL,WAAYQ,aAAa,0BAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAA,OAAbA,aAAa;AAAA;;AAQzB;;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAeC,IAAIA,CAACC,SAAiB,EAAEC,OAAgB,EAAqB;EAC1E,OAAO,MAAMP,UAAU,CAACK,IAAI,CAC1BC,SAAS,EACTC,OAAO,IAAI;IACTC,MAAM,EAAEJ,aAAa,CAACK;EACxB,CACF,CAAC;AACH;AAEA,eAAe;EAAEJ;AAAK,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["NativeModules","Platform","NativeTextReaderModule","LINKING_ERROR","select","ios","default","TextReaderModule","TextReader","Proxy","get","Error","ScriptOptions","DEFAULT_OPTIONS","script","LATIN","normalizeDetailedResult","result","fullText","lines","details","map","detail","text","confidence","frame","recognizedLanguages","read","imagePath","options","detailed","readDetailed"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,aAAa,EAAEC,QAAQ,QAAQ,cAAc;AACtD,OAAOC,sBAAsB,MAAM,uBAAoB;AAEvD,MAAMC,aAAa,GACjB,mFAAmF,GACnFF,QAAQ,CAACG,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,iEAAiE;AAEnE,MAAMC,gBAAgB,GAAGL,sBAAsB,IAAIF,aAAa,CAACQ,UAAU;AAE3E,MAAMA,UAAU,GAAGD,gBAAgB,GAC/BA,gBAAgB,GAChB,IAAIE,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACR,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAEL,WAAYS,aAAa,0BAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAbA,aAAa;EAAA,OAAbA,aAAa;AAAA;AA8CzB,MAAMC,eAAwB,GAAG;EAC/BC,MAAM,EAAEF,aAAa,CAACG;AACxB,CAAC;AAED,SAASC,uBAAuBA,CAACC,MAAsB,EAAkB;EACvE,OAAO;IACLC,QAAQ,EAAED,MAAM,CAACC,QAAQ,IAAI,EAAE;IAC/BC,KAAK,EAAEF,MAAM,CAACE,KAAK,IAAI,EAAE;IACzBC,OAAO,EAAE,CAACH,MAAM,CAACG,OAAO,IAAI,EAAE,EAAEC,GAAG,CAAEC,MAAM,KAAM;MAC/CC,IAAI,EAAED,MAAM,CAACC,IAAI;MACjBC,UAAU,EAAEF,MAAM,CAACE,UAAU;MAC7BC,KAAK,EAAEH,MAAM,CAACG,KAAK;MACnBC,mBAAmB,EAAEJ,MAAM,CAACI;IAC9B,CAAC,CAAC;EACJ,CAAC;AACH;;AAEA;AACA;AACA;AACA,eAAeC,IAAIA,CAACC,SAAiB,EAAEC,OAAiB,EAAqB;EAC3E,MAAMC,QAAQ,GAAG,MAAMC,YAAY,CAACH,SAAS,EAAEC,OAAO,CAAC;EACvD,OAAOC,QAAQ,CAACX,KAAK;AACvB;;AAEA;AACA;AACA;AACA,eAAeY,YAAYA,CACzBH,SAAiB,EACjBC,OAAiB,EACQ;EACzB,MAAMZ,MAAM,GAAG,MAAMT,UAAU,CAACuB,YAAY,CAC1CH,SAAS,EACTC,OAAO,IAAIhB,eACb,CAAC;EACD,OAAOG,uBAAuB,CAACC,MAAM,CAAC;AACxC;AAEA,eAAe;EAAEU,IAAI;EAAEI;AAAa,CAAC","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,34 @@
1
+ import type { TurboModule } from 'react-native';
2
+ export type NativeOptions = {
3
+ visionIgnoreThreshold?: number;
4
+ confidenceThreshold?: number;
5
+ script?: string;
6
+ recognitionLevel?: 'fast' | 'accurate';
7
+ recognitionLanguages?: string[];
8
+ customWords?: string[];
9
+ useLanguageCorrection?: boolean;
10
+ minimumTextHeight?: number;
11
+ };
12
+ export type NativeTextLine = {
13
+ text: string;
14
+ confidence?: number;
15
+ frame?: {
16
+ top: number;
17
+ left: number;
18
+ width: number;
19
+ height: number;
20
+ };
21
+ recognizedLanguages?: string[];
22
+ };
23
+ export type NativeDetailedResult = {
24
+ fullText: string;
25
+ lines: string[];
26
+ details: NativeTextLine[];
27
+ };
28
+ export interface Spec extends TurboModule {
29
+ read(imagePath: string, options?: NativeOptions): Promise<string[]>;
30
+ readDetailed(imagePath: string, options?: NativeOptions): Promise<NativeDetailedResult>;
31
+ }
32
+ declare const _default: Spec | null;
33
+ export default _default;
34
+ //# sourceMappingURL=NativeTextReader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeTextReader.d.ts","sourceRoot":"","sources":["../../../../src/NativeTextReader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,MAAM,aAAa,GAAG;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IACvC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,YAAY,CACV,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC;;AAED,wBAA2D"}
@@ -5,13 +5,38 @@ export declare enum ScriptOptions {
5
5
  JAPANESE = "Japanese",
6
6
  KOREAN = "Korean"
7
7
  }
8
+ export type RecognitionLevel = 'fast' | 'accurate';
9
+ export type TextFrame = {
10
+ top: number;
11
+ left: number;
12
+ width: number;
13
+ height: number;
14
+ };
15
+ export type TextLine = {
16
+ text: string;
17
+ confidence?: number;
18
+ frame?: TextFrame;
19
+ recognizedLanguages?: string[];
20
+ };
21
+ export type DetailedResult = {
22
+ fullText: string;
23
+ lines: string[];
24
+ details: TextLine[];
25
+ };
8
26
  export type Options = {
9
27
  visionIgnoreThreshold?: number;
28
+ confidenceThreshold?: number;
10
29
  script?: ScriptOptions;
30
+ recognitionLevel?: RecognitionLevel;
31
+ recognitionLanguages?: string[];
32
+ customWords?: string[];
33
+ useLanguageCorrection?: boolean;
34
+ minimumTextHeight?: number;
11
35
  };
12
- type TextReaderType = {
36
+ type TextReaderNative = {
13
37
  read(imagePath: string, options?: Options): Promise<string[]>;
38
+ readDetailed(imagePath: string, options?: Options): Promise<DetailedResult>;
14
39
  };
15
- declare const _default: TextReaderType;
40
+ declare const _default: TextReaderNative;
16
41
  export default _default;
17
42
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.tsx"],"names":[],"mappings":"AAmBA,oBAAY,aAAa;IACvB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAGD,MAAM,MAAM,OAAO,GAAG;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC/D,CAAC;wBAkByB,cAAc;AAAzC,wBAA0C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.tsx"],"names":[],"mappings":"AAsBA,oBAAY,aAAa;IACvB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9D,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC7E,CAAC;wBAyCuC,gBAAgB;AAAzD,wBAA0D"}
@@ -0,0 +1,34 @@
1
+ import type { TurboModule } from 'react-native';
2
+ export type NativeOptions = {
3
+ visionIgnoreThreshold?: number;
4
+ confidenceThreshold?: number;
5
+ script?: string;
6
+ recognitionLevel?: 'fast' | 'accurate';
7
+ recognitionLanguages?: string[];
8
+ customWords?: string[];
9
+ useLanguageCorrection?: boolean;
10
+ minimumTextHeight?: number;
11
+ };
12
+ export type NativeTextLine = {
13
+ text: string;
14
+ confidence?: number;
15
+ frame?: {
16
+ top: number;
17
+ left: number;
18
+ width: number;
19
+ height: number;
20
+ };
21
+ recognizedLanguages?: string[];
22
+ };
23
+ export type NativeDetailedResult = {
24
+ fullText: string;
25
+ lines: string[];
26
+ details: NativeTextLine[];
27
+ };
28
+ export interface Spec extends TurboModule {
29
+ read(imagePath: string, options?: NativeOptions): Promise<string[]>;
30
+ readDetailed(imagePath: string, options?: NativeOptions): Promise<NativeDetailedResult>;
31
+ }
32
+ declare const _default: Spec | null;
33
+ export default _default;
34
+ //# sourceMappingURL=NativeTextReader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeTextReader.d.ts","sourceRoot":"","sources":["../../../../src/NativeTextReader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,MAAM,aAAa,GAAG;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IACvC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,YAAY,CACV,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC;;AAED,wBAA2D"}
@@ -5,13 +5,38 @@ export declare enum ScriptOptions {
5
5
  JAPANESE = "Japanese",
6
6
  KOREAN = "Korean"
7
7
  }
8
+ export type RecognitionLevel = 'fast' | 'accurate';
9
+ export type TextFrame = {
10
+ top: number;
11
+ left: number;
12
+ width: number;
13
+ height: number;
14
+ };
15
+ export type TextLine = {
16
+ text: string;
17
+ confidence?: number;
18
+ frame?: TextFrame;
19
+ recognizedLanguages?: string[];
20
+ };
21
+ export type DetailedResult = {
22
+ fullText: string;
23
+ lines: string[];
24
+ details: TextLine[];
25
+ };
8
26
  export type Options = {
9
27
  visionIgnoreThreshold?: number;
28
+ confidenceThreshold?: number;
10
29
  script?: ScriptOptions;
30
+ recognitionLevel?: RecognitionLevel;
31
+ recognitionLanguages?: string[];
32
+ customWords?: string[];
33
+ useLanguageCorrection?: boolean;
34
+ minimumTextHeight?: number;
11
35
  };
12
- type TextReaderType = {
36
+ type TextReaderNative = {
13
37
  read(imagePath: string, options?: Options): Promise<string[]>;
38
+ readDetailed(imagePath: string, options?: Options): Promise<DetailedResult>;
14
39
  };
15
- declare const _default: TextReaderType;
40
+ declare const _default: TextReaderNative;
16
41
  export default _default;
17
42
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.tsx"],"names":[],"mappings":"AAmBA,oBAAY,aAAa;IACvB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAGD,MAAM,MAAM,OAAO,GAAG;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC/D,CAAC;wBAkByB,cAAc;AAAzC,wBAA0C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.tsx"],"names":[],"mappings":"AAsBA,oBAAY,aAAa;IACvB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9D,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC7E,CAAC;wBAyCuC,gBAAgB;AAAzD,wBAA0D"}