@trustchex/react-native-sdk 1.362.6 → 1.381.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 (58) hide show
  1. package/TrustchexSDK.podspec +3 -3
  2. package/android/build.gradle +3 -3
  3. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +64 -19
  4. package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +636 -301
  5. package/ios/Camera/TrustchexCameraView.swift +166 -119
  6. package/ios/OpenCV/OpenCVHelper.h +0 -7
  7. package/ios/OpenCV/OpenCVHelper.mm +0 -60
  8. package/ios/OpenCV/OpenCVModule.h +0 -4
  9. package/ios/OpenCV/OpenCVModule.mm +440 -358
  10. package/lib/module/Shared/Components/DebugOverlay.js +541 -0
  11. package/lib/module/Shared/Components/FaceCamera.js +1 -0
  12. package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +44 -0
  13. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +270 -0
  14. package/lib/module/Shared/Components/IdentityDocumentCamera.js +708 -1593
  15. package/lib/module/Shared/Components/IdentityDocumentCamera.types.js +3 -0
  16. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +273 -0
  17. package/lib/module/Shared/Components/QrCodeScannerCamera.js +1 -8
  18. package/lib/module/Shared/Libs/mrz.utils.js +202 -9
  19. package/lib/module/Translation/Resources/en.js +0 -4
  20. package/lib/module/Translation/Resources/tr.js +0 -4
  21. package/lib/module/version.js +1 -1
  22. package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts +30 -0
  23. package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts.map +1 -0
  24. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  25. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +35 -0
  26. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -0
  27. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -56
  28. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  29. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +88 -0
  30. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -0
  31. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +116 -0
  32. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -0
  33. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +93 -0
  34. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -0
  35. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  36. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +1 -0
  37. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
  38. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +8 -0
  39. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  40. package/lib/typescript/src/Translation/Resources/en.d.ts +0 -4
  41. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  42. package/lib/typescript/src/Translation/Resources/tr.d.ts +0 -4
  43. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  44. package/lib/typescript/src/version.d.ts +1 -1
  45. package/package.json +1 -1
  46. package/src/Shared/Components/DebugOverlay.tsx +656 -0
  47. package/src/Shared/Components/FaceCamera.tsx +1 -0
  48. package/src/Shared/Components/IdentityDocumentCamera.constants.ts +44 -0
  49. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +342 -0
  50. package/src/Shared/Components/IdentityDocumentCamera.tsx +1105 -2324
  51. package/src/Shared/Components/IdentityDocumentCamera.types.ts +136 -0
  52. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +364 -0
  53. package/src/Shared/Components/QrCodeScannerCamera.tsx +1 -9
  54. package/src/Shared/Components/TrustchexCamera.tsx +1 -0
  55. package/src/Shared/Libs/mrz.utils.ts +238 -26
  56. package/src/Translation/Resources/en.ts +0 -4
  57. package/src/Translation/Resources/tr.ts +0 -4
  58. package/src/version.ts +1 -1
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export {};
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+
3
+ import { Dimensions } from 'react-native';
4
+ import { SIGNATURE_REGEX, PASSPORT_MRZ_PATTERN } from "./IdentityDocumentCamera.constants.js";
5
+ import { debugLog, isDebugEnabled } from "../Libs/debug.utils.js";
6
+
7
+ /**
8
+ * Frame-to-screen coordinate transform using FILL_CENTER scaling
9
+ */
10
+ export function getFrameToScreenTransform(frameWidth, frameHeight) {
11
+ const screen = Dimensions.get('window');
12
+ const frameAspect = frameWidth / frameHeight;
13
+ const screenAspect = screen.width / screen.height;
14
+ let scale;
15
+ let offsetX = 0;
16
+ let offsetY = 0;
17
+ if (frameAspect > screenAspect) {
18
+ scale = screen.height / frameHeight;
19
+ offsetX = (frameWidth * scale - screen.width) / 2;
20
+ } else {
21
+ scale = screen.width / frameWidth;
22
+ offsetY = (frameHeight * scale - screen.height) / 2;
23
+ }
24
+ return {
25
+ scale,
26
+ offsetX,
27
+ offsetY,
28
+ screen
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Compute scan area bounds in frame coordinates
34
+ */
35
+ export function getScanAreaBounds(frameWidth, frameHeight) {
36
+ const {
37
+ scale,
38
+ offsetX,
39
+ offsetY,
40
+ screen
41
+ } = getFrameToScreenTransform(frameWidth, frameHeight);
42
+ const scanLeft = (screen.width * 0.05 + offsetX) / scale;
43
+ const scanTop = (screen.height * 0.36 + offsetY) / scale;
44
+ const scanRight = (screen.width * 0.95 + offsetX) / scale;
45
+ const scanBottom = (screen.height * 0.64 + offsetY) / scale;
46
+ const isInsideScan = (x, y, w, h) => x >= scanLeft && y >= scanTop && x + w <= scanRight && y + h <= scanBottom;
47
+ return {
48
+ scanLeft,
49
+ scanTop,
50
+ scanRight,
51
+ scanBottom,
52
+ isInsideScan
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Transform frame-space bounds to screen-space bounds
58
+ */
59
+ export function transformBoundsToScreen(bounds, scale, offsetX, offsetY) {
60
+ return {
61
+ x: bounds.x * scale - offsetX,
62
+ y: bounds.y * scale - offsetY,
63
+ width: bounds.width * scale,
64
+ height: bounds.height * scale
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Unified status message logic used by both voice guidance and render text.
70
+ * Returns the appropriate i18n key arguments for the current scan state.
71
+ */
72
+ export function getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t) {
73
+ if (nextStep === 'COMPLETED') {
74
+ return t('identityDocumentCamera.scanCompleted');
75
+ }
76
+ if (status === 'INCORRECT') {
77
+ if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
78
+ return t('identityDocumentCamera.alignIDFront');
79
+ }
80
+ if (nextStep === 'SCAN_ID_BACK') {
81
+ return t('identityDocumentCamera.alignIDBack');
82
+ }
83
+ if (nextStep === 'SCAN_HOLOGRAM') {
84
+ return t('identityDocumentCamera.alignIDFront');
85
+ }
86
+ return t('identityDocumentCamera.alignPhotoSide');
87
+ }
88
+ if (isBrightnessLow) {
89
+ return t('identityDocumentCamera.lowBrightness');
90
+ }
91
+ if (isFrameBlurry) {
92
+ return t('identityDocumentCamera.avoidBlur');
93
+ }
94
+ if (status === 'SCANNING' && allElementsDetected && elementsOutsideScanArea.length === 0) {
95
+ if (nextStep === 'SCAN_ID_BACK') {
96
+ return t('identityDocumentCamera.idCardBackDetected');
97
+ }
98
+ if (detectedDocumentType === 'PASSPORT') {
99
+ return t('identityDocumentCamera.passportDetected');
100
+ }
101
+ if (detectedDocumentType === 'ID_FRONT') {
102
+ return t('identityDocumentCamera.idCardFrontDetected');
103
+ }
104
+ if (nextStep === 'SCAN_HOLOGRAM') {
105
+ return t('identityDocumentCamera.alignHologram');
106
+ }
107
+ return t('identityDocumentCamera.readingDocument');
108
+ }
109
+ if (elementsOutsideScanArea.length > 0) {
110
+ return t('identityDocumentCamera.centerDocument');
111
+ }
112
+ if ((status === 'SCANNING' || status === 'SEARCHING') && !allElementsDetected) {
113
+ if (nextStep === 'SCAN_ID_BACK') {
114
+ return t('identityDocumentCamera.alignIDBack');
115
+ }
116
+ if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
117
+ if (detectedDocumentType === 'PASSPORT') {
118
+ return t('identityDocumentCamera.alignPassport');
119
+ }
120
+ if (detectedDocumentType === 'ID_FRONT') {
121
+ return t('identityDocumentCamera.alignIDFront');
122
+ }
123
+ return t('identityDocumentCamera.alignPhotoSide');
124
+ }
125
+ if (nextStep === 'SCAN_HOLOGRAM') {
126
+ return t('identityDocumentCamera.alignHologram');
127
+ }
128
+ return t('identityDocumentCamera.readingDocument');
129
+ }
130
+
131
+ // Default fallback
132
+ if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
133
+ return status === 'SCANNING' ? t('identityDocumentCamera.readingDocument') : t('identityDocumentCamera.alignPhotoSide');
134
+ }
135
+ if (nextStep === 'SCAN_HOLOGRAM') {
136
+ return t('identityDocumentCamera.alignHologram');
137
+ }
138
+ if (nextStep === 'SCAN_ID_BACK') {
139
+ return status === 'SCANNING' ? t('identityDocumentCamera.readingDocument') : t('identityDocumentCamera.alignIDBackSide');
140
+ }
141
+ return '';
142
+ }
143
+
144
+ /**
145
+ * Calculate angle from two points in degrees
146
+ */
147
+ export function angleBetweenPoints(p1, p2) {
148
+ const dx = p2.x - p1.x;
149
+ const dy = p2.y - p1.y;
150
+ return Math.atan2(dy, dx) * (180 / Math.PI);
151
+ }
152
+
153
+ /**
154
+ * Calculate distance between two points
155
+ */
156
+ export function distanceBetweenPoints(p1, p2) {
157
+ const dx = p2.x - p1.x;
158
+ const dy = p2.y - p1.y;
159
+ return Math.sqrt(dx * dx + dy * dy);
160
+ }
161
+
162
+ /**
163
+ * Detect document type based on faces, OCR text, and MRZ fields
164
+ */
165
+ export function detectDocumentType(faces, ocrText, mrzFields, frameWidth, mrzText) {
166
+ // Relaxed signature detection: matches signature/imza variants and OCR errors
167
+ const hasSignatureMatch = SIGNATURE_REGEX.test(ocrText);
168
+ if (isDebugEnabled()) {
169
+ debugLog('IdentityDocumentCamera.utils', '[DocType] Detection', {
170
+ faces: faces.length,
171
+ hasMRZ: !!mrzFields,
172
+ hasMRZText: !!mrzText,
173
+ textLength: ocrText?.length,
174
+ hasSignature: hasSignatureMatch
175
+ });
176
+ }
177
+
178
+ // ID Back: no face + ID MRZ
179
+ if (faces.length === 0 && mrzFields?.documentCode === 'I') {
180
+ return 'ID_BACK';
181
+ }
182
+
183
+ // Passport: face + passport MRZ
184
+ if (faces.length > 0 && mrzFields?.documentCode === 'P') {
185
+ return 'PASSPORT';
186
+ }
187
+
188
+ // Check for PASSPORT MRZ pattern BEFORE classifying as ID_FRONT
189
+ // Both passports and ID cards have face + signature, so we MUST check MRZ first
190
+ // Check BOTH parsed mrzText AND raw ocrText — MRZ parsing can fail while
191
+ // the raw OCR still contains P<TUR/P<USA pattern
192
+ const hasPassportMRZPattern = mrzText && mrzText.length > 20 && PASSPORT_MRZ_PATTERN.test(mrzText) || PASSPORT_MRZ_PATTERN.test(ocrText);
193
+ if (hasPassportMRZPattern) {
194
+ // Passport MRZ pattern detected (P<TUR, P<USA, etc.)
195
+ // Even if not fully parsed, this is definitely a passport, not ID card
196
+ return 'PASSPORT';
197
+ }
198
+
199
+ // ID Front: face detected
200
+ // CRITICAL: Only classify as ID_FRONT when we're confident it's NOT a passport
201
+ // This means we must have either:
202
+ // 1. MRZ code 'I' (definitive ID card), OR
203
+ // 2. Face + signature but NO passport MRZ pattern visible
204
+ if (faces.length > 0 && ocrText?.length >= 5) {
205
+ // Filter to card-sized faces only (min 5% of frame width to exclude tiny background faces)
206
+ const cardSizedFaces = frameWidth ? faces.filter(face => face.bounds.width >= frameWidth * 0.05 && face.bounds.height >= frameWidth * 0.05) : faces;
207
+ if (cardSizedFaces.length > 0) {
208
+ // If we have MRZ code 'I', it's definitely an ID card
209
+ if (mrzFields?.documentCode === 'I') {
210
+ return 'ID_FRONT';
211
+ }
212
+
213
+ // If signature present and NO passport MRZ pattern, likely ID_FRONT
214
+ // But we can't be 100% sure yet - passport MRZ might appear in next frames
215
+ const hasSignature = hasSignatureMatch;
216
+ if (hasSignature && !hasPassportMRZPattern) {
217
+ return 'ID_FRONT';
218
+ }
219
+ }
220
+ }
221
+ return 'UNKNOWN';
222
+ }
223
+
224
+ /**
225
+ * Determine the document type to set based on current frame analysis
226
+ * Handles correction of misdetections and passport pattern checking
227
+ */
228
+ export function determineDocumentTypeToSet(documentType, cardSizedFaces, parsedMRZFields, mrzText) {
229
+ // CRITICAL: Passport MRZ always takes precedence over other detections
230
+ const hasPassportMRZ = mrzText && mrzText.length > 20 && PASSPORT_MRZ_PATTERN.test(mrzText) || parsedMRZFields?.documentCode === 'P';
231
+ if (hasPassportMRZ) {
232
+ return 'PASSPORT';
233
+ }
234
+
235
+ // After passport MRZ check, accept the detected type directly
236
+ if (documentType === 'PASSPORT') return 'PASSPORT';
237
+ if (documentType === 'ID_FRONT') return 'ID_FRONT';
238
+ return 'UNKNOWN';
239
+ }
240
+
241
+ /**
242
+ * Compare MRZ field values (ignore raw text variations)
243
+ */
244
+ export function areMRZFieldsEqual(fields1, fields2) {
245
+ if (!fields1 || !fields2) return false;
246
+ // Compare critical fields that define document identity
247
+ return fields1.documentNumber === fields2.documentNumber && fields1.birthDate === fields2.birthDate && fields1.expirationDate === fields2.expirationDate && fields1.firstName === fields2.firstName && fields1.lastName === fields2.lastName && fields1.issuingState === fields2.issuingState;
248
+ }
249
+
250
+ /**
251
+ * Check if all required MRZ fields are present
252
+ */
253
+ export function hasRequiredMRZFields(fields) {
254
+ return !!fields?.firstName && !!fields?.lastName && !!fields?.documentNumber && !!fields?.birthDate;
255
+ }
256
+
257
+ /**
258
+ * Validate if face position has changed within acceptable tolerance
259
+ * Returns true if position is valid (within tolerance)
260
+ */
261
+ export function validateFacePosition(currentBounds, referenceBounds, isHologramStep) {
262
+ const xDiff = Math.abs(currentBounds.x - referenceBounds.x);
263
+ const yDiff = Math.abs(currentBounds.y - referenceBounds.y);
264
+ const widthDiff = Math.abs(currentBounds.width - referenceBounds.width);
265
+ const heightDiff = Math.abs(currentBounds.height - referenceBounds.height);
266
+
267
+ // Use looser tolerance during hologram step since flash toggling causes position jitter
268
+ const tolerance = isHologramStep ? 0.5 : 0.2;
269
+ const xTolerance = referenceBounds.width * tolerance;
270
+ const yTolerance = referenceBounds.height * tolerance;
271
+ const sizeTolerance = referenceBounds.width * tolerance;
272
+ return xDiff <= xTolerance && yDiff <= yTolerance && widthDiff <= sizeTolerance && heightDiff <= sizeTolerance;
273
+ }
@@ -7,7 +7,6 @@ import { useKeepAwake } from "../Libs/native-keep-awake.utils.js";
7
7
  import { useIsFocused } from '@react-navigation/native';
8
8
  import { useTranslation } from 'react-i18next';
9
9
  import { debugLog, logError } from "../Libs/debug.utils.js";
10
- import LottieView from 'lottie-react-native';
11
10
  import StyledButton from "./StyledButton.js";
12
11
  import { useTheme } from "../Contexts/ThemeContext.js";
13
12
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -148,13 +147,7 @@ const QrCodeScannerCamera = ({
148
147
  }), /*#__PURE__*/_jsx(View, {
149
148
  style: styles.bottomZone
150
149
  }), /*#__PURE__*/_jsx(View, {
151
- style: styles.scanArea,
152
- children: /*#__PURE__*/_jsx(LottieView, {
153
- source: require('../../Shared/Animations/scanning.json'),
154
- style: styles.animation,
155
- loop: true,
156
- autoPlay: true
157
- })
150
+ style: styles.scanArea
158
151
  }), onClose && /*#__PURE__*/_jsx(TouchableOpacity, {
159
152
  onPress: onClose,
160
153
  style: [styles.backButton, {
@@ -22,6 +22,9 @@ const fixMRZ = rawText => {
22
22
 
23
23
  // Ensure uppercase immediately
24
24
  rawText = rawText.toUpperCase();
25
+
26
+ // Apply OCR-B specific corrections early
27
+ rawText = applyOCRBCorrections(rawText);
25
28
  const fillerChar = '<';
26
29
  const mrzFormats = [{
27
30
  lines: 3,
@@ -47,6 +50,15 @@ const fixMRZ = rawText => {
47
50
  cleanedText = cleanedText.replace(/^1</gm, 'I<');
48
51
  cleanedText = cleanedText.replace(/\n1</g, '\nI<');
49
52
 
53
+ // Fix lowercase letters that should be uppercase (common OCR error)
54
+ cleanedText = cleanedText.split('\n').map(line => {
55
+ // Only uppercase if line contains fillers (indicates MRZ line)
56
+ if (line.includes('<')) {
57
+ return line.toUpperCase();
58
+ }
59
+ return line;
60
+ }).join('\n');
61
+
50
62
  // Conservative OCR error corrections - only fix clear filler character errors
51
63
  // Don't touch valid digit sequences!
52
64
 
@@ -72,6 +84,24 @@ const fixMRZ = rawText => {
72
84
  return fillers.padEnd(match.length, '<');
73
85
  });
74
86
 
87
+ // Pattern 5: Fix common OCR misreadings in filler areas
88
+ // Convert non-alphanumeric chars in filler areas to '<' (but not digits/letters in content areas)
89
+ cleanedText = cleanedText.split('\n').map(line => {
90
+ // If line has fillers, likely MRZ - safe to convert uncommon chars near fillers
91
+ if (!line.includes('<')) return line;
92
+
93
+ // Replace '.' ',' '-' with fillers in MRZ lines (OCR noise)
94
+ line = line.replace(/[.,\-~`^]/g, '<');
95
+
96
+ // Replace lowercase o/i/l with uppercase or filler depending on context
97
+ line = line.replace(/o(?=[A-Z<]|[A-Z<]o)/g, 'O'); // lowercase o surrounded by uppercase
98
+ line = line.replace(/i(?=[A-Z<]|[A-Z<]i)/g, 'I'); // lowercase i surrounded by uppercase
99
+ line = line.replace(/l(?=[A-Z<0-9])/g, 'L'); // lowercase l before uppercase or digit
100
+ line = line.replace(/l$/g, '<'); // lowercase l at line end is filler
101
+
102
+ return line;
103
+ }).join('\n');
104
+
75
105
  // Split into lines and filter for MRZ-like content
76
106
  // MRZ lines must start with I< or contain long sequences of < and alphanumeric chars
77
107
  const lines = cleanedText.split('\n').map(line => line.trim()).filter(line => {
@@ -134,20 +164,118 @@ const fixMRZ = rawText => {
134
164
  };
135
165
  const AMBIGUOUS_CHAR_MAP = {
136
166
  '0': ['O', 'Q', 'D'],
137
- O: ['0', 'Q', 'D'],
138
- Q: ['0', 'O', 'D'],
139
- D: ['0', 'O', 'Q'],
167
+ 'O': ['0', 'Q', 'D'],
168
+ 'Q': ['0', 'O', 'D'],
169
+ 'D': ['0', 'O', 'Q'],
140
170
  '1': ['I', 'L'],
141
- I: ['1', 'L'],
142
- L: ['1', 'I'],
171
+ 'I': ['1', 'L'],
172
+ 'L': ['1', 'I'],
143
173
  '2': ['Z'],
144
- Z: ['2'],
174
+ 'Z': ['2'],
145
175
  '5': ['S'],
146
- S: ['5'],
176
+ 'S': ['5'],
147
177
  '6': ['G'],
148
- G: ['6'],
178
+ 'G': ['6'],
149
179
  '8': ['B'],
150
- B: ['8']
180
+ 'B': ['8'],
181
+ '9': ['g'],
182
+ 'g': ['9'],
183
+ '4': ['A'],
184
+ 'A': ['4']
185
+ };
186
+
187
+ /**
188
+ * OCR-B specific character corrections (used for MRZ on passports/IDs)
189
+ * OCR-B is monospaced and has specific glyph patterns that differ from other fonts
190
+ * Common OCR-B misreadings in document scanning context
191
+ */
192
+ const OCRB_SPECIFIC_MAP = {
193
+ // Glyph confusion in OCR-B (low contrast scanning)
194
+ Ø: '0',
195
+ // Slashed zero sometimes appears as capital O with slash
196
+ ø: '0',
197
+ // Lowercase variant
198
+ œ: 'O',
199
+ // Ligature
200
+
201
+ // Common in monospace OCR
202
+ Ι: 'I',
203
+ // Greek capital iota confused with I
204
+ ι: 'i',
205
+ // Greek lowercase iota
206
+ l: 'I' // Lowercase L as I (common with serif OCR-B variants)
207
+ };
208
+
209
+ /**
210
+ * Validates OCR-B specific character patterns
211
+ * @param text Text to validate
212
+ * @returns True if text matches OCR-B patterns reasonably well
213
+ */
214
+ const isValidOCRBPattern = text => {
215
+ const lines = text.split('\n').filter(l => l.trim());
216
+
217
+ // OCR-B MRZ validation
218
+ for (const line of lines) {
219
+ if (!/<+/.test(line)) continue; // Skip non-MRZ lines
220
+
221
+ // OCR-B characteristics check:
222
+ // 1. Monospaced - all lines should have similar length (±2 chars)
223
+ // 2. Specific filler pattern - at least 3 consecutive fillers
224
+ // 3. Mixed digits and letters - checkdigits, names, dates
225
+
226
+ const fillerGroups = (line.match(/<+/g) || []).length;
227
+ const letterDigitSegments = (line.match(/[A-Z0-9]+/g) || []).length;
228
+
229
+ // Valid OCR-B should have 2-4 filler groups and 2-4 alphanumeric segments
230
+ if (fillerGroups < 1 || letterDigitSegments < 1) return false;
231
+ if (fillerGroups > 6 || letterDigitSegments > 6) return false; // Too fragmented
232
+ }
233
+ return true;
234
+ };
235
+
236
+ /**
237
+ * Applies OCR-B specific corrections to raw OCR text
238
+ * @param text Raw OCR text
239
+ * @returns Text with OCR-B corrections applied
240
+ */
241
+ const applyOCRBCorrections = text => {
242
+ let corrected = text;
243
+
244
+ // Replace OCR-B specific misreadings
245
+ for (const [wrong, correct] of Object.entries(OCRB_SPECIFIC_MAP)) {
246
+ corrected = corrected.replace(new RegExp(wrong, 'g'), correct);
247
+ }
248
+
249
+ // Apply line-by-line OCR-B specific logic
250
+ corrected = corrected.split('\n').map(line => {
251
+ if (!line.includes('<')) return line; // Not an MRZ line
252
+
253
+ // In OCR-B MRZ, digit positions are strictly defined
254
+ // If we see obvious letter/digit confusion in digit areas, correct it
255
+
256
+ // Document number area: should be digits or fillers
257
+ // Pattern: after 2nd filler block, expect <8-9 digit chars> before next filler block
258
+ line = line.replace(/([<]+)([A-Z])([0-9]{1,2})([<]+)/g, (match, filler1, letter, digits, filler2) => {
259
+ // If single letter between fillers and digits, likely OCR error
260
+ if (letter === 'O') return `${filler1}0${digits}${filler2}`; // O -> 0
261
+ if (letter === 'I' || letter === 'L') return `${filler1}1${digits}${filler2}`; // I/L -> 1
262
+ return match;
263
+ });
264
+
265
+ // Check digit areas (end of lines): should be single digit, not letter
266
+ line = line.replace(/([<]+)([A-Z])(\d)?$/gm, (match, fillers, letter, maybeDigit) => {
267
+ // If a letter appears before line end in check digit area, convert
268
+ if (letter === 'O') return `${fillers}0${maybeDigit || ''}`;
269
+ if (letter === 'I' || letter === 'L') return `${fillers}1${maybeDigit || ''}`;
270
+ if (letter === 'S') return `${fillers}5${maybeDigit || ''}`;
271
+ if (letter === 'Z') return `${fillers}2${maybeDigit || ''}`;
272
+ if (letter === 'B') return `${fillers}8${maybeDigit || ''}`;
273
+ if (letter === 'G') return `${fillers}6${maybeDigit || ''}`;
274
+ return match;
275
+ });
276
+ return line;
277
+ }).join('\n');
278
+ return corrected;
151
279
  };
152
280
  const generateAmbiguousVariants = (text, maxChanges = 2, maxVariants = 64) => {
153
281
  const entries = [{
@@ -262,6 +390,67 @@ const validateMRZWithCorrections = mrzText => {
262
390
  return validateMRZ(mrzText, true);
263
391
  };
264
392
 
393
+ /**
394
+ * Calculates quality score for recognized MRZ text (0-100)
395
+ * Higher score = higher confidence in the OCR result
396
+ * @param mrzText Recognized MRZ text
397
+ * @returns Quality score 0-100
398
+ */
399
+ const calculateMRZQualityScore = mrzText => {
400
+ if (!mrzText) return 0;
401
+ let score = 100;
402
+ const lines = mrzText.split('\n').filter(l => l.trim());
403
+
404
+ // Penalty for incomplete lines
405
+ if (lines.length < 2) score -= 40;
406
+ if (lines.some(l => l.length < 30)) score -= 20;
407
+ for (const line of lines) {
408
+ // Count suspicious patterns that indicate OCR errors
409
+ const fillerCount = (line.match(/</g) || []).length;
410
+ const digitCount = (line.match(/\d/g) || []).length;
411
+ const letterCount = (line.match(/[A-Z]/g) || []).length;
412
+ const suspiciousCount = (line.match(/[KGB]{2,}|[0O]{2,}/g) || []).length;
413
+
414
+ // Penalize if too many suspicious patterns
415
+ if (suspiciousCount > 0) score -= suspiciousCount * 5;
416
+
417
+ // Penalize if ratio of fillers seems wrong (should be 40-60% for most lines)
418
+ const fillerRatio = fillerCount / line.length;
419
+ if (fillerRatio < 0.3 || fillerRatio > 0.7) score -= 10;
420
+
421
+ // Penalize if no digits where expected (birth date, expiry date, check digits)
422
+ if (letterCount > digitCount) {
423
+ // Some lines should have mostly digits (date lines)
424
+ if (line.match(/\d{5,6}/)) {
425
+ // Has valid date pattern, ok
426
+ } else if (line.length > 30 && digitCount === 0) {
427
+ score -= 15;
428
+ }
429
+ }
430
+ }
431
+
432
+ // Apply consistency bonus if lines have similar length
433
+ const lengths = lines.map(l => l.length);
434
+ const avgLength = lengths.reduce((a, b) => a + b, 0) / lines.length;
435
+ const variance = lengths.reduce((sum, len) => sum + Math.abs(len - avgLength), 0) / lines.length;
436
+ if (variance < 5) score += 10;
437
+ return Math.max(0, Math.min(100, score));
438
+ };
439
+
440
+ /**
441
+ * Validates MRZ quality and returns confidence level
442
+ * @param mrzText Raw or fixed MRZ text
443
+ * @returns Object with quality score and confidence level
444
+ */
445
+ const assessMRZQuality = mrzText => {
446
+ const score = calculateMRZQualityScore(mrzText);
447
+ return {
448
+ score,
449
+ confidence: score >= 80 ? 'high' : score >= 60 ? 'medium' : 'low',
450
+ reliable: score >= 70
451
+ };
452
+ };
453
+
265
454
  /**
266
455
  * Converts an MRZ date string to an ISO date string.
267
456
  * @param mrzDate The MRZ date string to convert (YYMMDD format)
@@ -282,5 +471,9 @@ export default {
282
471
  fixMRZ,
283
472
  validateMRZ,
284
473
  validateMRZWithCorrections,
474
+ calculateMRZQualityScore,
475
+ assessMRZQuality,
476
+ isValidOCRBPattern,
477
+ applyOCRBCorrections,
285
478
  convertMRZDateToISODate
286
479
  };
@@ -102,10 +102,6 @@ export default {
102
102
  'identityDocumentCamera.alignIDFrontSide': 'Align the front side of your ID card',
103
103
  'identityDocumentCamera.alignIDBackSide': 'Align the back side of your ID card',
104
104
  'identityDocumentCamera.scanCompleted': 'Scan completed!',
105
- 'identityDocumentCamera.frontSideScanned': 'Front side scanned!',
106
- 'identityDocumentCamera.passportScanned': 'Passport scanned!',
107
- 'identityDocumentCamera.backSideScanned': 'Back side scanned!',
108
- 'identityDocumentCamera.hologramVerified': 'Hologram verified!',
109
105
  'identityDocumentCamera.searchingDocument': 'Position document within the frame',
110
106
  'identityDocumentCamera.faceDetected': 'Keep device steady...',
111
107
  'identityDocumentCamera.readingDocument': 'Reading document...',
@@ -102,10 +102,6 @@ export default {
102
102
  'identityDocumentCamera.alignIDFrontSide': 'Kimlik kartınızın ön yüzünü hizalayın',
103
103
  'identityDocumentCamera.alignIDBackSide': 'Kimlik kartınızın arka yüzünü hizalayın',
104
104
  'identityDocumentCamera.scanCompleted': 'Tarama tamamlandı!',
105
- 'identityDocumentCamera.frontSideScanned': 'Ön yüz tarandı!',
106
- 'identityDocumentCamera.passportScanned': 'Pasaport tarandı!',
107
- 'identityDocumentCamera.backSideScanned': 'Arka yüz tarandı!',
108
- 'identityDocumentCamera.hologramVerified': 'Hologram doğrulandı!',
109
105
  'identityDocumentCamera.searchingDocument': 'Belgeyi çerçeve içine yerleştirin',
110
106
  'identityDocumentCamera.faceDetected': 'Cihazı sabit tutun...',
111
107
  'identityDocumentCamera.readingDocument': 'Belge okunuyor...',
@@ -2,4 +2,4 @@
2
2
 
3
3
  // This file is auto-generated. Do not edit manually.
4
4
  // Version is synced from package.json during build.
5
- export const SDK_VERSION = '1.362.6';
5
+ export const SDK_VERSION = '1.381.0';
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import type { ScanStep, ScanStatus, DocumentType, BoundsWithRotation, BoundsWithAngle } from './IdentityDocumentCamera.types';
3
+ export interface DebugOverlayProps {
4
+ nextStep: ScanStep;
5
+ status: ScanStatus;
6
+ detectedDocumentType: DocumentType;
7
+ isBrightnessLow: boolean;
8
+ isFrameBlurry: boolean;
9
+ isTorchOn: boolean;
10
+ documentPlaneBounds: BoundsWithRotation | null;
11
+ secondaryFaceBounds: BoundsWithRotation | null;
12
+ barcodeBounds: BoundsWithAngle | null;
13
+ mrzBounds: BoundsWithAngle | null;
14
+ signatureBounds: BoundsWithAngle | null;
15
+ currentFaceImage?: string;
16
+ currentSecondaryFaceImage?: string;
17
+ currentHologramImage?: string;
18
+ currentHologramMaskImage?: string;
19
+ latestHologramFaceImage?: string;
20
+ hologramImageCount: number;
21
+ allElementsDetected: boolean;
22
+ elementsOutsideScanArea: string[];
23
+ }
24
+ declare const DebugOverlay: React.NamedExoticComponent<DebugOverlayProps>;
25
+ export interface TestModePanelProps {
26
+ mrzText: string;
27
+ }
28
+ export declare const TestModePanel: React.NamedExoticComponent<TestModePanelProps>;
29
+ export default DebugOverlay;
30
+ //# sourceMappingURL=DebugOverlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DebugOverlay.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/DebugOverlay.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,eAAe,EAChB,MAAM,gCAAgC,CAAC;AA2PxC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB,oBAAoB,EAAE,YAAY,CAAC;IACnC,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,mBAAmB,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC/C,mBAAmB,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC/C,aAAa,EAAE,eAAe,GAAG,IAAI,CAAC;IACtC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,uBAAuB,EAAE,MAAM,EAAE,CAAC;CACnC;AAED,QAAA,MAAM,YAAY,+CAyThB,CAAC;AAIH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,aAAa,gDAiDxB,CAAC;AAEH,eAAe,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"FaceCamera.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/FaceCamera.tsx"],"names":[],"mappings":"AAeA,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,mBAAmB,CAAC;AAa3B,MAAM,MAAM,IAAI,GAAG;IACjB,MAAM,EAAE;QACN,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,eAAe,EAAE,CACf,KAAK,EAAE,IAAI,EAAE,EACb,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,KAChB,IAAI,CAAC;IACV,mBAAmB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC7D,WAAW,CAAC,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,UAAU,GAAI,wDAIjB,eAAe,4CAiTjB,CAAC;AA6BF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"FaceCamera.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/FaceCamera.tsx"],"names":[],"mappings":"AAeA,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,mBAAmB,CAAC;AAa3B,MAAM,MAAM,IAAI,GAAG;IACjB,MAAM,EAAE;QACN,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,eAAe,EAAE,CACf,KAAK,EAAE,IAAI,EAAE,EACb,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,KAChB,IAAI,CAAC;IACV,mBAAmB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC7D,WAAW,CAAC,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF,QAAA,MAAM,UAAU,GAAI,wDAIjB,eAAe,4CAkTjB,CAAC;AA6BF,eAAe,UAAU,CAAC"}
@@ -0,0 +1,35 @@
1
+ export declare const HOLOGRAM_IMAGE_COUNT = 16;
2
+ export declare const HOLOGRAM_DETECTION_THRESHOLD = 1000;
3
+ export declare const HOLOGRAM_DETECTION_RETRY_COUNT = 3;
4
+ export declare const HOLOGRAM_CAPTURE_INTERVAL = 250;
5
+ export declare const HOLOGRAM_MAX_FRAMES_WITHOUT_FACE = 30;
6
+ export declare const MIN_BRIGHTNESS_THRESHOLD = 45;
7
+ export declare const MAX_BRIGHTNESS_THRESHOLD = 210;
8
+ export declare const MAX_CONSECUTIVE_QUALITY_FAILURES = 30;
9
+ export declare const FACE_EDGE_MARGIN_PERCENT = 0.08;
10
+ export declare const MIN_CARD_FACE_SIZE_PERCENT = 0.05;
11
+ export declare const MIN_MLI_FACE_SIZE_PERCENT = 0.01;
12
+ export declare const REQUIRED_CONSISTENT_MRZ_READS = 2;
13
+ export declare const REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS = 3;
14
+ export declare const SIGNATURE_REGEX: RegExp;
15
+ export declare const SIGNATURE_TEXT_REGEX: RegExp;
16
+ export declare const MRZ_BLOCK_PATTERN: RegExp;
17
+ export declare const PASSPORT_MRZ_PATTERN: RegExp;
18
+ export declare const MIN_SECURITY_FEATURES_REQUIRED = 4;
19
+ export declare const SECURITY_FEATURE_NAMES: {
20
+ readonly icao_photo: "ICAO Uyumlu Fotoğraf (ICAO Compliant Photo)";
21
+ readonly barcode: "Barkod (Barcode)";
22
+ readonly hologram: "DOVID/OVD (Hologram)";
23
+ readonly wet_signature: "Islak İmza (Wet Signature)";
24
+ readonly guilloche: "Giyoş (Guilloche Pattern)";
25
+ readonly rainbow_print: "Gökkuşağı Baskı (Rainbow Print)";
26
+ readonly ovi: "Optik Değişken Mürekkep (OVI)";
27
+ readonly latent_image: "Gizli Görüntü (Latent Image)";
28
+ readonly microtext: "Mikro Yazı (Microtext)";
29
+ };
30
+ export declare const FACE_SHAPE_MATCH_THRESHOLD = 0.5;
31
+ export declare const FACE_VISUAL_SIMILARITY_THRESHOLD = 0.5;
32
+ export declare const FACE_ASPECT_RATIO_TOLERANCE = 0.15;
33
+ export declare const FACE_SIZE_RATIO_MIN = 0.7;
34
+ export declare const FACE_SIZE_RATIO_MAX = 1.3;
35
+ //# sourceMappingURL=IdentityDocumentCamera.constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IdentityDocumentCamera.constants.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAChD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AACnD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAC/C,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,sCAAsC,IAAI,CAAC;AAExD,eAAO,MAAM,eAAe,QAAkC,CAAC;AAC/D,eAAO,MAAM,oBAAoB,QAAyB,CAAC;AAC3D,eAAO,MAAM,iBAAiB,QAAsB,CAAC;AACrD,eAAO,MAAM,oBAAoB,QAAe,CAAC;AAIjD,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAChD,eAAO,MAAM,sBAAsB;;;;;;;;;;CAUzB,CAAC;AAKX,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAChD,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,mBAAmB,MAAM,CAAC"}