@trustchex/react-native-sdk 1.512.0 → 1.513.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/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +9 -2
  2. package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +54 -0
  3. package/ios/Camera/TrustchexCameraView.swift +6 -2
  4. package/ios/OpenCV/OpenCVModule.mm +59 -0
  5. package/lib/module/Screens/Dynamic/AddressCaptureScreen.js +427 -112
  6. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +9 -4
  7. package/lib/module/Screens/Static/ResultScreen.js +46 -0
  8. package/lib/module/Shared/Animations/address-scan.json +1 -0
  9. package/lib/module/Shared/Components/DebugNavigationPanel.js +10 -0
  10. package/lib/module/Shared/Components/EIDScanner.js +16 -4
  11. package/lib/module/Shared/Components/IdentityDocumentCamera.js +100 -19
  12. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +47 -0
  13. package/lib/module/Shared/Libs/addressProofHeuristic.js +130 -0
  14. package/lib/module/Shared/Libs/demo.utils.js +4 -0
  15. package/lib/module/Shared/Libs/sendDiagnosticReport.js +47 -1
  16. package/lib/module/Shared/Libs/vizName.js +138 -16
  17. package/lib/module/Translation/Resources/en.js +16 -3
  18. package/lib/module/Translation/Resources/tr.js +16 -3
  19. package/lib/module/version.js +1 -1
  20. package/lib/typescript/src/Screens/Dynamic/AddressCaptureScreen.d.ts.map +1 -1
  21. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  22. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  23. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  24. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts +8 -3
  25. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  26. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  27. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +25 -0
  28. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
  29. package/lib/typescript/src/Shared/Libs/addressProofHeuristic.d.ts +34 -0
  30. package/lib/typescript/src/Shared/Libs/addressProofHeuristic.d.ts.map +1 -0
  31. package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
  32. package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts.map +1 -1
  33. package/lib/typescript/src/Shared/Libs/vizName.d.ts +31 -6
  34. package/lib/typescript/src/Shared/Libs/vizName.d.ts.map +1 -1
  35. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +0 -5
  36. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  37. package/lib/typescript/src/Translation/Resources/en.d.ts +13 -0
  38. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  39. package/lib/typescript/src/Translation/Resources/tr.d.ts +13 -0
  40. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  41. package/lib/typescript/src/version.d.ts +1 -1
  42. package/package.json +1 -1
  43. package/src/Screens/Dynamic/AddressCaptureScreen.tsx +434 -75
  44. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +14 -4
  45. package/src/Screens/Static/ResultScreen.tsx +59 -0
  46. package/src/Shared/Animations/address-scan.json +1 -0
  47. package/src/Shared/Components/DebugNavigationPanel.tsx +13 -0
  48. package/src/Shared/Components/EIDScanner.tsx +38 -12
  49. package/src/Shared/Components/IdentityDocumentCamera.tsx +109 -17
  50. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +53 -0
  51. package/src/Shared/Libs/addressProofHeuristic.ts +197 -0
  52. package/src/Shared/Libs/demo.utils.ts +5 -0
  53. package/src/Shared/Libs/sendDiagnosticReport.ts +65 -7
  54. package/src/Shared/Libs/vizName.ts +158 -20
  55. package/src/Shared/Types/identificationInfo.ts +0 -5
  56. package/src/Translation/Resources/en.ts +28 -3
  57. package/src/Translation/Resources/tr.ts +28 -3
  58. package/src/version.ts +1 -1
@@ -807,6 +807,26 @@ const ResultScreen = () => {
807
807
  })]
808
808
  })
809
809
  })]
