@trustchex/react-native-sdk 1.355.1 → 1.357.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.
Files changed (254) hide show
  1. package/README.md +2 -9
  2. package/TrustchexSDK.podspec +5 -4
  3. package/android/build.gradle +6 -4
  4. package/android/src/main/AndroidManifest.xml +1 -1
  5. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +45 -25
  6. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +168 -0
  7. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +871 -0
  8. package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +245 -0
  9. package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +785 -0
  10. package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +419 -0
  11. package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +818 -0
  12. package/ios/Camera/TrustchexCameraManager.m +37 -0
  13. package/ios/Camera/TrustchexCameraManager.swift +125 -0
  14. package/ios/Camera/TrustchexCameraView.swift +1176 -0
  15. package/ios/MLKit/MLKitModule.m +23 -0
  16. package/ios/MLKit/MLKitModule.swift +250 -0
  17. package/ios/MRZValidation.m +39 -0
  18. package/ios/MRZValidation.swift +802 -0
  19. package/ios/MRZValidator.swift +466 -0
  20. package/ios/OpenCV/OpenCVModule.h +4 -0
  21. package/ios/OpenCV/OpenCVModule.mm +810 -0
  22. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +2 -3
  23. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +1 -2
  24. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +418 -193
  25. package/lib/module/Screens/Static/OTPVerificationScreen.js +11 -11
  26. package/lib/module/Screens/Static/QrCodeScanningScreen.js +5 -1
  27. package/lib/module/Screens/Static/ResultScreen.js +25 -2
  28. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +25 -7
  29. package/lib/module/Shared/Components/DebugNavigationPanel.js +234 -24
  30. package/lib/module/Shared/Components/EIDScanner.js +99 -9
  31. package/lib/module/Shared/Components/FaceCamera.js +170 -179
  32. package/lib/module/Shared/Components/IdentityDocumentCamera.js +2151 -771
  33. package/lib/module/Shared/Components/QrCodeScannerCamera.js +109 -107
  34. package/lib/module/Shared/Components/TrustchexCamera.js +122 -0
  35. package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +91 -0
  36. package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +2 -124
  37. package/lib/module/Shared/EIDReader/tlv/tlvInputStream.js +4 -4
  38. package/lib/module/Shared/EIDReader/tlv/tlvOutputState.js +4 -4
  39. package/lib/module/Shared/EIDReader/tlv/tlvOutputStream.js +4 -4
  40. package/lib/module/Shared/Libs/analytics.utils.js +2 -2
  41. package/lib/module/Shared/Libs/debug.utils.js +132 -0
  42. package/lib/module/Shared/Libs/deeplink.utils.js +6 -5
  43. package/lib/module/Shared/Libs/demo.utils.js +13 -3
  44. package/lib/module/Shared/Libs/mrz.utils.js +1 -175
  45. package/lib/module/Shared/Libs/native-device-info.utils.js +12 -6
  46. package/lib/module/Shared/Libs/tts.utils.js +40 -6
  47. package/lib/module/Shared/Services/AnalyticsService.js +9 -8
  48. package/lib/module/Shared/Types/mrzFields.js +1 -0
  49. package/lib/module/Translation/Resources/en.js +87 -88
  50. package/lib/module/Translation/Resources/tr.js +84 -85
  51. package/lib/module/Trustchex.js +9 -2
  52. package/lib/module/index.js +1 -0
  53. package/lib/module/version.js +1 -1
  54. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  55. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  56. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  57. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
  58. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  59. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  60. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  61. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  62. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts +2 -2
  63. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  64. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +18 -4
  65. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  66. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -4
  67. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  68. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts +2 -1
  69. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  70. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +124 -0
  71. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -0
  72. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts +11 -0
  73. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.helpers.d.ts.map +1 -0
  74. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts +2 -39
  75. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
  76. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  77. package/lib/typescript/src/Shared/Libs/debug.utils.d.ts +42 -0
  78. package/lib/typescript/src/Shared/Libs/debug.utils.d.ts.map +1 -0
  79. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  80. package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
  81. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  82. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +0 -4
  83. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  84. package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts.map +1 -1
  85. package/lib/typescript/src/Shared/Libs/tts.utils.d.ts +4 -3
  86. package/lib/typescript/src/Shared/Libs/tts.utils.d.ts.map +1 -1
  87. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -1
  88. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +2 -2
  89. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  90. package/lib/typescript/src/Shared/Types/mrzFields.d.ts +11 -0
  91. package/lib/typescript/src/Shared/Types/mrzFields.d.ts.map +1 -0
  92. package/lib/typescript/src/Translation/Resources/en.d.ts +4 -5
  93. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  94. package/lib/typescript/src/Translation/Resources/tr.d.ts +4 -5
  95. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  96. package/lib/typescript/src/Trustchex.d.ts +2 -0
  97. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  98. package/lib/typescript/src/index.d.ts +1 -0
  99. package/lib/typescript/src/index.d.ts.map +1 -1
  100. package/lib/typescript/src/version.d.ts +1 -1
  101. package/package.json +4 -35
  102. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +1 -1
  103. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +7 -5
  104. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +2 -3
  105. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +498 -216
  106. package/src/Screens/Static/OTPVerificationScreen.tsx +37 -31
  107. package/src/Screens/Static/QrCodeScanningScreen.tsx +8 -1
  108. package/src/Screens/Static/ResultScreen.tsx +136 -88
  109. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +46 -13
  110. package/src/Shared/Components/DebugNavigationPanel.tsx +290 -34
  111. package/src/Shared/Components/EIDScanner.tsx +94 -16
  112. package/src/Shared/Components/FaceCamera.tsx +236 -203
  113. package/src/Shared/Components/IdentityDocumentCamera.tsx +3073 -1030
  114. package/src/Shared/Components/QrCodeScannerCamera.tsx +133 -127
  115. package/src/Shared/Components/TrustchexCamera.tsx +289 -0
  116. package/src/Shared/Config/camera-enhancement.config.ts +2 -2
  117. package/src/Shared/EIDReader/tlv/tlv.helpers.ts +96 -0
  118. package/src/Shared/EIDReader/tlv/tlv.utils.ts +2 -125
  119. package/src/Shared/EIDReader/tlv/tlvInputStream.ts +4 -4
  120. package/src/Shared/EIDReader/tlv/tlvOutputState.ts +4 -4
  121. package/src/Shared/EIDReader/tlv/tlvOutputStream.ts +4 -4
  122. package/src/Shared/Libs/analytics.utils.ts +48 -20
  123. package/src/Shared/Libs/debug.utils.ts +149 -0
  124. package/src/Shared/Libs/deeplink.utils.ts +7 -5
  125. package/src/Shared/Libs/demo.utils.ts +4 -0
  126. package/src/Shared/Libs/http-client.ts +12 -8
  127. package/src/Shared/Libs/mrz.utils.ts +1 -163
  128. package/src/Shared/Libs/native-device-info.utils.ts +12 -6
  129. package/src/Shared/Libs/tts.utils.ts +48 -6
  130. package/src/Shared/Services/AnalyticsService.ts +69 -24
  131. package/src/Shared/Types/identificationInfo.ts +2 -2
  132. package/src/Shared/Types/mrzFields.ts +29 -0
  133. package/src/Translation/Resources/en.ts +90 -100
  134. package/src/Translation/Resources/tr.ts +89 -97
  135. package/src/Translation/index.ts +1 -1
  136. package/src/Trustchex.tsx +21 -4
  137. package/src/index.tsx +14 -0
  138. package/src/version.ts +1 -1
  139. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/barcodescanner/BarcodeScannerFrameProcessorPlugin.kt +0 -301
  140. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/BitmapUtils.kt +0 -205
  141. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/CropperPlugin.kt +0 -72
  142. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/cropper/FrameMetadata.kt +0 -4
  143. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/facedetector/FaceDetectorFrameProcessorPlugin.kt +0 -303
  144. package/android/src/main/java/com/trustchex/reactnativesdk/visioncameraplugins/textrecognition/TextRecognitionFrameProcessorPlugin.kt +0 -115
  145. package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin-Bridging-Header.h +0 -9
  146. package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.mm +0 -22
  147. package/ios/VisionCameraPlugins/BarcodeScanner/BarcodeScannerFrameProcessorPlugin.swift +0 -188
  148. package/ios/VisionCameraPlugins/Cropper/Cropper-Bridging-Header.h +0 -13
  149. package/ios/VisionCameraPlugins/Cropper/Cropper.h +0 -20
  150. package/ios/VisionCameraPlugins/Cropper/Cropper.mm +0 -22
  151. package/ios/VisionCameraPlugins/Cropper/Cropper.swift +0 -145
  152. package/ios/VisionCameraPlugins/Cropper/CropperUtils.swift +0 -49
  153. package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin-Bridging-Header.h +0 -4
  154. package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.mm +0 -22
  155. package/ios/VisionCameraPlugins/FaceDetector/FaceDetectorFrameProcessorPlugin.swift +0 -320
  156. package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin-Bridging-Header.h +0 -4
  157. package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.mm +0 -27
  158. package/ios/VisionCameraPlugins/TextRecognition/TextRecognitionFrameProcessorPlugin.swift +0 -144
  159. package/lib/module/Shared/Libs/camera.utils.js +0 -308
  160. package/lib/module/Shared/Libs/frame-enhancement.utils.js +0 -133
  161. package/lib/module/Shared/Libs/opencv.utils.js +0 -21
  162. package/lib/module/Shared/Libs/worklet.utils.js +0 -68
  163. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.js +0 -46
  164. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +0 -35
  165. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/index.js +0 -19
  166. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.js +0 -26
  167. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/types.js +0 -3
  168. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.js +0 -197
  169. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.js +0 -101
  170. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.js +0 -60
  171. package/lib/module/Shared/VisionCameraPlugins/Cropper/index.js +0 -47
  172. package/lib/module/Shared/VisionCameraPlugins/FaceDetector/Camera.js +0 -42
  173. package/lib/module/Shared/VisionCameraPlugins/FaceDetector/detectFaces.js +0 -35
  174. package/lib/module/Shared/VisionCameraPlugins/FaceDetector/index.js +0 -4
  175. package/lib/module/Shared/VisionCameraPlugins/FaceDetector/types.js +0 -3
  176. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/Camera.js +0 -56
  177. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.js +0 -20
  178. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.js +0 -9
  179. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/index.js +0 -6
  180. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/scanText.js +0 -20
  181. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/translateText.js +0 -19
  182. package/lib/module/Shared/VisionCameraPlugins/TextRecognition/types.js +0 -3
  183. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +0 -87
  184. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +0 -1
  185. package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts +0 -25
  186. package/lib/typescript/src/Shared/Libs/frame-enhancement.utils.d.ts.map +0 -1
  187. package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts +0 -3
  188. package/lib/typescript/src/Shared/Libs/opencv.utils.d.ts.map +0 -1
  189. package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts +0 -9
  190. package/lib/typescript/src/Shared/Libs/worklet.utils.d.ts.map +0 -1
  191. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts +0 -13
  192. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.d.ts.map +0 -1
  193. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts +0 -6
  194. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +0 -1
  195. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts +0 -12
  196. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/index.d.ts.map +0 -1
  197. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts +0 -3
  198. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.d.ts.map +0 -1
  199. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts +0 -52
  200. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/types.d.ts.map +0 -1
  201. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts +0 -62
  202. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.d.ts.map +0 -1
  203. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts +0 -34
  204. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.d.ts.map +0 -1
  205. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts +0 -32
  206. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.d.ts.map +0 -1
  207. package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts +0 -23
  208. package/lib/typescript/src/Shared/VisionCameraPlugins/Cropper/index.d.ts.map +0 -1
  209. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts +0 -9
  210. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/Camera.d.ts.map +0 -1
  211. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts +0 -3
  212. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.d.ts.map +0 -1
  213. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts +0 -3
  214. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/index.d.ts.map +0 -1
  215. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts +0 -79
  216. package/lib/typescript/src/Shared/VisionCameraPlugins/FaceDetector/types.d.ts.map +0 -1
  217. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts +0 -6
  218. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/Camera.d.ts.map +0 -1
  219. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts +0 -3
  220. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.d.ts.map +0 -1
  221. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts +0 -3
  222. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.d.ts.map +0 -1
  223. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts +0 -5
  224. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/index.d.ts.map +0 -1
  225. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts +0 -3
  226. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/scanText.d.ts.map +0 -1
  227. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts +0 -3
  228. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/translateText.d.ts.map +0 -1
  229. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts +0 -67
  230. package/lib/typescript/src/Shared/VisionCameraPlugins/TextRecognition/types.d.ts.map +0 -1
  231. package/src/Shared/Libs/camera.utils.ts +0 -345
  232. package/src/Shared/Libs/frame-enhancement.utils.ts +0 -217
  233. package/src/Shared/Libs/opencv.utils.ts +0 -40
  234. package/src/Shared/Libs/worklet.utils.ts +0 -72
  235. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useBarcodeScanner.ts +0 -79
  236. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +0 -46
  237. package/src/Shared/VisionCameraPlugins/BarcodeScanner/index.ts +0 -60
  238. package/src/Shared/VisionCameraPlugins/BarcodeScanner/scanCodes.ts +0 -32
  239. package/src/Shared/VisionCameraPlugins/BarcodeScanner/types.ts +0 -82
  240. package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/convert.ts +0 -195
  241. package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/geometry.ts +0 -135
  242. package/src/Shared/VisionCameraPlugins/BarcodeScanner/utils/highlights.ts +0 -84
  243. package/src/Shared/VisionCameraPlugins/Cropper/index.ts +0 -78
  244. package/src/Shared/VisionCameraPlugins/FaceDetector/Camera.tsx +0 -63
  245. package/src/Shared/VisionCameraPlugins/FaceDetector/detectFaces.ts +0 -44
  246. package/src/Shared/VisionCameraPlugins/FaceDetector/index.ts +0 -3
  247. package/src/Shared/VisionCameraPlugins/FaceDetector/types.ts +0 -99
  248. package/src/Shared/VisionCameraPlugins/TextRecognition/Camera.tsx +0 -76
  249. package/src/Shared/VisionCameraPlugins/TextRecognition/PhotoRecognizer.ts +0 -18
  250. package/src/Shared/VisionCameraPlugins/TextRecognition/RemoveLanguageModel.ts +0 -7
  251. package/src/Shared/VisionCameraPlugins/TextRecognition/index.ts +0 -7
  252. package/src/Shared/VisionCameraPlugins/TextRecognition/scanText.ts +0 -27
  253. package/src/Shared/VisionCameraPlugins/TextRecognition/translateText.ts +0 -21
  254. package/src/Shared/VisionCameraPlugins/TextRecognition/types.ts +0 -141
