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