810
+ }), appContext.identificationInfo.address && (appContext.identificationInfo.address.addressText || appContext.identificationInfo.address.proofImage) && /*#__PURE__*/_jsxs(View, {
811
+ style: styles.addressSection,
812
+ children: [/*#__PURE__*/_jsx(Text, {
813
+ style: styles.mrzTextLabel,
814
+ children: t('addressCaptureScreen.addressLabel')
815
+ }), appContext.identificationInfo.address.addressText ? /*#__PURE__*/_jsx(Text, {
816
+ style: styles.addressText,
817
+ children: appContext.identificationInfo.address.addressText
818
+ }) : null, appContext.identificationInfo.address.proofImage ? /*#__PURE__*/_jsxs(_Fragment, {
819
+ children: [/*#__PURE__*/_jsx(Text, {
820
+ style: styles.addressProofLabel,
821
+ children: t('addressCaptureScreen.proofLabel')
822
+ }), /*#__PURE__*/_jsx(Image, {
823
+ style: styles.addressProofImage,
824
+ resizeMode: "contain",
825
+ source: {
826
+ uri: `data:image/jpeg;base64,${appContext.identificationInfo.address.proofImage}`
827
+ }
828
+ })]
829
+ }) : null]
810
830
  }), appContext.identificationInfo.livenessDetection?.videoPath && /*#__PURE__*/_jsxs(_Fragment, {
811
831
  children: [/*#__PURE__*/_jsx(Text, {
812
832
  style: styles.sectionHeader,
@@ -1090,6 +1110,32 @@ const styles = StyleSheet.create({
1090
1110
  lineHeight: 14,
1091
1111
  color: '#1F2937'
1092
1112
  },
1113
+ addressSection: {
1114
+ marginTop: 16,
1115
+ padding: 12,
1116
+ backgroundColor: '#F3F4F6',
1117
+ borderRadius: 8,
1118
+ borderWidth: 1,
1119
+ borderColor: '#D1D5DB'
1120
+ },
1121
+ addressText: {
1122
+ fontSize: 14,
1123
+ lineHeight: 20,
1124
+ color: '#1F2937'
1125
+ },
1126
+ addressProofLabel: {
1127
+ fontSize: 12,
1128
+ fontWeight: '600',
1129
+ color: '#6B7280',
1130
+ marginTop: 12,
1131
+ marginBottom: 8
1132
+ },
1133
+ addressProofImage: {
1134
+ width: '100%',
1135
+ // Portrait frame so the whole document shows upright, letterboxed not cropped.
1136
+ aspectRatio: 3 / 4,
1137
+ borderRadius: 8
1138
+ },
1093
1139
  trustchexLogo: {
1094
1140
  width: 150,
1095
1141
  height: 75,
@@ -0,0 +1 @@
1
+ {"nm":"Search","ddd":0,"h":500,"w":500,"meta":{"g":"@lottiefiles/toolkit-js 0.25.8"},"layers":[{"ty":4,"nm":"Check Middle","sr":1,"st":-60,"op":145,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.134,"y":1},"s":[100,100,100],"t":4},{"s":[69,69,100],"t":16}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[250,250,0],"t":2,"ti":[33,0,0],"to":[-33,0,0]},{"s":[52,250,0],"t":14}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":2},{"s":[0],"t":20}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.5,11.5],[-11,50.5],[60.5,-34.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":15,"ix":5},"c":{"a":0,"k":[0.0627,0.7255,0.5059],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Check Right","sr":1,"st":-2,"op":144,"ip":58,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[250,250,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.5,11.5],[-11,50.5],[60.5,-34.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":15,"ix":5},"c":{"a":0,"k":[0.0627,0.7255,0.5059],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":58},{"s":[100],"t":65}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":0,"k":0,"ix":1},"m":1}],"ind":2},{"ty":4,"nm":"Magnifying Glass","sr":1,"st":0,"op":144,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[86,86,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-31.335,-31.31,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-29.547],[29.547,0],[0,29.547],[-29.547,0]],"o":[[0,29.547],[-29.547,0],[0,-29.547],[29.547,0]],"v":[[53.5,0],[0,53.5],[-53.5,0],[0,-53.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":13,"ix":5},"c":{"a":0,"k":[0.0627,0.7255,0.5059],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[86,86],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3,"parent":4},{"ty":4,"nm":"Handle","sr":1,"st":0,"op":144,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-30.17,-31.17,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.394,"y":1},"s":[648.829,532.045,0],"t":18,"ti":[33.833,65.833,0],"to":[-71.333,-60.333,0]},{"o":{"x":0.333,"y":0.333},"i":{"x":0.667,"y":0.667},"s":[220.829,170.045,0],"t":24,"ti":[0,0,0],"to":[0,0,0]},{"o":{"x":0.216,"y":0},"i":{"x":0,"y":1},"s":[220.829,170.045,0],"t":28,"ti":[-17.667,-10.167,0],"to":[13.667,-11.333,0]},{"o":{"x":0.851,"y":0.851},"i":{"x":0,"y":0},"s":[284.829,168.045,0],"t":32,"ti":[0,0,0],"to":[0,0,0]},{"o":{"x":0.375,"y":0},"i":{"x":0.057,"y":1},"s":[284.829,168.045,0],"t":34,"ti":[-2.631,-12.748,0],"to":[1.667,31.5,0]},{"o":{"x":0.981,"y":0},"i":{"x":0.826,"y":1},"s":[216.829,246.045,0],"t":37,"ti":[-9.667,-12,0],"to":[4.333,21,0]},{"o":{"x":0.333,"y":0.333},"i":{"x":0.667,"y":0.667},"s":[280.829,246.045,0],"t":41,"ti":[0,0,0],"to":[0,0,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.04,"y":1},"s":[280.829,246.045,0],"t":44,"ti":[-1,-13,0],"to":[-2.167,23.667,0]},{"o":{"x":0.871,"y":0},"i":{"x":0.667,"y":1},"s":[222.829,322.045,0],"t":48,"ti":[-10.667,-0.333,0],"to":[1,13,0]},{"o":{"x":0.333,"y":0.333},"i":{"x":0.667,"y":0.667},"s":[286.829,324.045,0],"t":54,"ti":[0,0,0],"to":[0,0,0]},{"o":{"x":0.923,"y":0},"i":{"x":0.667,"y":1},"s":[286.829,324.045,0],"t":56,"ti":[-60.667,-35.333,0],"to":[60.667,35.333,0]},{"s":[650.829,536.045,0],"t":60}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.221,1.12],[0,0],[1.122,-1.22],[0,0],[-1.221,-1.121],[0,0],[-1.12,1.221],[0,0]],"o":[[0,0],[-1.221,-1.12],[0,0],[-1.121,1.221],[0,0],[1.22,1.121],[0,0],[1.121,-1.219]],"v":[[46.352,42.076],[-23.804,-22.35],[-28.044,-22.169],[-36.836,-12.594],[-36.656,-8.355],[33.501,56.07],[37.74,55.889],[46.533,46.314]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0627,0.7255,0.5059],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[38.207,23.72],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4},{"ty":4,"nm":"Profile","sr":1,"st":4,"op":144,"ip":9,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[21.129,20.971,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0,100],"t":9},{"s":[93.913,93.913,100],"t":15}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[37.904,43.851,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Profile","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[3.642,0],[0,0],[0,3.642],[0,0],[-3.643,0],[0,0],[0,-3.642],[0,0]],"o":[[0,0],[-3.643,0],[0,0],[0,-3.642],[0,0],[3.642,0],[0,0],[0,3.642]],"v":[[14.126,20.72],[-14.126,20.72],[-20.721,14.126],[-20.721,-14.126],[-14.126,-20.72],[14.126,-20.72],[20.721,-14.126],[20.721,14.126]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.898,0.9059,0.9216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[21.129,20.971],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":5,"parent":13},{"ty":4,"nm":"Line 1","sr":1,"st":5,"op":144,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50.672,13.223,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,93.883,100],"t":10},{"s":[93.883,93.883,100],"t":16}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[65.64,36.578,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":10},{"s":[100],"t":16}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Line 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[20.074,4.963],[-20.074,4.963],[-23.074,1.963],[-23.074,-1.963],[-20.074,-4.963],[20.074,-4.963],[23.074,-1.963],[23.074,1.963]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9529,0.9569,0.9647],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[73.746,13.223],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":6,"parent":13},{"ty":4,"nm":"Line 2","sr":1,"st":5,"op":144,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[50.421,32.288,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,93.883,100],"t":11},{"s":[93.883,93.883,100],"t":17}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[65.972,54.476,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":11},{"s":[100],"t":17}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Line 2","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.658,0],[0,0],[0,-1.658],[0,0]],"o":[[0,0],[-1.658,0],[0,0],[0,-1.658],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[20.576,4.962],[-20.576,4.962],[-23.576,1.962],[-23.576,-1.962],[-20.576,-4.962],[20.576,-4.962],[23.576,-1.962],[23.576,1.962]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.898,0.9059,0.9216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[73.996,32.288],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":7,"parent":13},{"ty":4,"nm":"Line 3","sr":1,"st":5,"op":144,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0.501,60.383,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,93.883,100],"t":12},{"s":[93.883,93.883,100],"t":18}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[18.539,80.853,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":12},{"s":[100],"t":18}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Line 3","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[45.159,4.963],[-45.159,4.963],[-48.159,1.963],[-48.159,-1.963],[-45.159,-4.963],[45.159,-4.963],[48.159,-1.963],[48.159,1.963]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.898,0.9059,0.9216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[48.66,60.383],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":8,"parent":13},{"ty":4,"nm":"Line 4","sr":1,"st":5,"op":144,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0.752,83.963,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,93.883,100],"t":13},{"s":[93.883,93.883,100],"t":19}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[18.774,102.99,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":13},{"s":[100],"t":19}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Line 4","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[45.034,4.962],[-45.034,4.962],[-48.034,1.962],[-48.034,-1.962],[-45.034,-4.962],[45.034,-4.962],[48.034,-1.962],[48.034,1.962]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.898,0.9059,0.9216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[48.786,83.963],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":9,"parent":13},{"ty":4,"nm":"Line 5","sr":1,"st":5,"op":144,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0.25,107.543,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,93.883,100],"t":14},{"s":[93.883,93.883,100],"t":20}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[18.303,125.128,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":14},{"s":[100],"t":20}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Line 5","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[45.661,4.962],[-45.661,4.962],[-48.661,1.962],[-48.661,-1.962],[-45.661,-4.962],[45.661,-4.962],[48.661,-1.962],[48.661,1.962]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.898,0.9059,0.9216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[48.911,107.543],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":10,"parent":13},{"ty":4,"nm":"Line 6","sr":1,"st":5,"op":144,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0.25,131.876,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,93.883,100],"t":15},{"s":[93.883,93.883,100],"t":21}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[18.303,147.972,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":15},{"s":[100],"t":21}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Line 6","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.656],[0,0]],"o":[[0,0],[-1.657,0],[0,0],[0,-1.656],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[45.661,4.963],[-45.661,4.963],[-48.661,1.963],[-48.661,-1.963],[-45.661,-4.963],[45.661,-4.963],[48.661,-1.963],[48.661,1.963]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.898,0.9059,0.9216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[48.911,131.876],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":11,"parent":13},{"ty":4,"nm":"Line 7","sr":1,"st":5,"op":145,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0.501,155.958,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,93.883,100],"t":16},{"s":[93.883,93.883,100],"t":22}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[18.539,170.581,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":16},{"s":[100],"t":22}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Line 7","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.656],[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.656]],"v":[[45.159,4.963],[-45.159,4.963],[-48.159,1.963],[-48.159,-1.963],[-45.159,-4.963],[45.159,-4.963],[48.159,-1.963],[48.159,1.963]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.898,0.9059,0.9216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[48.66,155.958],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":12,"parent":13},{"ty":4,"nm":"Right Page 1","sr":1,"st":-6,"op":145,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[64.304,101.087,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[100,94.05,100],"t":4},{"s":[138,138,100],"t":16}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[435.776,247,0],"t":4,"ti":[30.833,-0.003,0],"to":[-30.833,0.003,0]},{"s":[250.776,247.017,0],"t":16}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[7.048,0],[0,0],[0,7.048],[0,0],[-7.048,0],[0,0],[0,-7.048],[0,0]],"o":[[0,0],[-7.048,0],[0,0],[0,-7.048],[0,0],[7.048,0],[0,0],[0,7.048]],"v":[[51.292,100.837],[-51.293,100.837],[-64.054,88.076],[-64.054,-88.076],[-51.293,-100.837],[51.292,-100.837],[64.054,-88.076],[64.054,88.076]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0.9176,0.9176,0.9176],"t":4},{"s":[1,1,1],"t":16}],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[101.747,96.634],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[64.304,101.087],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":13},{"ty":4,"nm":"Right Page 2","sr":1,"st":4,"op":148,"ip":4,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[64.304,101.087,0],"ix":1},"s":{"a":0,"k":[100,88.1,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[575.776,252.017,0],"t":8,"ti":[23.333,0.833,0],"to":[-23.333,-0.833,0]},{"s":[435.776,247.017,0],"t":20}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 9","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[7.048,0],[0,0],[0,7.048],[0,0],[-7.048,0],[0,0],[0,-7.047],[0,0]],"o":[[0,0],[-7.048,0],[0,0],[0,-7.047],[0,0],[7.048,0],[0,0],[0,7.048]],"v":[[51.292,100.837],[-51.293,100.837],[-64.054,88.076],[-64.054,-88.076],[-51.293,-100.837],[51.292,-100.837],[64.054,-88.076],[64.054,88.076]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.898,0.9059,0.9216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[64.304,101.087],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":14},{"ty":4,"nm":"Left Page 1","sr":1,"st":0,"op":145,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[64.303,101.087,0],"ix":1},"s":{"a":0,"k":[100,93.058,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[64.898,247.073,0],"t":-2,"ti":[26.333,0,0],"to":[-26.333,0,0]},{"s":[-93.102,247.073,0],"t":10}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[7.048,0],[0,0],[0,7.048],[0,0],[-7.048,0],[0,0],[0,-7.048],[0,0]],"o":[[0,0],[-7.048,0],[0,0],[0,-7.048],[0,0],[7.048,0],[0,0],[0,7.048]],"v":[[51.293,100.837],[-51.292,100.837],[-64.053,88.075],[-64.053,-88.076],[-51.292,-100.837],[51.293,-100.837],[64.054,-88.076],[64.054,88.075]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9176,0.9176,0.9176],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[64.303,101.087],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":15},{"ty":4,"nm":"Middle Text","sr":1,"st":0,"op":144,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[64.911,107,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100,100,100],"t":3},{"s":[75,75,100],"t":15}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[249.339,244.683,0],"t":2,"ti":[30.667,0,0],"to":[-30.667,0,0]},{"s":[65.339,244.683,0],"t":14}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":2},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":8},{"s":[0],"t":14}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[3.642,0],[0,0],[0,3.642],[0,0],[-3.642,0],[0,0],[0,-3.642],[0,0]],"o":[[0,0],[-3.642,0],[0,0],[0,-3.642],[0,0],[3.642,0],[0,0],[0,3.642]],"v":[[20.939,27.533],[-20.939,27.533],[-27.534,20.939],[-27.534,-20.939],[-20.939,-27.533],[20.939,-27.533],[27.534,-20.939],[27.534,20.939]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8471,0.8471,0.8471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[27.993,27.784],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 2","ix":2,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.656],[0,0],[-1.657,0],[0,0],[0,-1.658],[0,0]],"o":[[0,0],[-1.657,0],[0,0],[0,-1.658],[0,0],[1.657,0],[0,0],[0,1.656]],"v":[[61.661,6.594],[-61.661,6.594],[-64.661,3.594],[-64.661,-3.594],[-61.661,-6.594],[61.661,-6.594],[64.661,-3.594],[64.661,3.594]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8471,0.8471,0.8471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[64.911,142.822],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 3","ix":3,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[60.827,6.594],[-60.827,6.594],[-63.827,3.594],[-63.827,-3.594],[-60.827,-6.594],[60.827,-6.594],[63.827,-3.594],[63.827,3.594]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8471,0.8471,0.8471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[64.744,111.488],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 4","ix":4,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.658,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[28.327,6.594],[-28.327,6.594],[-31.327,3.594],[-31.327,-3.594],[-28.327,-6.594],[28.327,-6.594],[31.327,-3.594],[31.327,3.594]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8471,0.8471,0.8471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[98.244,42.822],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 5","ix":5,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[27.661,6.594],[-27.661,6.594],[-30.661,3.594],[-30.661,-3.594],[-27.661,-6.594],[27.661,-6.594],[30.661,-3.594],[30.661,3.594]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8471,0.8471,0.8471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[97.911,17.489],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 6","ix":6,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[60.995,6.594],[-60.995,6.594],[-63.995,3.594],[-63.995,-3.594],[-60.995,-6.594],[60.995,-6.594],[63.995,-3.594],[63.995,3.594]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8471,0.8471,0.8471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[64.577,80.155],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 7","ix":7,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[61.661,6.594],[-61.661,6.594],[-64.661,3.594],[-64.661,-3.594],[-61.661,-6.594],[61.661,-6.594],[64.661,-3.594],[64.661,3.594]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8471,0.8471,0.8471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[64.911,175.155],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 8","ix":8,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0]],"o":[[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657]],"v":[[60.995,6.594],[-60.995,6.594],[-63.995,3.594],[-63.995,-3.594],[-60.995,-6.594],[60.995,-6.594],[63.995,-3.594],[63.995,3.594]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8471,0.8471,0.8471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[64.577,207.155],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":16},{"ty":4,"nm":"Middle Page","sr":1,"st":0,"op":144,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[91.789,134.239,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.392,"y":0},"i":{"x":0.598,"y":1},"s":[100,100,100],"t":3},{"s":[69,69,100],"t":15}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.377,"y":0},"i":{"x":0.667,"y":1},"s":[227.461,246.517,0],"t":2,"ti":[30.167,0,0],"to":[-30.167,0,0]},{"s":[46.461,246.517,0],"t":14}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[7.048,0],[0,0],[0,7.048],[0,0],[-7.047,0],[0,0],[0,-7.048],[0,0]],"o":[[0,0],[-7.047,0],[0,0],[0,-7.048],[0,0],[7.048,0],[0,0],[0,7.048]],"v":[[78.779,133.989],[-78.779,133.989],[-91.539,121.227],[-91.539,-121.228],[-78.779,-133.989],[78.779,-133.989],[91.539,-121.228],[91.539,121.227]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 2","c":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[1,1,1],"t":2},{"s":[0.9529,0.9569,0.9647],"t":14}],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[115.102,134.239],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":17},{"ty":4,"nm":"Circle","sr":1,"st":0,"op":144,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[168.508,168.508,0],"ix":1},"s":{"a":0,"k":[115.452,115.452,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[246.09,247.079,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-92.927],[92.927,0],[0,92.926],[-92.927,0]],"o":[[0,92.926],[-92.927,0],[0,-92.927],[92.927,0]],"v":[[168.258,0],[-0.001,168.258],[-168.258,0],[-0.001,-168.258]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9647,0.9725,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[168.508,168.508],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":18}],"v":"5.4.4","fr":24,"op":72,"ip":0,"assets":[]}
@@ -149,6 +149,16 @@ const DebugNavigationPanel = () => {
149
149
  appContext.currentWorkflowStep = step;
150
150
  }