@@ -0,0 +1,802 @@
1
+ import Foundation
2
+ import React
3
+
4
+ /**
5
+ * Native MRZ Validation Module for iOS
6
+ *
7
+ * Performs high-performance MRZ detection and validation with checksum verification
8
+ * according to ICAO 9303 standards. This native implementation is significantly faster
9
+ * than JavaScript parsing, especially for real-time camera frame processing.
10
+ *
11
+ * ICAO 9303 Compliance:
12
+ * - TD1: Identity cards (3 lines × 30 characters)
13
+ * - TD2: Identity cards (2 lines × 36 characters)
14
+ * - TD3: Passports (2 lines × 44 characters)
15
+ * - Check digit algorithm: 7-3-1 weighted modulo 10
16
+ * - Character values: 0-9 = 0-9, A-Z = 10-35, < = 0
17
+ * - Validates: document number, birth date, expiry date, optional/personal number, composite checksums
18
+ *
19
+ * Key features:
20
+ * - Native checksum calculation (7-3-1 algorithm)
21
+ * - MRZ format detection (TD1, TD2, TD3)
22
+ * - Field extraction and validation
23
+ * - OCR correction with position-aware character mapping
24
+ * - Composite checksum brute-force correction
25
+ *
26
+ * @see https://www.icao.int/publications/Documents/9303_p3_cons_en.pdf
27
+ */
28
+ @objc(MRZValidation)
29
+ class MRZValidation: NSObject {
30
+
31
+ enum MRZFormat: String {
32
+ case TD1 = "TD1" // ID card, 3 lines of 30 chars
33
+ case TD2 = "TD2" // ID card, 2 lines of 36 chars
34
+ case TD3 = "TD3" // Passport, 2 lines of 44 chars
35
+ case UNKNOWN = "UNKNOWN"
36
+ }
37
+
38
+ /**
39
+ * Primary MRZ validation with OCR corrections and composite checksum brute-force
40
+ * This is the main entry point for fast native processing
41
+ */
42
+ @objc
43
+ func validateMRZWithCorrections(_ ocrText: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
44
+ do {
45
+ // Fix and clean raw OCR text
46
+ let fixedText = fixMRZ(ocrText)
47
+ var lines = fixedText.trimmingCharacters(in: .whitespacesAndNewlines)
48
+ .components(separatedBy: "\n")
49
+ .map { $0.trimmingCharacters(in: .whitespaces) }
50
+
51
+ if lines.isEmpty || fixedText.count < 60 {
52
+ resolve(createInvalidResult(error: "MRZ text too short"))
53
+ return
54
+ }
55
+
56
+ let format = detectMRZFormat(lines)
57
+ guard format != .UNKNOWN else {
58
+ resolve(createInvalidResult(error: "Unknown MRZ format"))
59
+ return
60
+ }
61
+
62
+ // Phase 1: Try parsing with position-aware OCR corrections
63
+ print("[MRZ Debug] Phase 1: Attempting position-aware OCR corrections")
64
+ var result = try parseMRZ(fixedText)
65
+ if let valid = result["valid"] as? Bool, valid == true {
66
+ print("[MRZ Debug] Phase 1: Valid MRZ found via position-aware corrections")
67
+ resolve(result)
68
+ return
69
+ }
70
+
71
+ // Phase 2: Try composite checksum brute-force if only composite is invalid
72
+ if let checksums = result["checksums"] as? [String: Any],
73
+ let invalidFields = checksums["invalidFields"] as? [String],
74
+ invalidFields.count == 1,
75
+ invalidFields[0] == "compositeCheckDigit" {
76
+
77
+ print("[MRZ Debug] Phase 2: Attempting composite checksum brute-force")
78
+
79
+ let compositePos: (line: Int, pos: Int)?
80
+ switch format {
81
+ case .TD1: compositePos = (1, 29)
82
+ case .TD2: compositePos = (1, 35)
83
+ case .TD3: compositePos = (1, 43)
84
+ case .UNKNOWN: compositePos = nil
85
+ }
86
+
87
+ if let pos = compositePos {
88
+ for digit in 0...9 {
89
+ var testLines = lines
90
+ let targetLine = testLines[pos.line]
91
+ let startIdx = targetLine.index(targetLine.startIndex, offsetBy: pos.pos)
92
+ let endIdx = targetLine.index(startIdx, offsetBy: 1)
93
+ var chars = Array(targetLine)
94
+ chars[pos.pos] = Character(String(digit))
95
+ testLines[pos.line] = String(chars)
96
+
97
+ let testMRZ = testLines.joined(separator: "\n")
98
+ let testResult = try parseMRZ(testMRZ)
99
+
100
+ if let valid = testResult["valid"] as? Bool, valid == true {
101
+ print("[MRZ Debug] Phase 2: Valid MRZ found via composite digit \(digit)")
102
+ resolve(testResult)
103
+ return
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ // Phase 3: Try O/0 permutations
110
+ print("[MRZ Debug] Phase 3: Attempting O/0 permutations")
111
+ let o0Permutations = generateO0Permutations(fixedText, maxPermutations: 100)
112
+ for (index, permutation) in o0Permutations.enumerated() {
113
+ let testResult = try parseMRZ(permutation)
114
+ if let valid = testResult["valid"] as? Bool, valid == true {
115
+ print("[MRZ Debug] Phase 3: Valid MRZ found via O/0 permutation \(index + 1)/\(o0Permutations.count)")
116
+ resolve(testResult)
117
+ return
118
+ }
119
+ }
120
+
121
+ // Phase 4: Try I/1 permutations as last resort
122
+ print("[MRZ Debug] Phase 4: Attempting I/1 permutations")
123
+ let i1Permutations = generateI1Permutations(fixedText, maxPermutations: 50)
124
+ for (index, permutation) in i1Permutations.enumerated() {
125
+ let testResult = try parseMRZ(permutation)
126
+ if let valid = testResult["valid"] as? Bool, valid == true {
127
+ print("[MRZ Debug] Phase 4: Valid MRZ found via I/1 permutation \(index + 1)/\(i1Permutations.count)")
128
+ resolve(testResult)
129
+ return
130
+ }
131
+ }
132
+
133
+ // Return invalid result if all attempts failed
134
+ print("[MRZ Debug] All correction phases failed")
135
+ resolve(result)
136
+ } catch {
137
+ reject("MRZ_VALIDATION_ERROR", "Failed to validate MRZ: \(error.localizedDescription)", error)
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Validates MRZ text and returns parse result with checksum validation
143
+ */
144
+ @objc
145
+ func validateMRZ(_ mrzText: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
146
+ do {
147
+ let result = try parseMRZ(mrzText)
148
+ resolve(result)
149
+ } catch {
150
+ reject("MRZ_VALIDATION_ERROR", "Failed to validate MRZ: \(error.localizedDescription)", error)
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Create invalid result response
156
+ */
157
+ private func createInvalidResult(error: String) -> [String: Any] {
158
+ return [
159
+ "valid": false,
160
+ "error": error,
161
+ "format": "UNKNOWN"
162
+ ]
163
+ }
164
+
165
+ /**
166
+ * Calculates checksum for MRZ field using 7-3-1 algorithm
167
+ */
168
+ @objc
169
+ func calculateChecksum(_ data: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
170
+ do {
171
+ let checksum = try calculateMRZChecksum(data)
172
+ resolve(String(checksum))
173
+ } catch {
174
+ reject("CHECKSUM_ERROR", "Failed to calculate checksum: \(error.localizedDescription)", error)
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Fixes common OCR errors in MRZ text
180
+ */
181
+ @objc
182
+ func fixMRZText(_ mrzText: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
183
+ do {
184
+ let fixed = fixMRZ(mrzText)
185
+ resolve(fixed)
186
+ } catch {
187
+ reject("MRZ_FIX_ERROR", "Failed to fix MRZ text: \(error.localizedDescription)", error)
188
+ }
189
+ }
190
+
191
+ // MARK: - Private Methods
192
+
193
+ /**
194
+ * Main MRZ parser - validates format and checksums
195
+ */
196
+ private func parseMRZ(_ rawText: String) throws -> [String: Any] {
197
+ var result: [String: Any] = [:]
198
+ let lines = rawText.trimmingCharacters(in: .whitespacesAndNewlines)
199
+ .components(separatedBy: "\n")
200
+ .map { $0.trimmingCharacters(in: .whitespaces) }
201
+
202
+ // Detect format
203
+ let format = detectMRZFormat(lines)
204
+ result["format"] = format.rawValue
205
+
206
+ guard format != .UNKNOWN else {
207
+ result["valid"] = false
208
+ result["error"] = "Unknown MRZ format"
209
+ return result
210
+ }
211
+
212
+ // Parse based on format
213
+ let fields: [String: Any]
214
+ switch format {
215
+ case .TD1:
216
+ fields = try parseTD1(lines)
217
+ case .TD2:
218
+ fields = try parseTD2(lines)
219
+ case .TD3:
220
+ fields = try parseTD3(lines)
221
+ case .UNKNOWN:
222
+ result["valid"] = false
223
+ result["error"] = "Unsupported format"
224
+ return result
225
+ }
226
+
227
+ result["fields"] = fields
228
+
229
+ // Validate all checksums
230
+ let checksumValidation = try validateChecksums(format: format, lines: lines)
231
+ result["valid"] = checksumValidation["allValid"] as? Bool ?? false
232
+ result["checksums"] = checksumValidation
233
+
234
+ return result
235
+ }
236
+
237
+ /**
238
+ * Detects MRZ format from line structure
239
+ */
240
+ private func detectMRZFormat(_ lines: [String]) -> MRZFormat {
241
+ if lines.count == 3 && lines.allSatisfy({ $0.count == 30 }) {
242
+ return .TD1
243
+ } else if lines.count == 2 && lines.allSatisfy({ $0.count == 36 }) {
244
+ return .TD2
245
+ } else if lines.count == 2 && lines.allSatisfy({ $0.count == 44 }) {
246
+ return .TD3
247
+ }
248
+ return .UNKNOWN
249
+ }
250
+
251
+ /**
252
+ * Parse TD1 format (ID card, 3 lines of 30 chars)
253
+ */
254
+ private func parseTD1(_ lines: [String]) throws -> [String: Any] {
255
+ guard lines.count >= 3 else {
256
+ throw NSError(domain: "MRZValidation", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid TD1 format"])
257
+ }
258
+
259
+ var fields: [String: Any] = [:]
260
+
261
+ // Line 1
262
+ let line1 = lines[0]
263
+ fields["documentCode"] = String(line1.prefix(2)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
264
+ fields["issuingState"] = String(line1.dropFirst(2).prefix(3)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
265
+ fields["documentNumber"] = String(line1.dropFirst(5).prefix(9)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
266
+ fields["documentNumberCheckDigit"] = String(line1.dropFirst(14).prefix(1))
267
+ fields["optional1"] = String(line1.dropFirst(15)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
268
+
269
+ // Line 2
270
+ let line2 = lines[1]
271
+ fields["birthDate"] = String(line2.prefix(6))
272
+ fields["birthDateCheckDigit"] = String(line2.dropFirst(6).prefix(1))
273
+ fields["sex"] = String(line2.dropFirst(7).prefix(1))
274
+ fields["expirationDate"] = String(line2.dropFirst(8).prefix(6))
275
+ fields["expirationDateCheckDigit"] = String(line2.dropFirst(14).prefix(1))
276
+ fields["nationality"] = String(line2.dropFirst(15).prefix(3)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
277
+ fields["optional2"] = String(line2.dropFirst(18).prefix(11)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
278
+ fields["compositeCheckDigit"] = String(line2.dropFirst(29).prefix(1))
279
+
280
+ // Line 3 - Names
281
+ let line3 = lines[2]
282
+ let names = line3.components(separatedBy: "<<")
283
+ fields["lastName"] = names[0].replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
284
+ fields["firstName"] = names.count > 1 ? names[1].replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces) : ""
285
+
286
+ return fields
287
+ }
288
+
289
+ /**
290
+ * Parse TD2 format (ID card, 2 lines of 36 chars)
291
+ */
292
+ private func parseTD2(_ lines: [String]) throws -> [String: Any] {
293
+ guard lines.count >= 2 else {
294
+ throw NSError(domain: "MRZValidation", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid TD2 format"])
295
+ }
296
+
297
+ var fields: [String: Any] = [:]
298
+
299
+ // Line 1
300
+ let line1 = lines[0]
301
+ fields["documentCode"] = String(line1.prefix(2)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
302
+ fields["issuingState"] = String(line1.dropFirst(2).prefix(3)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
303
+ let names = String(line1.dropFirst(5)).components(separatedBy: "<<")
304
+ fields["lastName"] = names[0].replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
305
+ fields["firstName"] = names.count > 1 ? names[1].replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces) : ""
306
+
307
+ // Line 2
308
+ let line2 = lines[1]
309
+ fields["documentNumber"] = String(line2.prefix(9)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
310
+ fields["documentNumberCheckDigit"] = String(line2.dropFirst(9).prefix(1))
311
+ fields["nationality"] = String(line2.dropFirst(10).prefix(3)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
312
+ fields["birthDate"] = String(line2.dropFirst(13).prefix(6))
313
+ fields["birthDateCheckDigit"] = String(line2.dropFirst(19).prefix(1))
314
+ fields["sex"] = String(line2.dropFirst(20).prefix(1))
315
+ fields["expirationDate"] = String(line2.dropFirst(21).prefix(6))
316
+ fields["expirationDateCheckDigit"] = String(line2.dropFirst(27).prefix(1))
317
+ fields["optional1"] = String(line2.dropFirst(28).prefix(7)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
318
+ fields["compositeCheckDigit"] = String(line2.dropFirst(35).prefix(1))
319
+
320
+ return fields
321
+ }
322
+
323
+ /**
324
+ * Parse TD3 format (Passport, 2 lines of 44 chars)
325
+ */
326
+ private func parseTD3(_ lines: [String]) throws -> [String: Any] {
327
+ guard lines.count >= 2 else {
328
+ throw NSError(domain: "MRZValidation", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid TD3 format"])
329
+ }
330
+
331
+ var fields: [String: Any] = [:]
332
+
333
+ // Line 1
334
+ let line1 = lines[0]
335
+ fields["documentCode"] = String(line1.prefix(2)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
336
+ fields["issuingState"] = String(line1.dropFirst(2).prefix(3)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
337
+ let names = String(line1.dropFirst(5)).components(separatedBy: "<<")
338
+ fields["lastName"] = names[0].replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
339
+ fields["firstName"] = names.count > 1 ? names[1].replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces) : ""
340
+
341
+ // Line 2
342
+ let line2 = lines[1]
343
+ fields["documentNumber"] = String(line2.prefix(9)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
344
+ fields["documentNumberCheckDigit"] = String(line2.dropFirst(9).prefix(1))
345
+ fields["nationality"] = String(line2.dropFirst(10).prefix(3)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
346
+ fields["birthDate"] = String(line2.dropFirst(13).prefix(6))
347
+ fields["birthDateCheckDigit"] = String(line2.dropFirst(19).prefix(1))
348
+ fields["sex"] = String(line2.dropFirst(20).prefix(1))
349
+ fields["expirationDate"] = String(line2.dropFirst(21).prefix(6))
350
+ fields["expirationDateCheckDigit"] = String(line2.dropFirst(27).prefix(1))
351
+ fields["optional1"] = String(line2.dropFirst(28).prefix(14)).replacingOccurrences(of: "<", with: " ").trimmingCharacters(in: .whitespaces)
352
+ fields["optional1CheckDigit"] = String(line2.dropFirst(42).prefix(1))
353
+ fields["compositeCheckDigit"] = String(line2.dropFirst(43).prefix(1))
354
+
355
+ return fields
356
+ }
357
+
358
+ /**
359
+ * Validates all checksums in MRZ according to ICAO 9303 Part 3
360
+ *
361
+ * ICAO 9303 Check Digit Calculation:
362
+ * - Weights: 7, 3, 1 (cycling)
363
+ * - Character values: 0-9 = 0-9, A-Z = 10-35, < (filler) = 0
364
+ * - Result: (sum of weighted values) mod 10
365
+ *
366
+ * @see https://www2023.icao.int/publications/Documents/9303_p3_cons_en.pdf Section 4.9
367
+ */
368
+ private func validateChecksums(format: MRZFormat, lines: [String]) throws -> [String: Any] {
369
+ var result: [String: Any] = [:]
370
+ var invalidFields: [String] = []
371
+ var allValid = true
372
+
373
+ switch format {
374
+ case .TD1:
375
+ // TD1 Format (ID card, 3 lines × 30 characters)
376
+ // Line 1: Document code(2) + Issuing state(3) + Document number(9) + Check(1) + Optional(15)
377
+ // Line 2: Birth date(6) + Check(1) + Sex(1) + Expiry(6) + Check(1) + Nationality(3) + Optional(11) + Composite check(1)
378
+ // Line 3: Names
379
+
380
+ let line1 = lines[0]
381
+ let line2 = lines[1]
382
+
383
+ // Document number checksum - ICAO 9303 Part 3, Section 4.9
384
+ let docNum = String(line1.dropFirst(5).prefix(9)) // Positions 5-13
385
+ let docNumCheck = line1[line1.index(line1.startIndex, offsetBy: 14)] // Position 14
386
+ let docNumValid = try calculateMRZChecksum(docNum) == docNumCheck
387
+ result["documentNumberCheckDigit"] = docNumValid
388
+ if !docNumValid {
389
+ invalidFields.append("documentNumberCheckDigit")
390
+ allValid = false
391
+ }
392
+
393
+ // Birth date checksum
394
+ let birthDate = String(line2.prefix(6)) // Positions 0-5
395
+ let birthDateCheck = line2[line2.index(line2.startIndex, offsetBy: 6)] // Position 6
396
+ let birthDateValid = try calculateMRZChecksum(birthDate) == birthDateCheck
397
+ result["birthDateCheckDigit"] = birthDateValid
398
+ if !birthDateValid {
399
+ invalidFields.append("birthDateCheckDigit")
400
+ allValid = false
401
+ }
402
+
403
+ // Expiry date checksum
404
+ let expiryDate = String(line2.dropFirst(8).prefix(6)) // Positions 8-13
405
+ let expiryDateCheck = line2[line2.index(line2.startIndex, offsetBy: 14)] // Position 14
406
+ let expiryDateValid = try calculateMRZChecksum(expiryDate) == expiryDateCheck
407
+ result["expirationDateCheckDigit"] = expiryDateValid
408
+ if !expiryDateValid {
409
+ invalidFields.append("expirationDateCheckDigit")
410
+ allValid = false
411
+ }
412
+
413
+ // Composite checksum - ICAO 9303 Part 3, Section 4.9
414
+ // TD1 Composite = Line1(5-29) + Line2(0-6) + Line2(8-14) + Line2(18-28)
415
+ // This includes: doc number(9) + check(1) + optional(15) + birth(6) + check(1) + expiry(6) + check(1) + optional(11)
416
+ // Total: 50 characters
417
+ let composite = String(line1.dropFirst(5)) + String(line2.prefix(7)) + String(line2.dropFirst(8).prefix(7)) + String(line2.dropFirst(18).prefix(11))
418
+ let compositeCheck = line2[line2.index(line2.startIndex, offsetBy: 29)] // Position 29
419
+ let compositeValid = try calculateMRZChecksum(composite) == compositeCheck
420
+ result["compositeCheckDigit"] = compositeValid
421
+ if !compositeValid {
422
+ invalidFields.append("compositeCheckDigit")
423
+ allValid = false
424
+ }
425
+
426
+ case .TD2, .TD3:
427
+ // TD2 Format (ID card, 2 lines × 36 characters)
428
+ // Line 1: Document code(2) + Issuing state(3) + Names(31)
429
+ // Line 2: Doc number(9) + Check(1) + Nationality(3) + Birth(6) + Check(1) + Sex(1) + Expiry(6) + Check(1) + Optional(7) + Composite check(1)
430
+ //
431
+ // TD3 Format (Passport, 2 lines × 44 characters)
432
+ // Line 1: Document code(2) + Issuing state(3) + Names(39)
433
+ // Line 2: Doc number(9) + Check(1) + Nationality(3) + Birth(6) + Check(1) + Sex(1) + Expiry(6) + Check(1) + Personal number(14) + Check(1) + Composite check(1)
434
+
435
+ let line = lines[1]
436
+
437
+ // Document number checksum - ICAO 9303 Part 3, Section 4.9
438
+ let docNum = String(line.prefix(9)) // Positions 0-8
439
+ let docNumCheck = line[line.index(line.startIndex, offsetBy: 9)] // Position 9
440
+ let docNumValid = try calculateMRZChecksum(docNum) == docNumCheck
441
+ result["documentNumberCheckDigit"] = docNumValid
442
+ if !docNumValid {
443
+ invalidFields.append("documentNumberCheckDigit")
444
+ allValid = false
445
+ }
446
+
447
+ // Birth date checksum
448
+ let birthDate = String(line.dropFirst(13).prefix(6)) // Positions 13-18
449
+ let birthDateCheck = line[line.index(line.startIndex, offsetBy: 19)] // Position 19
450
+ let birthDateValid = try calculateMRZChecksum(birthDate) == birthDateCheck
451
+ result["birthDateCheckDigit"] = birthDateValid
452
+ if !birthDateValid {
453
+ invalidFields.append("birthDateCheckDigit")
454
+ allValid = false
455
+ }
456
+
457
+ // Expiry date checksum
458
+ let expiryDate = String(line.dropFirst(21).prefix(6)) // Positions 21-26
459
+ let expiryDateCheck = line[line.index(line.startIndex, offsetBy: 27)] // Position 27
460
+ let expiryDateValid = try calculateMRZChecksum(expiryDate) == expiryDateCheck
461
+ result["expirationDateCheckDigit"] = expiryDateValid
462
+ if !expiryDateValid {
463
+ invalidFields.append("expirationDateCheckDigit")
464
+ allValid = false
465
+ }
466
+
467
+ // TD3 has personal number checksum (optional field)
468
+ if format == .TD3 {
469
+ let personalNum = String(line.dropFirst(28).prefix(14)) // Positions 28-41
470
+ let personalNumCheck = line[line.index(line.startIndex, offsetBy: 42)] // Position 42
471
+ let personalNumValid = try calculateMRZChecksum(personalNum) == personalNumCheck
472
+ result["optional1CheckDigit"] = personalNumValid
473
+ if !personalNumValid {
474
+ invalidFields.append("optional1CheckDigit")
475
+ allValid = false
476
+ }
477
+ }
478
+
479
+ // Composite checksum - ICAO 9303 Part 3, Section 4.9
480
+ // TD2: Doc number(9) + check(1) + Birth(6) + check(1) + Expiry(6) + check(1) + Optional(7) = 31 chars
481
+ // TD3: Doc number(9) + check(1) + Birth(6) + check(1) + Expiry(6) + check(1) + Personal(14) + check(1) = 39 chars
482
+ let compositeCheck = line[line.index(line.startIndex, offsetBy: format == .TD2 ? 35 : 43)]
483
+ let composite: String
484
+ if format == .TD2 {
485
+ // TD2: positions 0-9 + 13-19 + 21-34 (excluding composite at 35)
486
+ composite = String(line.prefix(10)) + String(line.dropFirst(13).prefix(7)) + String(line.dropFirst(21).prefix(14))
487
+ } else {
488
+ // TD3: positions 0-9 + 13-19 + 21-42 (excluding composite at 43)
489
+ composite = String(line.prefix(10)) + String(line.dropFirst(13).prefix(7)) + String(line.dropFirst(21).prefix(22))
490
+ }
491
+ let compositeValid = try calculateMRZChecksum(composite) == compositeCheck
492
+ result["compositeCheckDigit"] = compositeValid
493
+ if !compositeValid {
494
+ invalidFields.append("compositeCheckDigit")
495
+ allValid = false
496
+ }
497
+
498
+ case .UNKNOWN:
499
+ break
500
+ }
501
+
502
+ result["allValid"] = allValid
503
+ result["invalidFields"] = invalidFields
504
+
505
+ return result
506
+ }
507
+
508
+ /**
509
+ * Calculate MRZ checksum using 7-3-1 weighted algorithm per ICAO 9303
510
+ *
511
+ * ICAO 9303 Part 3, Section 4.9 - Check Digit Calculation:
512
+ *
513
+ * Algorithm:
514
+ * 1. Assign numerical values to characters:
515
+ * - Digits 0-9: values 0-9
516
+ * - Letters A-Z: values 10-35 (A=10, B=11, ..., Z=35)
517
+ * - Filler character '<': value 0
518
+ *
519
+ * 2. Apply weights [7, 3, 1] cyclically to each character position
520
+ *
521
+ * 3. Calculate: sum = Σ(character_value × weight) for all positions
522
+ *
523
+ * 4. Check digit = sum mod 10
524
+ *
525
+ * Example: "AB2134<"
526
+ * A(10)×7 + B(11)×3 + 2(2)×1 + 1(1)×7 + 3(3)×3 + 4(4)×1 + <(0)×7
527
+ * = 70 + 33 + 2 + 7 + 9 + 4 + 0 = 125
528
+ * Check digit = 125 mod 10 = 5
529
+ *
530
+ * @param data The string to calculate checksum for
531
+ * @return The check digit as a character ('0'-'9')
532
+ * @see https://www2023.icao.int/publications/Documents/9303_p3_cons_en.pdf
533
+ */
534
+ private func calculateMRZChecksum(_ data: String) throws -> Character {
535
+ let weights = [7, 3, 1]
536
+ var sum = 0
537
+
538
+ for (index, char) in data.enumerated() {
539
+ let value: Int
540
+ switch char {
541
+ case "0"..."9":
542
+ value = Int(char.asciiValue! - Character("0").asciiValue!)
543
+ case "A"..."Z":
544
+ value = Int(char.asciiValue! - Character("A").asciiValue!) + 10
545
+ case "<":
546
+ value = 0
547
+ default:
548
+ value = 0
549
+ }
550
+ sum += value * weights[index % 3]
551
+ }
552
+
553
+ let checksumDigit = sum % 10
554
+ return Character(String(checksumDigit))
555
+ }
556
+
557
+ /**
558
+ * Fix common OCR errors in MRZ text with mrz-fast inspired corrections
559
+ */
560
+ private func fixMRZ(_ rawText: String) -> String {
561
+ var fixed = rawText
562
+ .replacingOccurrences(of: " ", with: "")
563
+ .replacingOccurrences(of: "«", with: "")
564
+
565
+ // Remove invalid patterns
566
+ if let regex = try? NSRegularExpression(pattern: "<K+|r+K+|<r+K+", options: []) {
567
+ fixed = regex.stringByReplacingMatches(in: fixed, options: [], range: NSRange(fixed.startIndex..., in: fixed), withTemplate: "")
568
+ }
569
+
570
+ // Fix common O/0 confusion in Turkish ID document numbers
571
+ if let regex = try? NSRegularExpression(pattern: "\\bI<TUR([A-Z0-9]{3})0([A-Z0-9]{6})\\b", options: []) {
572
+ fixed = regex.stringByReplacingMatches(in: fixed, options: [], range: NSRange(fixed.startIndex..., in: fixed), withTemplate: "I<TUR$1O$2")
573
+ }
574
+
575
+ let lines = fixed.components(separatedBy: "\n")
576
+ .map { $0.trimmingCharacters(in: .whitespaces) }
577
+ .filter { $0.contains("<") }
578
+
579
+ // Detect format and pad lines
580
+ let targetLength: Int
581
+ if lines.allSatisfy({ $0.count <= 30 }) {
582
+ targetLength = 30 // TD1
583
+ } else if lines.allSatisfy({ $0.count <= 36 }) {
584
+ targetLength = 36 // TD2
585
+ } else if lines.allSatisfy({ $0.count <= 44 }) {
586
+ targetLength = 44 // TD3
587
+ } else {
588
+ return rawText // Cannot detect format
589
+ }
590
+
591
+ let paddedLines = lines.map { line in
592
+ line.count < targetLength ? line.padding(toLength: targetLength, withPad: "<", startingAt: 0) : line
593
+ }
594
+
595
+ return applyOCRCorrections(paddedLines.joined(separator: "\n"))
596
+ }
597
+
598
+ /**
599
+ * Apply position-aware OCR corrections inspired by mrz-fast
600
+ * Character confusion mapping: O↔0, I↔1↔l, S↔5, B↔8, Z↔2, G↔6
601
+ */
602
+ private func applyOCRCorrections(_ mrzText: String) -> String {
603
+ var lines = mrzText.components(separatedBy: "\n")
604
+ if lines.isEmpty { return mrzText }
605
+
606
+ let format = detectMRZFormat(lines)
607
+ if format == .UNKNOWN { return mrzText }
608
+
609
+ switch format {
610
+ case .TD1:
611
+ // Line 2: positions 0-5 (birth date), 8-13 (expiry date), 6,14,29 (check digits)
612
+ lines[1] = correctDigitPositions(lines[1], ranges: [
613
+ 0...5, 8...13 // Date fields need digits
614
+ ])
615
+ lines[1] = correctCheckDigitPositions(lines[1], positions: [6, 14, 29])
616
+ // Nationality (15-17) needs letters
617
+ lines[1] = correctLetterPositions(lines[1], ranges: [15...17])
618
+
619
+ case .TD2:
620
+ // Line 2: positions 13-18 (birth), 21-26 (expiry), 9,19,27,35 (checks)
621
+ lines[1] = correctDigitPositions(lines[1], ranges: [
622
+ 13...18, 21...26
623
+ ])
624
+ lines[1] = correctCheckDigitPositions(lines[1], positions: [9, 19, 27, 35])
625
+ // Nationality (10-12) needs letters
626
+ lines[1] = correctLetterPositions(lines[1], ranges: [10...12])
627
+
628
+ case .TD3:
629
+ // Line 2: positions 13-18 (birth), 21-26 (expiry), 9,19,27,42 (checks)
630
+ lines[1] = correctDigitPositions(lines[1], ranges: [
631
+ 13...18, 21...26
632
+ ])
633
+ lines[1] = correctCheckDigitPositions(lines[1], positions: [9, 19, 27, 42])
634
+ // Nationality (10-12) needs letters
635
+ lines[1] = correctLetterPositions(lines[1], ranges: [10...12])
636
+
637
+ case .UNKNOWN:
638
+ break
639
+ }
640
+
641
+ return lines.joined(separator: "\n")
642
+ }
643
+
644
+ /**
645
+ * Correct positions that should contain digits (0-9)
646
+ */
647
+ private func correctDigitPositions(_ line: String, ranges: [ClosedRange<Int>]) -> String {
648
+ var corrected = Array(line)
649
+ ranges.forEach { range in
650
+ range.forEach { pos in
651
+ if pos < corrected.count {
652
+ let char = corrected[pos]
653
+ let replacement: Character
654
+ switch char {
655
+ case "O", "o": replacement = "0"
656
+ case "I", "l": replacement = "1"
657
+ case "Z": replacement = "2"
658
+ case "S": replacement = "5"
659
+ case "G": replacement = "6"
660
+ case "B": replacement = "8"
661
+ case "D", "Q": replacement = "0"
662
+ default: replacement = char
663
+ }
664
+ corrected[pos] = replacement
665
+ }
666
+ }
667
+ }
668
+ return String(corrected)
669
+ }
670
+
671
+ /**
672
+ * Correct positions that should contain letters (A-Z) or <
673
+ */
674
+ private func correctLetterPositions(_ line: String, ranges: [ClosedRange<Int>]) -> String {
675
+ var corrected = Array(line)
676
+ ranges.forEach { range in
677
+ range.forEach { pos in
678
+ if pos < corrected.count {
679
+ let char = corrected[pos]
680
+ let replacement: Character
681
+ switch char {
682
+ case "0": replacement = "O"
683
+ case "1": replacement = "I"
684
+ case "5": replacement = "S"
685
+ case "8": replacement = "B"
686
+ case "2": replacement = "Z"
687
+ case "6": replacement = "G"
688
+ default: replacement = char
689
+ }
690
+ corrected[pos] = replacement
691
+ }
692
+ }
693
+ }
694
+ return String(corrected)
695
+ }
696
+
697
+ /**
698
+ * Correct check digit positions (should be 0-9 only)
699
+ */
700
+ private func correctCheckDigitPositions(_ line: String, positions: [Int]) -> String {
701
+ var corrected = Array(line)
702
+ positions.forEach { pos in
703
+ if pos < corrected.count {
704
+ let char = corrected[pos]
705
+ let replacement: Character
706
+ switch char {
707
+ case "O", "o": replacement = "0"
708
+ case "I", "l": replacement = "1"
709
+ case "Z": replacement = "2"
710
+ case "S": replacement = "5"
711
+ case "G": replacement = "6"
712
+ case "B": replacement = "8"
713
+ default: replacement = char
714
+ }
715
+ corrected[pos] = replacement
716
+ }
717
+ }
718
+ return String(corrected)
719
+ }
720
+
721
+ /**
722
+ * Generate all possible O/0 permutations for MRZ text
723
+ * This handles the most common OCR confusion between O and 0
724
+ *
725
+ * @param mrzText Original MRZ text
726
+ * @param maxPermutations Maximum number of permutations to generate (default: 100)
727
+ * @return Array of all possible O/0 permutations
728
+ */
729
+ private func generateO0Permutations(_ mrzText: String, maxPermutations: Int = 100) -> [String] {
730
+ var permutations = Set<String>([mrzText])
731
+
732
+ // Find all positions with O or 0
733
+ let ambiguousPositions = mrzText.enumerated().compactMap { (index, char) in
734
+ (char == "O" || char == "o" || char == "0") ? index : nil
735
+ }
736
+
737
+ // Limit positions to avoid combinatorial explosion
738
+ let positions = Array(ambiguousPositions.prefix(min(7, ambiguousPositions.count)))
739
+
740
+ // Generate all combinations (2^n where n = number of positions)
741
+ let numCombinations = 1 << positions.count // 2^n
742
+ let limit = min(numCombinations, maxPermutations)
743
+
744
+ for i in 0..<limit {
745
+ var variant = Array(mrzText)
746
+
747
+ // For each bit in the combination number, decide O or 0
748
+ for (j, pos) in positions.enumerated() {
749
+ let useZero = (i & (1 << j)) != 0
750
+ let currentChar = variant[pos]
751
+
752
+ // Replace with appropriate character
753
+ if useZero && currentChar != "0" {
754
+ variant[pos] = "0"
755
+ } else if !useZero && currentChar != "O" {
756
+ variant[pos] = "O"
757
+ }
758
+ }
759
+
760
+ permutations.insert(String(variant))
761
+ }
762
+
763
+ return Array(permutations)
764
+ }
765
+
766
+ /**
767
+ * Generate I/1 permutations for additional OCR confusion cases
768
+ */
769
+ private func generateI1Permutations(_ mrzText: String, maxPermutations: Int = 50) -> [String] {
770
+ var permutations = Set<String>([mrzText])
771
+
772
+ // Find all positions with I or 1
773
+ let ambiguousPositions = mrzText.enumerated().compactMap { (index, char) in
774
+ (char == "I" || char == "i" || char == "l" || char == "1") ? index : nil
775
+ }
776
+
777
+ // Limit positions to avoid too many permutations
778
+ let positions = Array(ambiguousPositions.prefix(min(6, ambiguousPositions.count)))
779
+
780
+ let numCombinations = 1 << positions.count
781
+ let limit = min(numCombinations, maxPermutations)
782
+
783
+ for i in 0..<limit {
784
+ var variant = Array(mrzText)
785
+
786
+ for (j, pos) in positions.enumerated() {
787
+ let useOne = (i & (1 << j)) != 0
788
+ let currentChar = variant[pos]
789
+
790
+ if useOne && currentChar != "1" {
791
+ variant[pos] = "1"
792
+ } else if !useOne && (currentChar == "1" || currentChar == "l" || currentChar == "i") {
793
+ variant[pos] = "I"
794
+ }
795
+ }
796
+
797
+ permutations.insert(String(variant))
798
+ }
799
+
800
+ return Array(permutations)
801
+ }
802
+ }