@trustchex/react-native-sdk 1.361.0 → 1.362.1
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.
- package/TrustchexSDK.podspec +3 -1
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +0 -13
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +0 -8
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +60 -39
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +94 -13
- package/ios/Camera/TrustchexCameraManager.m +0 -2
- package/ios/Camera/TrustchexCameraManager.swift +0 -7
- package/ios/Camera/TrustchexCameraView.swift +16 -47
- package/ios/OpenCV/OpenCVHelper.h +17 -0
- package/ios/OpenCV/OpenCVHelper.mm +128 -0
- package/ios/OpenCV/OpenCVModule.h +6 -0
- package/ios/OpenCV/OpenCVModule.mm +141 -0
- package/ios/TrustchexSDK-Bridging-Header.h +8 -0
- package/lib/module/Screens/Debug/MRZTestScreen.js +175 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +4 -0
- package/lib/module/Shared/Components/EIDScanner.js +0 -78
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +188 -150
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +0 -3
- package/lib/module/Shared/Libs/mrz.utils.js +265 -0
- package/lib/module/Trustchex.js +4 -0
- package/lib/module/index.js +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +0 -19
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +18 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +2 -1
- package/src/Screens/Debug/MRZTestScreen.tsx +209 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +5 -0
- package/src/Shared/Components/EIDScanner.tsx +0 -53
- package/src/Shared/Components/IdentityDocumentCamera.tsx +228 -146
- package/src/Shared/Components/QrCodeScannerCamera.tsx +0 -9
- package/src/Shared/Components/TrustchexCamera.tsx +0 -20
- package/src/Shared/Libs/mrz.utils.ts +289 -1
- package/src/Trustchex.tsx +5 -0
- package/src/index.tsx +3 -0
- package/src/version.ts +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +0 -785
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +0 -419
- package/ios/MRZValidation.m +0 -39
- package/ios/MRZValidation.swift +0 -802
- package/ios/MRZValidator.swift +0 -466
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
package com.trustchex.reactnativesdk.mrz
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Pure MRZ validation logic with no React Native dependencies.
|
|
5
|
-
* Used by both MRZValidationModule and TrustchexCameraView.
|
|
6
|
-
*/
|
|
7
|
-
object MRZValidator {
|
|
8
|
-
|
|
9
|
-
data class MRZResult(
|
|
10
|
-
val valid: Boolean,
|
|
11
|
-
val format: String,
|
|
12
|
-
val error: String? = null,
|
|
13
|
-
val documentCode: String? = null,
|
|
14
|
-
val issuingState: String? = null,
|
|
15
|
-
val documentNumber: String? = null,
|
|
16
|
-
val lastName: String? = null,
|
|
17
|
-
val firstName: String? = null,
|
|
18
|
-
val birthDate: String? = null,
|
|
19
|
-
val sex: String? = null,
|
|
20
|
-
val expirationDate: String? = null,
|
|
21
|
-
val nationality: String? = null,
|
|
22
|
-
val optional1: String? = null,
|
|
23
|
-
val optional2: String? = null,
|
|
24
|
-
val rawLines: String? = null
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
fun validateWithCorrections(ocrText: String): MRZResult {
|
|
28
|
-
val fixedText = fixMRZ(ocrText)
|
|
29
|
-
val lines = fixedText.trim().split("\n").map { it.trim() }
|
|
30
|
-
|
|
31
|
-
if (lines.isEmpty() || fixedText.length < 60) {
|
|
32
|
-
return MRZResult(valid = false, format = "UNKNOWN", error = "MRZ text too short")
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
val format = detectMRZFormat(lines)
|
|
36
|
-
if (format == "UNKNOWN") {
|
|
37
|
-
return MRZResult(valid = false, format = "UNKNOWN", error = "Unknown MRZ format")
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Phase 1: position-aware corrections
|
|
41
|
-
var result = parseMRZ(fixedText)
|
|
42
|
-
if (result.valid) return result
|
|
43
|
-
|
|
44
|
-
// Phase 2: composite checksum brute-force
|
|
45
|
-
val compositePos = when (format) {
|
|
46
|
-
"TD1" -> Pair(1, 29)
|
|
47
|
-
"TD2" -> Pair(1, 35)
|
|
48
|
-
"TD3" -> Pair(1, 43)
|
|
49
|
-
else -> null
|
|
50
|
-
}
|
|
51
|
-
if (compositePos != null) {
|
|
52
|
-
val mutableLines = lines.toMutableList()
|
|
53
|
-
val targetLine = mutableLines[compositePos.first]
|
|
54
|
-
for (digit in 0..9) {
|
|
55
|
-
val testLine = targetLine.substring(0, compositePos.second) +
|
|
56
|
-
digit +
|
|
57
|
-
targetLine.substring(compositePos.second + 1)
|
|
58
|
-
mutableLines[compositePos.first] = testLine
|
|
59
|
-
val testResult = parseMRZ(mutableLines.joinToString("\n"))
|
|
60
|
-
if (testResult.valid) return testResult
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Phase 3: O/0 permutations
|
|
65
|
-
for (permutation in generateO0Permutations(fixedText)) {
|
|
66
|
-
val testResult = parseMRZ(permutation)
|
|
67
|
-
if (testResult.valid) return testResult
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Phase 4: I/1 permutations
|
|
71
|
-
for (permutation in generateI1Permutations(fixedText)) {
|
|
72
|
-
val testResult = parseMRZ(permutation)
|
|
73
|
-
if (testResult.valid) return testResult
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Phase 5: S/5 permutations
|
|
77
|
-
for (permutation in generatePairPermutations(fixedText, 'S', '5', 30)) {
|
|
78
|
-
val testResult = parseMRZ(permutation)
|
|
79
|
-
if (testResult.valid) return testResult
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Phase 6: B/8 permutations
|
|
83
|
-
for (permutation in generatePairPermutations(fixedText, 'B', '8', 30)) {
|
|
84
|
-
val testResult = parseMRZ(permutation)
|
|
85
|
-
if (testResult.valid) return testResult
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Phase 7: Z/2 permutations
|
|
89
|
-
for (permutation in generatePairPermutations(fixedText, 'Z', '2', 30)) {
|
|
90
|
-
val testResult = parseMRZ(permutation)
|
|
91
|
-
if (testResult.valid) return testResult
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Phase 8: G/6 permutations
|
|
95
|
-
for (permutation in generatePairPermutations(fixedText, 'G', '6', 30)) {
|
|
96
|
-
val testResult = parseMRZ(permutation)
|
|
97
|
-
if (testResult.valid) return testResult
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return result
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private fun parseMRZ(rawText: String): MRZResult {
|
|
104
|
-
val lines = rawText.trim().split("\n").map { it.trim() }
|
|
105
|
-
val format = detectMRZFormat(lines)
|
|
106
|
-
|
|
107
|
-
if (format == "UNKNOWN") {
|
|
108
|
-
return MRZResult(valid = false, format = "UNKNOWN", error = "Unknown MRZ format")
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
val fields = when (format) {
|
|
112
|
-
"TD1" -> parseTD1(lines)
|
|
113
|
-
"TD2" -> parseTD2(lines)
|
|
114
|
-
"TD3" -> parseTD3(lines)
|
|
115
|
-
else -> return MRZResult(valid = false, format = "UNKNOWN", error = "Unsupported format")
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
val allValid = validateChecksums(format, lines)
|
|
119
|
-
return fields.copy(valid = allValid, format = format, rawLines = if (allValid) rawText.trim() else null)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private fun detectMRZFormat(lines: List<String>): String = when {
|
|
123
|
-
lines.size == 3 && lines.all { it.length == 30 } -> "TD1"
|
|
124
|
-
lines.size == 2 && lines.all { it.length == 36 } -> "TD2"
|
|
125
|
-
lines.size == 2 && lines.all { it.length == 44 } -> "TD3"
|
|
126
|
-
else -> "UNKNOWN"
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private fun parseTD1(lines: List<String>): MRZResult {
|
|
130
|
-
val names = lines[2].split("<<")
|
|
131
|
-
return MRZResult(
|
|
132
|
-
valid = false,
|
|
133
|
-
format = "TD1",
|
|
134
|
-
documentCode = lines[0].substring(0, 2).replace('<', ' ').trim(),
|
|
135
|
-
issuingState = lines[0].substring(2, 5).replace('<', ' ').trim(),
|
|
136
|
-
documentNumber = lines[0].substring(5, 14).replace('<', ' ').trim(),
|
|
137
|
-
optional1 = lines[0].substring(15, 30).replace('<', ' ').trim(),
|
|
138
|
-
birthDate = lines[1].substring(0, 6),
|
|
139
|
-
sex = lines[1].substring(7, 8),
|
|
140
|
-
expirationDate = lines[1].substring(8, 14),
|
|
141
|
-
nationality = lines[1].substring(15, 18).replace('<', ' ').trim(),
|
|
142
|
-
optional2 = lines[1].substring(18, 29).replace('<', ' ').trim(),
|
|
143
|
-
lastName = names[0].replace('<', ' ').trim(),
|
|
144
|
-
firstName = if (names.size > 1) names[1].replace('<', ' ').trim() else ""
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private fun parseTD2(lines: List<String>): MRZResult {
|
|
149
|
-
val names = lines[0].substring(5, 36).split("<<")
|
|
150
|
-
return MRZResult(
|
|
151
|
-
valid = false,
|
|
152
|
-
format = "TD2",
|
|
153
|
-
documentCode = lines[0].substring(0, 2).replace('<', ' ').trim(),
|
|
154
|
-
issuingState = lines[0].substring(2, 5).replace('<', ' ').trim(),
|
|
155
|
-
lastName = names[0].replace('<', ' ').trim(),
|
|
156
|
-
firstName = if (names.size > 1) names[1].replace('<', ' ').trim() else "",
|
|
157
|
-
documentNumber = lines[1].substring(0, 9).replace('<', ' ').trim(),
|
|
158
|
-
nationality = lines[1].substring(10, 13).replace('<', ' ').trim(),
|
|
159
|
-
birthDate = lines[1].substring(13, 19),
|
|
160
|
-
sex = lines[1].substring(20, 21),
|
|
161
|
-
expirationDate = lines[1].substring(21, 27),
|
|
162
|
-
optional1 = lines[1].substring(28, 35).replace('<', ' ').trim()
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private fun parseTD3(lines: List<String>): MRZResult {
|
|
167
|
-
val names = lines[0].substring(5, 44).split("<<")
|
|
168
|
-
return MRZResult(
|
|
169
|
-
valid = false,
|
|
170
|
-
format = "TD3",
|
|
171
|
-
documentCode = lines[0].substring(0, 2).replace('<', ' ').trim(),
|
|
172
|
-
issuingState = lines[0].substring(2, 5).replace('<', ' ').trim(),
|
|
173
|
-
lastName = names[0].replace('<', ' ').trim(),
|
|
174
|
-
firstName = if (names.size > 1) names[1].replace('<', ' ').trim() else "",
|
|
175
|
-
documentNumber = lines[1].substring(0, 9).replace('<', ' ').trim(),
|
|
176
|
-
nationality = lines[1].substring(10, 13).replace('<', ' ').trim(),
|
|
177
|
-
birthDate = lines[1].substring(13, 19),
|
|
178
|
-
sex = lines[1].substring(20, 21),
|
|
179
|
-
expirationDate = lines[1].substring(21, 27),
|
|
180
|
-
optional1 = lines[1].substring(28, 42).replace('<', ' ').trim()
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private fun validateChecksums(format: String, lines: List<String>): Boolean {
|
|
185
|
-
return try {
|
|
186
|
-
when (format) {
|
|
187
|
-
"TD1" -> {
|
|
188
|
-
val docNumValid = calculateMRZChecksum(lines[0].substring(5, 14)) == lines[0][14]
|
|
189
|
-
val birthValid = calculateMRZChecksum(lines[1].substring(0, 6)) == lines[1][6]
|
|
190
|
-
val expiryValid = calculateMRZChecksum(lines[1].substring(8, 14)) == lines[1][14]
|
|
191
|
-
val composite = lines[0].substring(5, 30) + lines[1].substring(0, 7) +
|
|
192
|
-
lines[1].substring(8, 15) + lines[1].substring(18, 29)
|
|
193
|
-
val compositeValid = calculateMRZChecksum(composite) == lines[1][29]
|
|
194
|
-
docNumValid && birthValid && expiryValid && compositeValid
|
|
195
|
-
}
|
|
196
|
-
"TD2" -> {
|
|
197
|
-
val line = lines[1]
|
|
198
|
-
val docNumValid = calculateMRZChecksum(line.substring(0, 9)) == line[9]
|
|
199
|
-
val birthValid = calculateMRZChecksum(line.substring(13, 19)) == line[19]
|
|
200
|
-
val expiryValid = calculateMRZChecksum(line.substring(21, 27)) == line[27]
|
|
201
|
-
val composite = line.substring(0, 10) + line.substring(13, 20) + line.substring(21, 35)
|
|
202
|
-
val compositeValid = calculateMRZChecksum(composite) == line[35]
|
|
203
|
-
docNumValid && birthValid && expiryValid && compositeValid
|
|
204
|
-
}
|
|
205
|
-
"TD3" -> {
|
|
206
|
-
val line = lines[1]
|
|
207
|
-
val docNumValid = calculateMRZChecksum(line.substring(0, 9)) == line[9]
|
|
208
|
-
val birthValid = calculateMRZChecksum(line.substring(13, 19)) == line[19]
|
|
209
|
-
val expiryValid = calculateMRZChecksum(line.substring(21, 27)) == line[27]
|
|
210
|
-
val personalValid = calculateMRZChecksum(line.substring(28, 42)) == line[42]
|
|
211
|
-
val composite = line.substring(0, 10) + line.substring(13, 20) + line.substring(21, 43)
|
|
212
|
-
val compositeValid = calculateMRZChecksum(composite) == line[43]
|
|
213
|
-
docNumValid && birthValid && expiryValid && personalValid && compositeValid
|
|
214
|
-
}
|
|
215
|
-
else -> false
|
|
216
|
-
}
|
|
217
|
-
} catch (e: Exception) {
|
|
218
|
-
false
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private fun calculateMRZChecksum(data: String): Char {
|
|
223
|
-
val weights = intArrayOf(7, 3, 1)
|
|
224
|
-
var sum = 0
|
|
225
|
-
for (i in data.indices) {
|
|
226
|
-
val value = when (val c = data[i]) {
|
|
227
|
-
in '0'..'9' -> c - '0'
|
|
228
|
-
in 'A'..'Z' -> c - 'A' + 10
|
|
229
|
-
else -> 0
|
|
230
|
-
}
|
|
231
|
-
sum += value * weights[i % 3]
|
|
232
|
-
}
|
|
233
|
-
return ('0' + (sum % 10))
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
fun fixMRZ(rawText: String): String {
|
|
237
|
-
var fixed = rawText
|
|
238
|
-
.replace(" ", "")
|
|
239
|
-
.replace("«", "")
|
|
240
|
-
.replace(Regex("<K+|r+K+|<r+K+"), "")
|
|
241
|
-
|
|
242
|
-
fixed = fixed.replace(Regex("""\bI<TUR([A-Z0-9]{3})0([A-Z0-9]{6})\b"""), "I<TUR$1O$2")
|
|
243
|
-
|
|
244
|
-
val lines = fixed.split("\n").map { it.trim() }
|
|
245
|
-
|
|
246
|
-
// Extract only lines that look like MRZ (uppercase alphanumeric + filler only, exact MRZ lengths)
|
|
247
|
-
val mrzLines = lines.filter { line ->
|
|
248
|
-
(line.length == 30 || line.length == 36 || line.length == 44) &&
|
|
249
|
-
line.matches(Regex("[A-Z0-9<]+"))
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// If no exact-length MRZ lines found, fall back to lines containing '<' that could be padded
|
|
253
|
-
val candidateLines = if (mrzLines.isNotEmpty()) mrzLines else lines.filter { it.contains('<') }
|
|
254
|
-
|
|
255
|
-
val targetLength = when {
|
|
256
|
-
candidateLines.all { it.length <= 30 } -> 30
|
|
257
|
-
candidateLines.all { it.length <= 36 } -> 36
|
|
258
|
-
candidateLines.all { it.length <= 44 } -> 44
|
|
259
|
-
else -> return rawText
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Keep only lines that fit the target length (filter out non-MRZ lines)
|
|
263
|
-
val filteredLines = candidateLines.filter { it.length <= targetLength && it.matches(Regex("[A-Z0-9<]+")) }
|
|
264
|
-
if (filteredLines.isEmpty()) return rawText
|
|
265
|
-
|
|
266
|
-
val paddedLines = filteredLines.map { line ->
|
|
267
|
-
if (line.length < targetLength) line.padEnd(targetLength, '<') else line
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return applyOCRCorrections(paddedLines.joinToString("\n"))
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
private fun applyOCRCorrections(mrzText: String): String {
|
|
274
|
-
val lines = mrzText.split("\n").toMutableList()
|
|
275
|
-
if (lines.isEmpty()) return mrzText
|
|
276
|
-
|
|
277
|
-
val format = detectMRZFormat(lines)
|
|
278
|
-
if (format == "UNKNOWN") return mrzText
|
|
279
|
-
|
|
280
|
-
when (format) {
|
|
281
|
-
"TD1" -> {
|
|
282
|
-
lines[0] = correctLetterPositions(lines[0], listOf(0..1, 2..4))
|
|
283
|
-
lines[0] = correctCheckDigitPositions(lines[0], listOf(14))
|
|
284
|
-
lines[1] = correctDigitPositions(lines[1], listOf(0..5, 8..13))
|
|
285
|
-
lines[1] = correctCheckDigitPositions(lines[1], listOf(6, 14, 29))
|
|
286
|
-
lines[1] = correctLetterPositions(lines[1], listOf(7..7, 15..17))
|
|
287
|
-
lines[2] = correctLetterPositions(lines[2], listOf(0..29))
|
|
288
|
-
}
|
|
289
|
-
"TD2" -> {
|
|
290
|
-
lines[0] = correctLetterPositions(lines[0], listOf(0..1, 2..4, 5..35))
|
|
291
|
-
lines[1] = correctDigitPositions(lines[1], listOf(13..18, 21..26))
|
|
292
|
-
lines[1] = correctCheckDigitPositions(lines[1], listOf(9, 19, 27, 35))
|
|
293
|
-
lines[1] = correctLetterPositions(lines[1], listOf(10..12, 20..20))
|
|
294
|
-
}
|
|
295
|
-
"TD3" -> {
|
|
296
|
-
lines[0] = correctLetterPositions(lines[0], listOf(0..1, 2..4, 5..43))
|
|
297
|
-
lines[1] = correctDigitPositions(lines[1], listOf(13..18, 21..26))
|
|
298
|
-
lines[1] = correctCheckDigitPositions(lines[1], listOf(9, 19, 27, 42))
|
|
299
|
-
lines[1] = correctLetterPositions(lines[1], listOf(10..12, 20..20))
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return lines.joinToString("\n")
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
private fun correctDigitPositions(line: String, ranges: List<IntRange>): String {
|
|
307
|
-
var corrected = line
|
|
308
|
-
ranges.forEach { range ->
|
|
309
|
-
range.forEach { pos ->
|
|
310
|
-
if (pos < corrected.length) {
|
|
311
|
-
val replacement = when (corrected[pos]) {
|
|
312
|
-
'O', 'o' -> '0'; 'I', 'l' -> '1'; 'Z' -> '2'
|
|
313
|
-
'S' -> '5'; 'G' -> '6'; 'B' -> '8'; 'D', 'Q' -> '0'
|
|
314
|
-
else -> corrected[pos]
|
|
315
|
-
}
|
|
316
|
-
if (replacement != corrected[pos])
|
|
317
|
-
corrected = corrected.substring(0, pos) + replacement + corrected.substring(pos + 1)
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return corrected
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
private fun correctLetterPositions(line: String, ranges: List<IntRange>): String {
|
|
325
|
-
var corrected = line
|
|
326
|
-
ranges.forEach { range ->
|
|
327
|
-
range.forEach { pos ->
|
|
328
|
-
if (pos < corrected.length) {
|
|
329
|
-
val replacement = when (corrected[pos]) {
|
|
330
|
-
'0' -> 'O'; '1' -> 'I'; '5' -> 'S'; '8' -> 'B'; '2' -> 'Z'; '6' -> 'G'
|
|
331
|
-
else -> corrected[pos]
|
|
332
|
-
}
|
|
333
|
-
if (replacement != corrected[pos])
|
|
334
|
-
corrected = corrected.substring(0, pos) + replacement + corrected.substring(pos + 1)
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return corrected
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
private fun correctCheckDigitPositions(line: String, positions: List<Int>): String {
|
|
342
|
-
var corrected = line
|
|
343
|
-
positions.forEach { pos ->
|
|
344
|
-
if (pos < corrected.length) {
|
|
345
|
-
val replacement = when (corrected[pos]) {
|
|
346
|
-
'O', 'o' -> '0'; 'I', 'l' -> '1'; 'Z' -> '2'
|
|
347
|
-
'S' -> '5'; 'G' -> '6'; 'B' -> '8'
|
|
348
|
-
else -> corrected[pos]
|
|
349
|
-
}
|
|
350
|
-
if (replacement != corrected[pos])
|
|
351
|
-
corrected = corrected.substring(0, pos) + replacement + corrected.substring(pos + 1)
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return corrected
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private fun generateO0Permutations(mrzText: String, maxPermutations: Int = 100): List<String> {
|
|
358
|
-
val permutations = mutableSetOf(mrzText)
|
|
359
|
-
val positions = mrzText.indices
|
|
360
|
-
.filter { val c = mrzText[it]; c == 'O' || c == 'o' || c == '0' }
|
|
361
|
-
.take(7)
|
|
362
|
-
val limit = minOf(1 shl positions.size, maxPermutations)
|
|
363
|
-
for (i in 0 until limit) {
|
|
364
|
-
var variant = mrzText
|
|
365
|
-
positions.forEachIndexed { j, pos ->
|
|
366
|
-
val useZero = (i and (1 shl j)) != 0
|
|
367
|
-
val replacement = if (useZero && variant[pos] != '0') '0'
|
|
368
|
-
else if (!useZero && variant[pos] != 'O') 'O'
|
|
369
|
-
else variant[pos]
|
|
370
|
-
if (replacement != variant[pos])
|
|
371
|
-
variant = variant.substring(0, pos) + replacement + variant.substring(pos + 1)
|
|
372
|
-
}
|
|
373
|
-
permutations.add(variant)
|
|
374
|
-
}
|
|
375
|
-
return permutations.toList()
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
private fun generatePairPermutations(mrzText: String, letter: Char, digit: Char, maxPermutations: Int = 30): List<String> {
|
|
379
|
-
val permutations = mutableSetOf(mrzText)
|
|
380
|
-
val positions = mrzText.indices
|
|
381
|
-
.filter { val c = mrzText[it]; c == letter || c == letter.lowercaseChar() || c == digit }
|
|
382
|
-
.take(5)
|
|
383
|
-
val limit = minOf(1 shl positions.size, maxPermutations)
|
|
384
|
-
for (i in 0 until limit) {
|
|
385
|
-
var variant = mrzText
|
|
386
|
-
positions.forEachIndexed { j, pos ->
|
|
387
|
-
val useDigit = (i and (1 shl j)) != 0
|
|
388
|
-
val replacement = if (useDigit && variant[pos] != digit) digit
|
|
389
|
-
else if (!useDigit && variant[pos] != letter) letter
|
|
390
|
-
else variant[pos]
|
|
391
|
-
if (replacement != variant[pos])
|
|
392
|
-
variant = variant.substring(0, pos) + replacement + variant.substring(pos + 1)
|
|
393
|
-
}
|
|
394
|
-
permutations.add(variant)
|
|
395
|
-
}
|
|
396
|
-
return permutations.toList()
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
private fun generateI1Permutations(mrzText: String, maxPermutations: Int = 50): List<String> {
|
|
400
|
-
val permutations = mutableSetOf(mrzText)
|
|
401
|
-
val positions = mrzText.indices
|
|
402
|
-
.filter { val c = mrzText[it]; c == 'I' || c == 'i' || c == 'l' || c == '1' }
|
|
403
|
-
.take(6)
|
|
404
|
-
val limit = minOf(1 shl positions.size, maxPermutations)
|
|
405
|
-
for (i in 0 until limit) {
|
|
406
|
-
var variant = mrzText
|
|
407
|
-
positions.forEachIndexed { j, pos ->
|
|
408
|
-
val useOne = (i and (1 shl j)) != 0
|
|
409
|
-
val replacement = if (useOne && variant[pos] != '1') '1'
|
|
410
|
-
else if (!useOne && (variant[pos] == '1' || variant[pos] == 'l' || variant[pos] == 'i')) 'I'
|
|
411
|
-
else variant[pos]
|
|
412
|
-
if (replacement != variant[pos])
|
|
413
|
-
variant = variant.substring(0, pos) + replacement + variant.substring(pos + 1)
|
|
414
|
-
}
|
|
415
|
-
permutations.add(variant)
|
|
416
|
-
}
|
|
417
|
-
return permutations.toList()
|
|
418
|
-
}
|
|
419
|
-
}
|
package/ios/MRZValidation.m
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
#import <React/RCTBridgeModule.h>
|
|
2
|
-
|
|
3
|
-
@interface RCT_EXTERN_MODULE(MRZValidation, NSObject)
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Primary MRZ validation with OCR corrections and composite checksum brute-force
|
|
7
|
-
* This is the main entry point for fast native processing
|
|
8
|
-
*/
|
|
9
|
-
RCT_EXTERN_METHOD(validateMRZWithCorrections:(NSString *)ocrText
|
|
10
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
11
|
-
rejecter:(RCTPromiseRejectBlock)reject)
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Validates MRZ text and returns parse result with checksum validation
|
|
15
|
-
*/
|
|
16
|
-
RCT_EXTERN_METHOD(validateMRZ:(NSString *)mrzText
|
|
17
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
18
|
-
rejecter:(RCTPromiseRejectBlock)reject)
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Calculates checksum for MRZ field using 7-3-1 algorithm
|
|
22
|
-
*/
|
|
23
|
-
RCT_EXTERN_METHOD(calculateChecksum:(NSString *)data
|
|
24
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
25
|
-
rejecter:(RCTPromiseRejectBlock)reject)
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Fixes common OCR errors in MRZ text
|
|
29
|
-
*/
|
|
30
|
-
RCT_EXTERN_METHOD(fixMRZText:(NSString *)mrzText
|
|
31
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
32
|
-
rejecter:(RCTPromiseRejectBlock)reject)
|
|
33
|
-
|
|
34
|
-
+ (BOOL)requiresMainQueueSetup
|
|
35
|
-
{
|
|
36
|
-
return NO;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@end
|