151
151
  }
152
+ }, {
153
+ screen: 'AddressCaptureScreen',
154
+ label: 'Address',
155
+ setupDemo: () => {
156
+ setupDemoSession(true);
157
+ const step = appContext.workflowSteps?.find(s => s.type === 'ADDRESS_CAPTURE');
158
+ if (step) {
159
+ appContext.currentWorkflowStep = step;
160
+ }
161
+ }
152
162
  }, {
153
163
  screen: 'LivenessDetectionScreen',
154
164
  label: 'Liveness',
@@ -11,7 +11,7 @@ import { debugError, debugLog } from "../Libs/debug.utils.js";
11
11
  import { eidReader } from "../EIDReader/eidReader.js";
12
12
  import NativeProgressBar from "./NativeProgressBar.js";
13
13
  import { buildDocumentName } from "../Libs/mrzTransliteration.js";
14
- import { recoverVizName, recognizeVizBlocks } from "../Libs/vizName.js";
14
+ import { recoverVizName, recognizeVizBlocks, voteVizName } from "../Libs/vizName.js";
15
15
  import { normalizeFromMRZInfo, normalizeFromMRZFields } from "../Libs/documentDataNormalizer.js";
16
16
  import { compareChipToViz } from "../Libs/chipVizMatch.js";
17
17
  import { useTranslation } from 'react-i18next';
@@ -38,6 +38,7 @@ const EIDScanner = ({
38
38
  documentType,
39
39
  vizMrzFields,
40
40
  vizImage,
41
+ vizImages,
41
42
  onScanSuccess,
42
43
  isNFCSupported
43
44
  }) => {
@@ -256,11 +257,22 @@ const EIDScanner = ({
256
257
  // VIZ candidate when DG11 did NOT provide a clean two-part name.
257
258
  const dg11HasBothNames = !!passportData.dg11FirstName && !!passportData.dg11LastName;
258
259
  // Only OCR the VIZ when DG11 didn't already provide a clean name.
259
- const vizBlocks = !dg11HasBothNames ? await recognizeVizBlocks(vizImage) : [];
260
- const viz = vizBlocks.length ? recoverVizName(vizBlocks, documentData.firstName, documentData.lastName, Math.max(...vizBlocks.map(b => b.top))) : null;
260
+ // OCR every available image (data page + ID front/back) and fuse the
261
+ // recovered names by majority vote, so one noisy OCR pass can't corrupt
262
+ // the diacritic name. De-dup images so the same capture isn't voted
263
+ // twice (which would let a single pass outvote the others).
264
+ const vizSources = !dg11HasBothNames ? Array.from(new Set([vizImage, ...(vizImages ?? [])].filter(Boolean))) : [];
265
+ const perImage = await Promise.all(vizSources.map(async img => {
266
+ const blocks = await recognizeVizBlocks(img);
267
+ if (!blocks.length) return null;
268
+ return recoverVizName(blocks, documentData.firstName, documentData.lastName, Math.max(...blocks.map(b => b.top)), documentData.issuingCountry);
269
+ }));
270
+ const candidates = perImage.filter(c => c !== null);
271
+ const viz = candidates.length ? voteVizName(candidates) : null;
261
272
  debugLog('VIZName', 'eid recovery', {
262
273
  dg11HasBothNames,
263
- vizBlockCount: vizBlocks.length,
274
+ sources: vizSources.length,
275
+ candidates: candidates.length,
264
276
  matchedFirst: !!viz?.vizFirst,
265
277
  matchedLast: !!viz?.vizLast,
266
278
  mismatch: viz?.mismatch ?? false
@@ -22,7 +22,7 @@ import { speak, resetLastMessage } from "../Libs/tts.utils.js";
22
22
  import AppContext from "../Contexts/AppContext.js";
23
23
  import { useTheme } from "../Contexts/ThemeContext.js";
24
24
  import DebugOverlay, { TestModePanel } from "./DebugOverlay.js";
25
- import { getStatusMessage, getFrameToScreenTransform, transformBoundsToScreen, getScanAreaBounds, angleBetweenPoints, detectDocumentType, determineDocumentTypeToSet, areMRZFieldsEqual, hasRequiredMRZFields, validateFacePosition, isPassportDocumentCode } from "./IdentityDocumentCamera.utils.js";
25
+ import { getStatusMessage, getFrameToScreenTransform, transformBoundsToScreen, getScanAreaBounds, angleBetweenPoints, detectDocumentType, determineDocumentTypeToSet, areMRZFieldsEqual, hasRequiredMRZFields, validateFacePosition, isPassportDocumentCode, StableFlag } from "./IdentityDocumentCamera.utils.js";
26
26
  import { handlePassportFlow, handleIDFrontFlow, handleIDBackFlow, getNextStepAfterHologram } from "./IdentityDocumentCamera.flows.js";
27
27
  import { HOLOGRAM_IMAGE_COUNT, HOLOGRAM_DETECTION_THRESHOLD, HOLOGRAM_DETECTION_RETRY_COUNT, HOLOGRAM_CAPTURE_INTERVAL, HOLOGRAM_MAX_FRAMES_WITHOUT_FACE, MIN_BRIGHTNESS_THRESHOLD, MAX_BRIGHTNESS_THRESHOLD, FACE_EDGE_MARGIN_PERCENT, MAX_CONSECUTIVE_QUALITY_FAILURES, REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS, HELP_PROMPT_DELAY_MS, SIGNATURE_TEXT_REGEX, MRZ_BLOCK_PATTERN, MIN_CARD_FACE_SIZE_PERCENT } from "./IdentityDocumentCamera.constants.js";
28
28
 
@@ -99,6 +99,31 @@ const IdentityDocumentCamera = ({
99
99
  const consecutiveQualityFailures = useRef(0);
100
100
  const mrzDetectionCurrentRetryCount = useRef(0);
101
101
 
102
+ // Hysteresis for the on-screen quality guidance. Brightness/blur/glare are
103
+ // measured every frame; feeding the raw per-frame booleans straight into
104
+ // state made the guidance strobe (a single blurry hand-shake frame flipped
105
+ // "hold steady" on and off). These smooth each flag: it appears after a few
106
+ // confirming frames and lingers briefly before clearing, so the message is
107
+ // stable and only reflects a sustained condition. Raise fast (react to real
108
+ // problems), clear slow (don't flicker off on one good frame).
109
+ const brightnessLowFlag = useRef(new StableFlag(2, 5));
110
+ const blurryFlag = useRef(new StableFlag(3, 6));
111
+ const glareFlag = useRef(new StableFlag(2, 5));
112
+ // Glare is asserted by accept-time helpers that run inside the (separately
113
+ // scoped) handleFaceAndText callback, so it can't flow through a handler
114
+ // local. This ref is cleared at the top of each frame and set true if any
115
+ // glare check in that frame found glare; the handler reads it to feed
116
+ // `glareFlag`. `hadGlareText` distinguishes "card present but glaring" from
117
+ // an empty view so the warning only shows over an actual document.
118
+ const rawGlareRef = useRef(false);
119
+ // Whether a document was visible in the PREVIOUS frame (recognized text,
120
+ // barcode, or face). The quality guidance is stabilized BEFORE the quality
121
+ // gate (so dark/blurry frames — which the gate early-returns on — can still
122
+ // raise their warnings), but document-presence is only known LATER in the
123
+ // frame, so blur/glare are gated on the previous frame's value. One-frame lag
124
+ // at ~8fps is imperceptible and the hysteresis smooths it.
125
+ const documentPresentRef = useRef(false);
126
+
102
127
  // MRZ stability tracking - require consistent valid reads
103
128
  const lastValidMRZText = useRef(null);
104
129
  const lastValidMRZFields = useRef(null);
@@ -422,8 +447,13 @@ const IdentityDocumentCamera = ({
422
447
  100,
423
448
  // Full face height
424
449
  MAX_BRIGHTNESS_THRESHOLD);
425
- setIsGlareDetected(hasGlare);
426
- if (hasGlare) diagStats.current.hadGlare = true;
450
+ // OR-accumulate into the per-frame glare signal (reset at frame top); the
451
+ // frame handler feeds it through the stabilized UI flag. Also record the
452
+ // diagnostic.
453
+ if (hasGlare) {
454
+ rawGlareRef.current = true;
455
+ diagStats.current.hadGlare = true;
456
+ }
427
457
  return hasGlare;
428
458
  } catch (error) {
429
459
  return false; // Assume no glare on error
@@ -442,8 +472,13 @@ const IdentityDocumentCamera = ({
442
472
  try {
443
473
  // Check center 80% region for glare (document area)
444
474
  const hasGlare = await OpenCVModule.isRectangularRegionBright(image, Math.round(width * 0.1), Math.round(height * 0.1), Math.round(width * 0.8), Math.round(height * 0.8), MAX_BRIGHTNESS_THRESHOLD);
445
- setIsGlareDetected(hasGlare);
446
- if (hasGlare) diagStats.current.hadGlare = true;
475
+ // OR-accumulate into the per-frame glare signal (reset at frame top); the
476
+ // frame handler feeds it through the stabilized UI flag. Also record the
477
+ // diagnostic.
478
+ if (hasGlare) {
479
+ rawGlareRef.current = true;
480
+ diagStats.current.hadGlare = true;
481
+ }
447
482
  return hasGlare;
448
483
  } catch (error) {
449
484
  return false;
@@ -511,7 +546,16 @@ const IdentityDocumentCamera = ({
511
546
  }
512
547
  validMRZConsecutiveCount.current = 0;
513
548
  cachedBarcode.current = null; // Clear cached barcode on step change
514
-
549
+ // Start each step with clean, non-flickering guidance (no stale warning
550
+ // carried over from the previous side).
551
+ brightnessLowFlag.current.reset(false);
552
+ blurryFlag.current.reset(false);
553
+ glareFlag.current.reset(false);
554
+ rawGlareRef.current = false;
555
+ documentPresentRef.current = false;
556
+ setIsBrightnessLow(false);
557
+ setIsFrameBlurry(false);
558
+ setIsGlareDetected(false);
515
559
  if (nextStepType !== 'COMPLETED') {
516
560
  setTimeout(() => {
517
561
  setStatus('SEARCHING');
@@ -1407,17 +1451,26 @@ const IdentityDocumentCamera = ({
1407
1451
  } catch (error) {
1408
1452
  isRegionBright = isOverallBright;
1409
1453
  }
1410
- setIsBrightnessLow(!isRegionBright);
1411
- if (!isRegionBright) diagStats.current.hadLowBrightness = true;
1412
-
1413
- // Reset glare to false at the start of every frame. The glare helpers
1414
- // (checkDocumentGlare/checkFaceGlare) only run at accept-time and set it
1415
- // true when they find glare; without this per-frame clear the flag would
1416
- // latch a "Glare detected" warning would stick over a now-clean view
1417
- // (and, once a face is locked, the face-glare site never runs again so it
1418
- // could never clear at all). The accept-time checks run later in this same
1419
- // frame's handlers and re-assert true if glare is still present.
1420
- setIsGlareDetected(false);
1454
+
1455
+ // Raw per-frame quality readings. The UI-facing warnings are NOT set from
1456
+ // these directly — they are stabilized (hysteresis) and gated on document
1457
+ // presence at the end of the handler so the guidance doesn't strobe or nag
1458
+ // over an empty view. The raw values still feed the frame-processing
1459
+ // quality gate below unchanged.
1460
+ const rawBrightnessLow = !isRegionBright;
1461
+ if (rawBrightnessLow) diagStats.current.hadLowBrightness = true;
1462
+
1463
+ // Per-frame glare is asserted by the accept-time helpers (checkDocumentGlare
1464
+ // /checkFaceGlare), but those run inside handleFaceAndText which is fired
1465
+ // (async, un-awaited) LATER in this handler — so this frame's glare result
1466
+ // isn't known yet at the stabilization block below. We therefore feed the
1467
+ // stabilizer the PREVIOUS frame's glare signal (captured here before the
1468
+ // reset). At ~8fps the one-frame lag is imperceptible and the hysteresis
1469
+ // smooths it anyway; without this the read would always see false and the
1470
+ // glare warning could never fire. Reset after capturing so a now-clean
1471
+ // view clears (no latch).
1472
+ const prevFrameGlare = rawGlareRef.current;
1473
+ rawGlareRef.current = false;
1421
1474
 
1422
1475
  // Check blur only in center region (area of interest) to avoid false positives
1423
1476
  // from iOS depth-of-field background blur
@@ -1437,10 +1490,9 @@ const IdentityDocumentCamera = ({
1437
1490
  50 // threshold
1438
1491
  );
1439
1492
  isNotBlurry = !isBlurry;
1440
- setIsFrameBlurry(isBlurry);
1441
1493
  if (isBlurry) diagStats.current.hadBlurryFrames = true;
1442
1494
  } catch (error) {
1443
- setIsFrameBlurry(false);
1495
+ isBlurry = false;
1444
1496
  }
1445
1497
 
1446
1498
  // Flush accumulated camera stats into the diagnostics collector.
@@ -1465,6 +1517,29 @@ const IdentityDocumentCamera = ({
1465
1517
  // Only proceed if image quality is acceptable
1466
1518
  const hasAcceptableQuality = isRegionBright && isNotBlurry;
1467
1519
 
1520
+ // --- Stabilized, presence-gated quality guidance -------------------
1521
+ // MUST run BEFORE the quality-gate early-return below: a dark or blurry
1522
+ // frame is exactly what makes hasAcceptableQuality false, so if this ran
1523
+ // after the gate the "too dark" / "hold steady" warnings could never
1524
+ // raise (their triggering frames would have already returned). Blur/glare
1525
+ // are gated on document presence; brightness shows regardless, since a
1526
+ // dark room is worth flagging before the card is even framed.
1527
+ //
1528
+ // Presence combines the previous frame's confirmed value (documentPresent-
1529
+ // Ref, set post-detection) with a CHEAP raw-frame proxy read directly off
1530
+ // the native ML Kit results (faces/text/barcodes are on `frame` before any
1531
+ // processing). Without the raw proxy, a document that is blurry/dark from
1532
+ // its very first framed moment would keep failing the quality gate and
1533
+ // early-returning before documentPresentRef is ever set true — suppressing
1534
+ // its blur/glare hints for ~30 frames. The proxy lets the hint fire on the
1535
+ // first framed frame.
1536
+ const rawDocumentPresent = (frame.faces?.length ?? 0) > 0 || (frame.textBlocks?.length ?? 0) > 0 || (frame.barcodes?.length ?? 0) > 0;
1537
+ const documentWasPresent = documentPresentRef.current || rawDocumentPresent;
1538
+ setIsBrightnessLow(brightnessLowFlag.current.push(rawBrightnessLow));
1539
+ setIsFrameBlurry(blurryFlag.current.push(documentWasPresent && isBlurry));
1540
+ setIsGlareDetected(glareFlag.current.push(documentWasPresent && prevFrameGlare));
1541
+ // -------------------------------------------------------------------
1542
+
1468
1543
  // Store quality metrics in ref for access in handleFaceAndText callback
1469
1544
  lastFrameQuality.current = {
1470
1545
  hasAcceptableQuality,
@@ -1698,6 +1773,12 @@ const IdentityDocumentCamera = ({
1698
1773
  setAllElementsDetected(false);
1699
1774
  }
1700
1775
 
1776
+ // Record whether a document is present THIS frame (recognized text, a
1777
+ // barcode, or a face) so next frame's blur/glare gating can read it.
1778
+ // The stabilized guidance itself is set earlier, before the quality
1779
+ // gate — see the block above `hasAcceptableQuality`.
1780
+ documentPresentRef.current = textBlocks.length > 0 || barcodes.length > 0 || detectedFaces.length > 0 || cachedBarcode.current !== null;
1781
+
1701
1782
  // Check if detected elements are inside the scan area
1702
1783
  const {
1703
1784
  scanLeft,
@@ -4,6 +4,53 @@ import { Dimensions } from 'react-native';
4
4
  import { SIGNATURE_REGEX, PASSPORT_MRZ_PATTERN, MRZ_DENSE_PATTERN } from "./IdentityDocumentCamera.constants.js";
5
5
  import { debugLog, isDebugEnabled } from "../Libs/debug.utils.js";
6
6
 
7
+ /**
8
+ * Debounced boolean with hysteresis, for stabilizing per-frame quality flags.
9
+ *
10
+ * The camera measures brightness/blur/glare on EVERY frame, and a single noisy
11
+ * frame (a momentary blur while the hand moves, one dark frame) would otherwise
12
+ * flip the on-screen guidance on and off many times a second — a distracting
13
+ * flicker. This smooths a raw per-frame boolean so it only turns ON after
14
+ * `raiseFrames` consecutive true readings and only turns OFF after `clearFrames`
15
+ * consecutive false readings; isolated opposite frames are absorbed. Asymmetric
16
+ * by design: warnings should appear promptly once a problem is real (small
17
+ * `raiseFrames`) but linger briefly so they don't strobe (larger `clearFrames`).
18
+ */
19
+ export class StableFlag {
20
+ streak = 0;
21
+ constructor(raiseFrames, clearFrames, initial = false) {
22
+ this.raiseFrames = raiseFrames;
23
+ this.clearFrames = clearFrames;
24
+ this.value = initial;
25
+ this.lastRaw = initial;
26
+ }
27
+
28
+ /** Feed one raw measurement; returns the current stabilized value. */
29
+ push(raw) {
30
+ if (raw === this.lastRaw) {
31
+ this.streak++;
32
+ } else {
33
+ this.streak = 1;
34
+ this.lastRaw = raw;
35
+ }
36
+ if (raw !== this.value) {
37
+ const needed = raw ? this.raiseFrames : this.clearFrames;
38
+ if (this.streak >= needed) this.value = raw;
39
+ }
40
+ return this.value;
41
+ }
42
+
43
+ /** Force the stabilized value (e.g. on step change) without waiting. */
44
+ reset(value) {
45
+ this.value = value;
46
+ this.lastRaw = value;
47
+ this.streak = 0;
48
+ }
49
+ get current() {
50
+ return this.value;
51
+ }
52
+ }
53
+
7
54
  /**
8
55
  * Checks if a document code represents an ID card (TD1/TD2).
9
56
  * Per ICAO 9303, valid ID card codes start with 'I', 'A', or 'C'.
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * On-device, best-effort check that a captured address-proof photo actually
5
+ * looks like a document containing the holder's NAME and ADDRESS information.
6
+ *
7
+ * MASAK Tebliğ No. 32 Art. 4/C(1)(d) requires an OPERATOR to manually review the
8
+ * address proof, so this is only a soft quality gate: it nudges the user to
9
+ * retake an obviously-wrong photo (a selfie, a blank wall, the wrong document)
10
+ * before it wastes an operator's time. A negative result NEVER blocks the user.
11
+ *
12
+ * Input is the OCR text recognised on the captured proof photo (full image,
13
+ * via the native still-image recognizer), plus the holder's name and any chip
14
+ * (DG11) address tokens — no extra ML models, no network, works offline.
15
+ */
16
+
17
+ import { foldDiacritics } from "./vizName.js";
18
+ /**
19
+ * Address keywords across the languages this SDK targets (TR + common EU/EN).
20
+ * Matched as whole words after normalisation. A Set so membership is O(1) and
21
+ * duplicates collapse. Deliberately broad — recall over precision, since the
22
+ * operator makes the real decision — BUT we exclude very short tokens
23
+ * ('NO','IL','ST','SK','RD') that collide with everyday non-address text and
24
+ * would make the address signal fire on almost any document.
25
+ */
26
+ const ADDRESS_KEYWORDS = new Set([
27
+ // Turkish
28
+ 'MAH', 'MAHALLE', 'MAHALLESI', 'CAD', 'CADDE', 'CADDESI', 'SOK', 'SOKAK', 'APT', 'DAIRE', 'BINA', 'BLOK', 'KAPI', 'ILCE', 'SEHIR', 'POSTA', 'ADRES', 'BULVAR', 'BULVARI', 'SITE',
29
+ // English / generic
30
+ 'STREET', 'AVENUE', 'AVE', 'ROAD', 'LANE', 'BLVD', 'BOULEVARD', 'APARTMENT', 'SUITE', 'FLOOR', 'BUILDING', 'CITY', 'TOWN', 'POSTAL', 'POSTCODE', 'ZIP', 'ADDRESS', 'DISTRICT', 'PROVINCE',
31
+ // German (passports/utility bills are common)
32
+ 'STRASSE', 'PLATZ', 'GASSE', 'STADT', 'PLZ', 'ADRESSE']);
33
+
34
+ /**
35
+ * Strip diacritics + uppercase so "Şişli" ~ "SISLI" and OCR variants align.
36
+ * Shares the SDK's canonical diacritic fold ({@link foldDiacritics}) so the
37
+ * address path and the VIZ-name path stay in sync (ß→SS, ø→O, ł→L, …).
38
+ */
39
+ function normalize(input) {
40
+ return foldDiacritics(input);
41
+ }
42
+
43
+ /** Split into alphabetic / numeric tokens, dropping punctuation. */
44
+ function tokenize(input) {
45
+ return normalize(input).split(/[^A-Z0-9]+/).filter(t => t.length > 0);
46
+ }
47
+
48
+ /**
49
+ * A postal-code-like run, REQUIRED to be flanked by address-ish context to
50
+ * avoid matching invoice/account/order numbers. We accept a 4–6 digit run only
51
+ * when it is not part of a longer digit run (so account numbers like 1234567890
52
+ * don't count) — `\b\d{4,6}\b` already excludes longer runs.
53
+ */
54
+ const POSTAL_RE = /\b\d{4,6}\b/;
55
+
56
+ /**
57
+ * Levenshtein distance ≤ 1 check (single insert/delete/substitute), used to
58
+ * tolerate one OCR slip in a name token (e.g. "SAYGI" read as "SAYGL").
59
+ */
60
+ function withinEditDistance1(a, b) {
61
+ if (a === b) return true;
62
+ const la = a.length;
63
+ const lb = b.length;
64
+ if (Math.abs(la - lb) > 1) return false;
65
+ // Substitution (equal length): at most one differing char.
66
+ if (la === lb) {
67
+ let diffs = 0;
68
+ for (let i = 0; i < la; i++) {
69
+ if (a[i] !== b[i] && ++diffs > 1) return false;
70
+ }
71
+ return true;
72
+ }
73
+ // Insertion/deletion (length differs by 1): the shorter is a subsequence
74
+ // of the longer with exactly one extra char.
75
+ const [short, long] = la < lb ? [a, b] : [b, a];
76
+ let i = 0;
77
+ let j = 0;
78
+ let skips = 0;
79
+ while (i < short.length && j < long.length) {
80
+ if (short[i] === long[j]) {
81
+ i++;
82
+ j++;
83
+ } else if (++skips > 1) {
84
+ return false;
85
+ } else {
86
+ j++;
87
+ }
88
+ }
89
+ return true;
90
+ }
91
+
92
+ /**
93
+ * Assess whether the OCR text from a captured proof plausibly contains the
94
+ * holder's name and an address. Best-effort and tolerant of OCR noise.
95
+ */
96
+ export function assessAddressProof(recognizedText, ctx) {
97
+ const text = (recognizedText ?? '').trim();
98
+ if (!text) {
99
+ // No text recognised at all (blank/sideways/too blurry) — fail soft.
100
+ return {
101
+ hasName: false,
102
+ hasAddress: false,
103
+ ok: false
104
+ };
105
+ }
106
+ const normText = normalize(text);
107
+ const tokens = tokenize(text);
108
+ const textTokens = new Set(tokens);
109
+
110
+ // --- Name: a name part (>=3 chars) appears as a token in the OCR text,
111
+ // tolerating a single OCR slip (edit distance ≤1) on tokens of 4+ chars
112
+ // so the holder's own document isn't flagged over one misread letter. ---
113
+ const nameTokens = ctx.nameParts.flatMap(p => p ? tokenize(p) : []).filter(t => t.length >= 3 && !/^\d+$/.test(t));
114
+ const hasName = nameTokens.some(n => textTokens.has(n) || n.length >= 4 && tokens.some(t => withinEditDistance1(n, t)));
115
+
116
+ // --- Address: postal code OR an address keyword OR overlap with the chip
117
+ // address tokens (a strong, holder-specific signal). ---
118
+ const hasPostal = POSTAL_RE.test(normText);
119
+ const hasKeyword = tokens.some(t => ADDRESS_KEYWORDS.has(t));
120
+ const chipTokens = (ctx.chipAddress ? tokenize(ctx.chipAddress) : []).filter(t => t.length >= 3);
121
+ const chipOverlap = chipTokens.filter(t => textTokens.has(t)).length;
122
+ const hasChipMatch = chipOverlap >= 2; // ≥2 chip-address words present
123
+
124
+ const hasAddress = hasPostal || hasKeyword || hasChipMatch;
125
+ return {
126
+ hasName,
127
+ hasAddress,
128
+ ok: hasName && hasAddress
129
+ };
130
+ }