@smileid/web-components 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/document/src/DocumentCaptureScreens.js +409 -409
- package/components/document/src/document-capture/DocumentCapture.js +760 -760
- package/components/selfie/src/SelfieCaptureScreens.js +290 -290
- package/components/selfie/src/selfie-capture/SelfieCapture.js +1011 -979
- package/components/selfie/src/selfie-capture/SelfieCapture.stories.js +36 -36
- package/components/signature-pad/package.json +30 -30
- package/package.json +48 -48
- package/generate-exports.js +0 -34
|
@@ -1,760 +1,760 @@
|
|
|
1
|
-
import SmartCamera from '../../../../domain/camera/src/SmartCamera';
|
|
2
|
-
import styles from '../../../../styles/src/styles';
|
|
3
|
-
import '../../../navigation/src';
|
|
4
|
-
|
|
5
|
-
function hasMoreThanNColors(data, n = 16) {
|
|
6
|
-
const colors = new Set();
|
|
7
|
-
for (let i = 0; i < Math.min(data.length, 10000); i += 4) {
|
|
8
|
-
// eslint-disable-next-line no-bitwise
|
|
9
|
-
colors.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]);
|
|
10
|
-
if (colors.size > n) {
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function templateString() {
|
|
18
|
-
return `
|
|
19
|
-
<style>
|
|
20
|
-
.visually-hidden {
|
|
21
|
-
border: 0;
|
|
22
|
-
clip: rect(1px 1px 1px 1px);
|
|
23
|
-
clip: rect(1px, 1px, 1px, 1px);
|
|
24
|
-
height: auto;
|
|
25
|
-
margin: 0;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
padding: 0;
|
|
28
|
-
position: absolute;
|
|
29
|
-
white-space: nowrap;
|
|
30
|
-
width: 1px;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.mobile-camera-screen video {
|
|
34
|
-
display: block;
|
|
35
|
-
object-fit: cover;
|
|
36
|
-
object-position: center;
|
|
37
|
-
width: 100%;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.id-video.mobile-camera-screen {
|
|
41
|
-
display: flex;
|
|
42
|
-
align-items: stretch;
|
|
43
|
-
justify-content: center;
|
|
44
|
-
max-height: 300px;
|
|
45
|
-
height: 15rem;
|
|
46
|
-
width: 100%;
|
|
47
|
-
overflow: visible;
|
|
48
|
-
margin: 0 auto;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@media (max-width: 600px) {
|
|
52
|
-
.section {
|
|
53
|
-
width: 99%;
|
|
54
|
-
height: 100vh;
|
|
55
|
-
justify-content: center;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
#document-capture-screen,
|
|
62
|
-
#back-of-document-capture-screen {
|
|
63
|
-
block-size: 45rem;
|
|
64
|
-
padding-block: 2rem;
|
|
65
|
-
display: flex;
|
|
66
|
-
flex-direction: column;
|
|
67
|
-
max-block-size: 100%;
|
|
68
|
-
max-inline-size: 40ch;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
#document-capture-screen header p {
|
|
72
|
-
margin-block: 0 !important;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.padding-bottom-2 {
|
|
76
|
-
padding-bottom: 2rem;
|
|
77
|
-
}
|
|
78
|
-
@media (min-width: 600px) {
|
|
79
|
-
video {
|
|
80
|
-
object-fit: contain;
|
|
81
|
-
-webkit-tap-highlight-color: transparent;
|
|
82
|
-
content: normal;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.id-video {
|
|
86
|
-
width: 99%;
|
|
87
|
-
text-align: center;
|
|
88
|
-
position: relative;
|
|
89
|
-
overflow: hidden;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.id-video-container {
|
|
93
|
-
margin: auto;
|
|
94
|
-
padding: 0px;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
.id-video-container {
|
|
98
|
-
display: flex;
|
|
99
|
-
flex-direction: column;
|
|
100
|
-
align-items: center;
|
|
101
|
-
}
|
|
102
|
-
.id-video {
|
|
103
|
-
width: 100%;
|
|
104
|
-
text-align: center;
|
|
105
|
-
position: relative;
|
|
106
|
-
background: white;
|
|
107
|
-
}
|
|
108
|
-
.video-overlay {
|
|
109
|
-
position: absolute;
|
|
110
|
-
border-style: solid;
|
|
111
|
-
border-color: rgba(0, 0, 0, 0.48);
|
|
112
|
-
box-sizing: border-box;
|
|
113
|
-
inset: 0px;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.video-overlay .inner-border {
|
|
117
|
-
position: absolute;
|
|
118
|
-
border-width: 0.25rem;
|
|
119
|
-
border-color: #9394ab;
|
|
120
|
-
border-style: solid;
|
|
121
|
-
border-radius: 0.25rem;
|
|
122
|
-
inset: -1px;
|
|
123
|
-
}
|
|
124
|
-
canvas {
|
|
125
|
-
border-width: 0.25rem;
|
|
126
|
-
border-color: #9394ab;
|
|
127
|
-
border-style: solid;
|
|
128
|
-
border-radius: 0.25rem;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.description {
|
|
132
|
-
align-self: center;
|
|
133
|
-
padding-bottom: 1.75rem;
|
|
134
|
-
}
|
|
135
|
-
.reset-margin-block {
|
|
136
|
-
margin-block: 0;
|
|
137
|
-
}
|
|
138
|
-
.align-items-center {
|
|
139
|
-
align-items: center;
|
|
140
|
-
}
|
|
141
|
-
.id-side {
|
|
142
|
-
padding-bottom: 0.5rem;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.circle-progress {
|
|
146
|
-
display: flex;
|
|
147
|
-
flex-direction: column;
|
|
148
|
-
align-items: center;
|
|
149
|
-
justify-content: center;
|
|
150
|
-
height: 10rem;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.portrait .sticky {
|
|
154
|
-
position: -webkit-sticky; /* Safari */
|
|
155
|
-
position: sticky;
|
|
156
|
-
bottom: 0;
|
|
157
|
-
}
|
|
158
|
-
.video-footer {
|
|
159
|
-
background-color: rgba(255, 255, 255, 0.17);
|
|
160
|
-
padding-top: 10px;
|
|
161
|
-
}
|
|
162
|
-
</style>
|
|
163
|
-
${styles(this.themeColor)}
|
|
164
|
-
<div id='document-capture-screen' class='flow center flex-column'>
|
|
165
|
-
<smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
|
|
166
|
-
<h2 class='text-base font-bold title-color'>${this.documentName}</h2>
|
|
167
|
-
<div class="circle-progress" id="loader">
|
|
168
|
-
${this.cameraError ? '' : '<p class="spinner"></p>'}
|
|
169
|
-
${this.cameraError ? `<p style="--flow-space: 4rem" class='color-red | center'>${this.cameraError}</p>` : '<p style="--flow-space: 4rem">Checking permissions</p>'}
|
|
170
|
-
</div>
|
|
171
|
-
<div class='section | flow ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' ${this.cameraError ? 'hidden' : ''}>
|
|
172
|
-
<div class='id-video-container'>
|
|
173
|
-
<div class='id-video ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' hidden>
|
|
174
|
-
</div>
|
|
175
|
-
<div class='video-footer sticky'>
|
|
176
|
-
<h2 class='text-base font-bold title-color reset-margin-block id-side'>${this.title}</h2>
|
|
177
|
-
<h4 class='text-base font-normal title-color description reset-margin-block'>Make sure all corners are visible and there is no glare.</h4>
|
|
178
|
-
<div class='actions' hidden>
|
|
179
|
-
<button id='capture-id-image' class='button icon-btn | center' type='button'>
|
|
180
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="70" viewBox="0 0 70 70" fill="none" aria-hidden="true" focusable="false">
|
|
181
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M35 70C54.33 70 70 54.33 70 35C70 15.67 54.33 0 35 0C15.67 0 0 15.67 0 35C0 54.33 15.67 70 35 70ZM61 35C61 49.3594 49.3594 61 35 61C20.6406 61 9 49.3594 9 35C9 20.6406 20.6406 9 35 9C49.3594 9 61 20.6406 61 35ZM65 35C65 51.5685 51.5685 65 35 65C18.4315 65 5 51.5685 5 35C5 18.4315 18.4315 5 35 5C51.5685 5 65 18.4315 65 35Z" fill="${this.themeColor}"/>
|
|
182
|
-
</svg>
|
|
183
|
-
<span class='visually-hidden'>Capture Document</span>
|
|
184
|
-
</button>
|
|
185
|
-
</div>
|
|
186
|
-
${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
`;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const documentCaptureScale = 0.6;
|
|
195
|
-
|
|
196
|
-
class DocumentCapture extends HTMLElement {
|
|
197
|
-
constructor() {
|
|
198
|
-
super();
|
|
199
|
-
this.templateString = templateString.bind(this);
|
|
200
|
-
this.render = () => this.templateString();
|
|
201
|
-
|
|
202
|
-
this.attachShadow({ mode: 'open' });
|
|
203
|
-
this.IdSides = {
|
|
204
|
-
back: 'Back',
|
|
205
|
-
front: 'Front',
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
connectedCallback() {
|
|
210
|
-
const template = document.createElement('template');
|
|
211
|
-
template.innerHTML = this.render();
|
|
212
|
-
this.shadowRoot.innerHTML = '';
|
|
213
|
-
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
|
214
|
-
this.setUpEventListeners();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async getUserMedia() {
|
|
218
|
-
if (SmartCamera.stream) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (!this.hasAttribute('data-camera-error')) return;
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
await SmartCamera.getMedia({
|
|
225
|
-
audio: false,
|
|
226
|
-
video: {
|
|
227
|
-
...SmartCamera.environmentOptions,
|
|
228
|
-
aspectRatio: { ideal: 16 / 9 },
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
} catch (error) {
|
|
232
|
-
console.error(error.constraint);
|
|
233
|
-
console.error(error.message);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
this.handleIDStream(SmartCamera.stream);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
_captureIDImage() {
|
|
240
|
-
const imageDetails = this._drawIDImage();
|
|
241
|
-
this._stopIDVideoStream();
|
|
242
|
-
|
|
243
|
-
this.dispatchEvent(
|
|
244
|
-
new CustomEvent('document-capture.publish', {
|
|
245
|
-
detail: {
|
|
246
|
-
...imageDetails,
|
|
247
|
-
},
|
|
248
|
-
}),
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
_drawIDImage(video = this._IDVideo) {
|
|
253
|
-
const canvas = document.createElement('canvas');
|
|
254
|
-
if (this.isPortraitCaptureView) {
|
|
255
|
-
canvas.width = video.videoWidth;
|
|
256
|
-
canvas.height = (canvas.width * 16) / 9;
|
|
257
|
-
|
|
258
|
-
const previewCanvas = document.createElement('canvas');
|
|
259
|
-
previewCanvas.width = canvas.width;
|
|
260
|
-
previewCanvas.height = canvas.height;
|
|
261
|
-
|
|
262
|
-
this.updatePortraitId(canvas, video, 1, 1);
|
|
263
|
-
this.updatePortraitId(previewCanvas, video);
|
|
264
|
-
const image = canvas.toDataURL('image/jpeg');
|
|
265
|
-
const previewImage = previewCanvas.toDataURL('image/jpeg');
|
|
266
|
-
return {
|
|
267
|
-
image,
|
|
268
|
-
originalHeight: canvas.height,
|
|
269
|
-
originalWidth: canvas.width,
|
|
270
|
-
previewImage,
|
|
271
|
-
...this.idCardRegion,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
canvas.width = 2240;
|
|
276
|
-
canvas.height = 1260;
|
|
277
|
-
|
|
278
|
-
const height = canvas.width / (video.videoWidth / video.videoHeight);
|
|
279
|
-
canvas.height = height;
|
|
280
|
-
|
|
281
|
-
const previewCanvas = document.createElement('canvas');
|
|
282
|
-
previewCanvas.height = canvas.height;
|
|
283
|
-
previewCanvas.width = canvas.width;
|
|
284
|
-
const isPortrait = video.videoWidth < video.videoHeight;
|
|
285
|
-
if (isPortrait) {
|
|
286
|
-
const intermediateCanvas = document.createElement('canvas');
|
|
287
|
-
previewCanvas.height = canvas.width / 1.75;
|
|
288
|
-
canvas.width = 2240;
|
|
289
|
-
canvas.height = canvas.width / 1.77;
|
|
290
|
-
this._capturePortraitToLandscapeImage(intermediateCanvas, video);
|
|
291
|
-
this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas, 1, 1);
|
|
292
|
-
this._drawLandscapeImageFromCanvas(previewCanvas, intermediateCanvas);
|
|
293
|
-
} else {
|
|
294
|
-
this._drawLandscapeImage(canvas, video, 1, 1);
|
|
295
|
-
this._drawLandscapeImage(previewCanvas, video);
|
|
296
|
-
}
|
|
297
|
-
const image = canvas.toDataURL('image/jpeg');
|
|
298
|
-
|
|
299
|
-
const previewImage = previewCanvas.toDataURL('image/jpeg');
|
|
300
|
-
return {
|
|
301
|
-
image,
|
|
302
|
-
originalHeight: canvas.height,
|
|
303
|
-
originalWidth: canvas.width,
|
|
304
|
-
previewImage,
|
|
305
|
-
...this.idCardRegion,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
_drawImage(canvas, enableImageTests = true, video = SmartCamera.stream) {
|
|
310
|
-
this.resetErrorMessage();
|
|
311
|
-
const context = canvas.getContext('2d');
|
|
312
|
-
|
|
313
|
-
context.drawImage(
|
|
314
|
-
video,
|
|
315
|
-
0,
|
|
316
|
-
0,
|
|
317
|
-
video.videoWidth,
|
|
318
|
-
video.videoHeight,
|
|
319
|
-
0,
|
|
320
|
-
0,
|
|
321
|
-
canvas.width,
|
|
322
|
-
canvas.height,
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
if (enableImageTests) {
|
|
326
|
-
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
327
|
-
|
|
328
|
-
const hasEnoughColors = hasMoreThanNColors(imageData.data);
|
|
329
|
-
|
|
330
|
-
if (hasEnoughColors) {
|
|
331
|
-
return context;
|
|
332
|
-
}
|
|
333
|
-
throw new Error(
|
|
334
|
-
'Unable to capture webcam images - Please try another device',
|
|
335
|
-
);
|
|
336
|
-
} else {
|
|
337
|
-
return context;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
handleIDStream(stream) {
|
|
342
|
-
try {
|
|
343
|
-
const videoExists = this.shadowRoot.querySelector('canvas');
|
|
344
|
-
if (videoExists) {
|
|
345
|
-
// remove canvas
|
|
346
|
-
videoExists.remove();
|
|
347
|
-
}
|
|
348
|
-
let video = null;
|
|
349
|
-
let canvas = null;
|
|
350
|
-
video = document.createElement('video');
|
|
351
|
-
canvas = document.createElement('canvas');
|
|
352
|
-
const videoContainer = this.shadowRoot.querySelector(
|
|
353
|
-
'.id-video-container',
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
video.muted = true;
|
|
357
|
-
video.setAttribute('muted', 'true');
|
|
358
|
-
|
|
359
|
-
video.autoplay = true;
|
|
360
|
-
video.playsInline = true;
|
|
361
|
-
if ('srcObject' in video) {
|
|
362
|
-
video.srcObject = stream;
|
|
363
|
-
} else {
|
|
364
|
-
video.src = window.URL.createObjectURL(stream);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
canvas.width = videoContainer.clientWidth;
|
|
368
|
-
canvas.height = (videoContainer.clientWidth * 9) / 16;
|
|
369
|
-
if (this.isPortraitCaptureView) {
|
|
370
|
-
canvas.height = (videoContainer.clientWidth * 16) / 9;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
video.onloadedmetadata = () => {
|
|
374
|
-
video.play();
|
|
375
|
-
|
|
376
|
-
this.shadowRoot.querySelector('#loader').hidden = true;
|
|
377
|
-
this.shadowRoot.querySelector('.id-video').hidden = false;
|
|
378
|
-
this.shadowRoot.querySelector('.actions').hidden = false;
|
|
379
|
-
if (!videoExists) {
|
|
380
|
-
videoContainer.prepend(canvas);
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
const onVideoStart = () => {
|
|
385
|
-
if (video.paused || video.ended) return;
|
|
386
|
-
video.removeEventListener('playing', onVideoStart);
|
|
387
|
-
const aspectRatio = video.videoWidth / video.videoHeight;
|
|
388
|
-
const portrait = aspectRatio < 1;
|
|
389
|
-
if (this.isPortraitCaptureView) {
|
|
390
|
-
this.updatePortraitId(canvas, video);
|
|
391
|
-
requestAnimationFrame(onVideoStart);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (portrait) {
|
|
396
|
-
videoContainer.classList.add('mobile-camera-screen');
|
|
397
|
-
const intermediateCanvas = document.createElement('canvas');
|
|
398
|
-
this._capturePortraitToLandscapeImage(intermediateCanvas, video);
|
|
399
|
-
this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas);
|
|
400
|
-
} else {
|
|
401
|
-
this._drawLandscapeImage(canvas, video);
|
|
402
|
-
}
|
|
403
|
-
requestAnimationFrame(onVideoStart);
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
video.addEventListener('playing', onVideoStart);
|
|
407
|
-
|
|
408
|
-
this._IDStream = stream;
|
|
409
|
-
this._IDVideo = video;
|
|
410
|
-
} catch (error) {
|
|
411
|
-
this.setAttribute(
|
|
412
|
-
'data-camera-error',
|
|
413
|
-
SmartCamera.handleCameraError(error),
|
|
414
|
-
);
|
|
415
|
-
if (error.name !== 'AbortError') {
|
|
416
|
-
console.error(error);
|
|
417
|
-
}
|
|
418
|
-
SmartCamera.stopMedia();
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
_drawLandscapeImage(
|
|
423
|
-
canvas,
|
|
424
|
-
video = this._IDVideo,
|
|
425
|
-
scaleHeight = documentCaptureScale,
|
|
426
|
-
scaleWidth = documentCaptureScale,
|
|
427
|
-
) {
|
|
428
|
-
const heightScaleFactor = this.height
|
|
429
|
-
? this.height / video.videoHeight
|
|
430
|
-
: scaleHeight;
|
|
431
|
-
const widthScaleFactor = this.width
|
|
432
|
-
? this.width / video.videoWidth
|
|
433
|
-
: scaleWidth;
|
|
434
|
-
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
435
|
-
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
436
|
-
const width = video.videoWidth * widthScaleFactor;
|
|
437
|
-
const height = video.videoHeight * heightScaleFactor;
|
|
438
|
-
const startX = video.videoWidth * scaleWidthOffset;
|
|
439
|
-
const startY = video.videoHeight * scaleHeightOffset;
|
|
440
|
-
|
|
441
|
-
canvas
|
|
442
|
-
.getContext('2d')
|
|
443
|
-
.drawImage(
|
|
444
|
-
video,
|
|
445
|
-
startX,
|
|
446
|
-
startY,
|
|
447
|
-
width,
|
|
448
|
-
height,
|
|
449
|
-
0,
|
|
450
|
-
0,
|
|
451
|
-
canvas.width,
|
|
452
|
-
canvas.height,
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
_capturePortraitToLandscapeImage(canvas, video = this._IDVideo) {
|
|
457
|
-
const { videoHeight, videoWidth } = video;
|
|
458
|
-
const cropWidth = videoWidth;
|
|
459
|
-
const cropHeight = (videoWidth * 9) / 16; // convert to landscape aspect ratio
|
|
460
|
-
const startX = 0;
|
|
461
|
-
const startY = (videoHeight - cropHeight) / 2;
|
|
462
|
-
|
|
463
|
-
canvas.width = cropWidth;
|
|
464
|
-
canvas.height = cropHeight;
|
|
465
|
-
|
|
466
|
-
canvas
|
|
467
|
-
.getContext('2d')
|
|
468
|
-
.drawImage(
|
|
469
|
-
video,
|
|
470
|
-
startX,
|
|
471
|
-
startY,
|
|
472
|
-
cropWidth,
|
|
473
|
-
cropHeight,
|
|
474
|
-
0,
|
|
475
|
-
0,
|
|
476
|
-
canvas.width,
|
|
477
|
-
canvas.height,
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
_drawLandscapeImageFromCanvas(
|
|
482
|
-
canvas,
|
|
483
|
-
sourceCanvas,
|
|
484
|
-
scaleHeight = documentCaptureScale,
|
|
485
|
-
scaleWidth = documentCaptureScale,
|
|
486
|
-
) {
|
|
487
|
-
const heightScaleFactor = this.height
|
|
488
|
-
? this.height / sourceCanvas.height
|
|
489
|
-
: scaleHeight;
|
|
490
|
-
const widthScaleFactor = this.width
|
|
491
|
-
? this.width / sourceCanvas.width
|
|
492
|
-
: scaleWidth;
|
|
493
|
-
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
494
|
-
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
495
|
-
const width = sourceCanvas.width * widthScaleFactor;
|
|
496
|
-
const height = sourceCanvas.height * heightScaleFactor;
|
|
497
|
-
const startX = sourceCanvas.width * scaleWidthOffset;
|
|
498
|
-
const startY = sourceCanvas.height * scaleHeightOffset;
|
|
499
|
-
|
|
500
|
-
canvas
|
|
501
|
-
.getContext('2d')
|
|
502
|
-
.drawImage(
|
|
503
|
-
sourceCanvas,
|
|
504
|
-
startX,
|
|
505
|
-
startY,
|
|
506
|
-
width,
|
|
507
|
-
height,
|
|
508
|
-
0,
|
|
509
|
-
0,
|
|
510
|
-
canvas.width,
|
|
511
|
-
canvas.height,
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
_drawPortraitToLandscapeImage(canvas, video = this._IDVideo) {
|
|
516
|
-
const { videoHeight, videoWidth } = video;
|
|
517
|
-
const cropWidth = 600;
|
|
518
|
-
const cropHeight = 400;
|
|
519
|
-
|
|
520
|
-
canvas.width = cropWidth;
|
|
521
|
-
canvas.height = cropHeight;
|
|
522
|
-
|
|
523
|
-
const startX = (videoWidth - cropWidth) / 2;
|
|
524
|
-
const startY = (videoHeight - cropHeight) / 2;
|
|
525
|
-
|
|
526
|
-
canvas
|
|
527
|
-
.getContext('2d')
|
|
528
|
-
.drawImage(
|
|
529
|
-
video,
|
|
530
|
-
startX,
|
|
531
|
-
startY,
|
|
532
|
-
cropWidth,
|
|
533
|
-
cropHeight,
|
|
534
|
-
0,
|
|
535
|
-
0,
|
|
536
|
-
canvas.width,
|
|
537
|
-
canvas.height,
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
updatePortraitId(
|
|
542
|
-
destinationCanvas,
|
|
543
|
-
video = this._IDVideo,
|
|
544
|
-
scaleHeight = documentCaptureScale,
|
|
545
|
-
scaleWidth = documentCaptureScale,
|
|
546
|
-
) {
|
|
547
|
-
const { videoWidth, videoHeight } = video;
|
|
548
|
-
|
|
549
|
-
if (videoWidth && videoHeight) {
|
|
550
|
-
const intermediateCanvas = document.createElement('canvas');
|
|
551
|
-
const aspectRatio = 9 / 16;
|
|
552
|
-
let cropWidth;
|
|
553
|
-
let cropHeight;
|
|
554
|
-
let offsetX;
|
|
555
|
-
let offsetY;
|
|
556
|
-
|
|
557
|
-
if (videoWidth / videoHeight > aspectRatio) {
|
|
558
|
-
// we scale the canvas to portrait aspect ratio
|
|
559
|
-
cropHeight = videoHeight;
|
|
560
|
-
cropWidth = cropHeight * aspectRatio;
|
|
561
|
-
offsetX = (videoWidth - cropWidth) / 2;
|
|
562
|
-
offsetY = 0;
|
|
563
|
-
} else {
|
|
564
|
-
// video already has portrait aspect ratio
|
|
565
|
-
cropWidth = videoWidth;
|
|
566
|
-
cropHeight = cropWidth;
|
|
567
|
-
offsetX = 0;
|
|
568
|
-
offsetY = 0;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
intermediateCanvas.height = cropHeight;
|
|
572
|
-
intermediateCanvas.width = cropWidth;
|
|
573
|
-
// draw the video frame onto the intermediate canvas
|
|
574
|
-
intermediateCanvas
|
|
575
|
-
.getContext('2d')
|
|
576
|
-
.drawImage(
|
|
577
|
-
video,
|
|
578
|
-
offsetX,
|
|
579
|
-
offsetY,
|
|
580
|
-
cropWidth,
|
|
581
|
-
cropHeight,
|
|
582
|
-
0,
|
|
583
|
-
0,
|
|
584
|
-
intermediateCanvas.width,
|
|
585
|
-
intermediateCanvas.height,
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
// draw the intermediate canvas onto the destination canvas
|
|
589
|
-
// we scale image based on the scaleHeight and scaleWidth
|
|
590
|
-
const heightScaleFactor = this.height
|
|
591
|
-
? this.height / cropWidth
|
|
592
|
-
: scaleHeight;
|
|
593
|
-
const widthScaleFactor = this.width
|
|
594
|
-
? this.width / cropHeight
|
|
595
|
-
: scaleWidth;
|
|
596
|
-
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
597
|
-
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
598
|
-
const width = cropWidth * widthScaleFactor;
|
|
599
|
-
const height = cropHeight * heightScaleFactor;
|
|
600
|
-
const startX = cropWidth * scaleWidthOffset;
|
|
601
|
-
const startY = cropHeight * scaleHeightOffset;
|
|
602
|
-
destinationCanvas
|
|
603
|
-
.getContext('2d')
|
|
604
|
-
.drawImage(
|
|
605
|
-
intermediateCanvas,
|
|
606
|
-
startX,
|
|
607
|
-
startY,
|
|
608
|
-
width,
|
|
609
|
-
height,
|
|
610
|
-
0,
|
|
611
|
-
0,
|
|
612
|
-
destinationCanvas.width,
|
|
613
|
-
destinationCanvas.height,
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
_stopIDVideoStream(stream = this._IDStream) {
|
|
619
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
setUpEventListeners() {
|
|
623
|
-
this.captureIDImage = this.shadowRoot.querySelector('#capture-id-image');
|
|
624
|
-
this.navigation = this.shadowRoot.querySelector('smileid-navigation');
|
|
625
|
-
|
|
626
|
-
if (SmartCamera.stream) {
|
|
627
|
-
this.handleIDStream(SmartCamera.stream);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
this.navigation.addEventListener('navigation.back', () => {
|
|
631
|
-
this.handleBackEvents();
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
this.navigation.addEventListener('navigation.close', () => {
|
|
635
|
-
this.handleCloseEvents();
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
this.captureIDImage.addEventListener('click', () => {
|
|
639
|
-
this._captureIDImage();
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
this.getUserMedia();
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
get hideBack() {
|
|
646
|
-
return this.hasAttribute('hide-back-to-host');
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
get showNavigation() {
|
|
650
|
-
return this.hasAttribute('show-navigation');
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
get themeColor() {
|
|
654
|
-
return this.getAttribute('theme-color') || '#001096';
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
get hideAttribution() {
|
|
658
|
-
return this.hasAttribute('hide-attribution');
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
get documentCaptureModes() {
|
|
662
|
-
return this.getAttribute('document-capture-modes') || 'camera';
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
get supportBothCaptureModes() {
|
|
666
|
-
const value = this.documentCaptureModes;
|
|
667
|
-
return value.includes('camera') && value.includes('upload');
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
get title() {
|
|
671
|
-
return (
|
|
672
|
-
this.getAttribute('title') ||
|
|
673
|
-
`${this.IdSides[this.sideOfId]} of ${this.documentName}`
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
get height() {
|
|
678
|
-
return this.getAttribute('height');
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
get width() {
|
|
682
|
-
return this.getAttribute('width');
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
get hidden() {
|
|
686
|
-
return this.getAttribute('hidden');
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
get sideOfId() {
|
|
690
|
-
return (this.getAttribute('side-of-id') || 'front').toLowerCase();
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
get isFrontOfId() {
|
|
694
|
-
return this.sideOfId === 'front';
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
get isBackOfId() {
|
|
698
|
-
return !this.isFrontOfId;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
get documentType() {
|
|
702
|
-
return this.getAttribute('document-type') || '';
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
get documentName() {
|
|
706
|
-
return this.getAttribute('document-name') || 'Document';
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
get isPortraitCaptureView() {
|
|
710
|
-
return this.getAttribute('document-type') === 'GREEN_BOOK';
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
get cameraError() {
|
|
714
|
-
return this.getAttribute('data-camera-error');
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
static get observedAttributes() {
|
|
718
|
-
return [
|
|
719
|
-
'data-camera-error',
|
|
720
|
-
'data-camera-ready',
|
|
721
|
-
'document-name',
|
|
722
|
-
'document-type',
|
|
723
|
-
'hidden',
|
|
724
|
-
'hide-back-to-host',
|
|
725
|
-
'show-navigation',
|
|
726
|
-
'title',
|
|
727
|
-
];
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
attributeChangedCallback(name) {
|
|
731
|
-
switch (name) {
|
|
732
|
-
case 'data-camera-error':
|
|
733
|
-
case 'data-camera-ready':
|
|
734
|
-
case 'document-name':
|
|
735
|
-
case 'document-type':
|
|
736
|
-
case 'hidden':
|
|
737
|
-
case 'title':
|
|
738
|
-
this.connectedCallback();
|
|
739
|
-
break;
|
|
740
|
-
default:
|
|
741
|
-
break;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
handleBackEvents() {
|
|
746
|
-
this.dispatchEvent(new CustomEvent('document-capture.cancelled'));
|
|
747
|
-
SmartCamera.stopMedia();
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
handleCloseEvents() {
|
|
751
|
-
this.dispatchEvent(new CustomEvent('document-capture.close'));
|
|
752
|
-
SmartCamera.stopMedia();
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
if ('customElements' in window && !customElements.get('document-capture')) {
|
|
757
|
-
window.customElements.define('document-capture', DocumentCapture);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
export default DocumentCapture;
|
|
1
|
+
import SmartCamera from '../../../../domain/camera/src/SmartCamera';
|
|
2
|
+
import styles from '../../../../styles/src/styles';
|
|
3
|
+
import '../../../navigation/src';
|
|
4
|
+
|
|
5
|
+
function hasMoreThanNColors(data, n = 16) {
|
|
6
|
+
const colors = new Set();
|
|
7
|
+
for (let i = 0; i < Math.min(data.length, 10000); i += 4) {
|
|
8
|
+
// eslint-disable-next-line no-bitwise
|
|
9
|
+
colors.add((data[i] << 16) | (data[i + 1] << 8) | data[i + 2]);
|
|
10
|
+
if (colors.size > n) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function templateString() {
|
|
18
|
+
return `
|
|
19
|
+
<style>
|
|
20
|
+
.visually-hidden {
|
|
21
|
+
border: 0;
|
|
22
|
+
clip: rect(1px 1px 1px 1px);
|
|
23
|
+
clip: rect(1px, 1px, 1px, 1px);
|
|
24
|
+
height: auto;
|
|
25
|
+
margin: 0;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
padding: 0;
|
|
28
|
+
position: absolute;
|
|
29
|
+
white-space: nowrap;
|
|
30
|
+
width: 1px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.mobile-camera-screen video {
|
|
34
|
+
display: block;
|
|
35
|
+
object-fit: cover;
|
|
36
|
+
object-position: center;
|
|
37
|
+
width: 100%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.id-video.mobile-camera-screen {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: stretch;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
max-height: 300px;
|
|
45
|
+
height: 15rem;
|
|
46
|
+
width: 100%;
|
|
47
|
+
overflow: visible;
|
|
48
|
+
margin: 0 auto;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@media (max-width: 600px) {
|
|
52
|
+
.section {
|
|
53
|
+
width: 99%;
|
|
54
|
+
height: 100vh;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
#document-capture-screen,
|
|
62
|
+
#back-of-document-capture-screen {
|
|
63
|
+
block-size: 45rem;
|
|
64
|
+
padding-block: 2rem;
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
max-block-size: 100%;
|
|
68
|
+
max-inline-size: 40ch;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#document-capture-screen header p {
|
|
72
|
+
margin-block: 0 !important;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.padding-bottom-2 {
|
|
76
|
+
padding-bottom: 2rem;
|
|
77
|
+
}
|
|
78
|
+
@media (min-width: 600px) {
|
|
79
|
+
video {
|
|
80
|
+
object-fit: contain;
|
|
81
|
+
-webkit-tap-highlight-color: transparent;
|
|
82
|
+
content: normal;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.id-video {
|
|
86
|
+
width: 99%;
|
|
87
|
+
text-align: center;
|
|
88
|
+
position: relative;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.id-video-container {
|
|
93
|
+
margin: auto;
|
|
94
|
+
padding: 0px;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
.id-video-container {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
align-items: center;
|
|
101
|
+
}
|
|
102
|
+
.id-video {
|
|
103
|
+
width: 100%;
|
|
104
|
+
text-align: center;
|
|
105
|
+
position: relative;
|
|
106
|
+
background: white;
|
|
107
|
+
}
|
|
108
|
+
.video-overlay {
|
|
109
|
+
position: absolute;
|
|
110
|
+
border-style: solid;
|
|
111
|
+
border-color: rgba(0, 0, 0, 0.48);
|
|
112
|
+
box-sizing: border-box;
|
|
113
|
+
inset: 0px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.video-overlay .inner-border {
|
|
117
|
+
position: absolute;
|
|
118
|
+
border-width: 0.25rem;
|
|
119
|
+
border-color: #9394ab;
|
|
120
|
+
border-style: solid;
|
|
121
|
+
border-radius: 0.25rem;
|
|
122
|
+
inset: -1px;
|
|
123
|
+
}
|
|
124
|
+
canvas {
|
|
125
|
+
border-width: 0.25rem;
|
|
126
|
+
border-color: #9394ab;
|
|
127
|
+
border-style: solid;
|
|
128
|
+
border-radius: 0.25rem;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.description {
|
|
132
|
+
align-self: center;
|
|
133
|
+
padding-bottom: 1.75rem;
|
|
134
|
+
}
|
|
135
|
+
.reset-margin-block {
|
|
136
|
+
margin-block: 0;
|
|
137
|
+
}
|
|
138
|
+
.align-items-center {
|
|
139
|
+
align-items: center;
|
|
140
|
+
}
|
|
141
|
+
.id-side {
|
|
142
|
+
padding-bottom: 0.5rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.circle-progress {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
height: 10rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.portrait .sticky {
|
|
154
|
+
position: -webkit-sticky; /* Safari */
|
|
155
|
+
position: sticky;
|
|
156
|
+
bottom: 0;
|
|
157
|
+
}
|
|
158
|
+
.video-footer {
|
|
159
|
+
background-color: rgba(255, 255, 255, 0.17);
|
|
160
|
+
padding-top: 10px;
|
|
161
|
+
}
|
|
162
|
+
</style>
|
|
163
|
+
${styles(this.themeColor)}
|
|
164
|
+
<div id='document-capture-screen' class='flow center flex-column'>
|
|
165
|
+
<smileid-navigation theme-color='${this.themeColor}' ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
|
|
166
|
+
<h2 class='text-base font-bold title-color'>${this.documentName}</h2>
|
|
167
|
+
<div class="circle-progress" id="loader">
|
|
168
|
+
${this.cameraError ? '' : '<p class="spinner"></p>'}
|
|
169
|
+
${this.cameraError ? `<p style="--flow-space: 4rem" class='color-red | center'>${this.cameraError}</p>` : '<p style="--flow-space: 4rem">Checking permissions</p>'}
|
|
170
|
+
</div>
|
|
171
|
+
<div class='section | flow ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' ${this.cameraError ? 'hidden' : ''}>
|
|
172
|
+
<div class='id-video-container'>
|
|
173
|
+
<div class='id-video ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' hidden>
|
|
174
|
+
</div>
|
|
175
|
+
<div class='video-footer sticky'>
|
|
176
|
+
<h2 class='text-base font-bold title-color reset-margin-block id-side'>${this.title}</h2>
|
|
177
|
+
<h4 class='text-base font-normal title-color description reset-margin-block'>Make sure all corners are visible and there is no glare.</h4>
|
|
178
|
+
<div class='actions' hidden>
|
|
179
|
+
<button id='capture-id-image' class='button icon-btn | center' type='button'>
|
|
180
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="70" viewBox="0 0 70 70" fill="none" aria-hidden="true" focusable="false">
|
|
181
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M35 70C54.33 70 70 54.33 70 35C70 15.67 54.33 0 35 0C15.67 0 0 15.67 0 35C0 54.33 15.67 70 35 70ZM61 35C61 49.3594 49.3594 61 35 61C20.6406 61 9 49.3594 9 35C9 20.6406 20.6406 9 35 9C49.3594 9 61 20.6406 61 35ZM65 35C65 51.5685 51.5685 65 35 65C18.4315 65 5 51.5685 5 35C5 18.4315 18.4315 5 35 5C51.5685 5 65 18.4315 65 35Z" fill="${this.themeColor}"/>
|
|
182
|
+
</svg>
|
|
183
|
+
<span class='visually-hidden'>Capture Document</span>
|
|
184
|
+
</button>
|
|
185
|
+
</div>
|
|
186
|
+
${this.hideAttribution ? '' : '<powered-by-smile-id></powered-by-smile-id>'}
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const documentCaptureScale = 0.6;
|
|
195
|
+
|
|
196
|
+
class DocumentCapture extends HTMLElement {
|
|
197
|
+
constructor() {
|
|
198
|
+
super();
|
|
199
|
+
this.templateString = templateString.bind(this);
|
|
200
|
+
this.render = () => this.templateString();
|
|
201
|
+
|
|
202
|
+
this.attachShadow({ mode: 'open' });
|
|
203
|
+
this.IdSides = {
|
|
204
|
+
back: 'Back',
|
|
205
|
+
front: 'Front',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
connectedCallback() {
|
|
210
|
+
const template = document.createElement('template');
|
|
211
|
+
template.innerHTML = this.render();
|
|
212
|
+
this.shadowRoot.innerHTML = '';
|
|
213
|
+
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
|
214
|
+
this.setUpEventListeners();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async getUserMedia() {
|
|
218
|
+
if (SmartCamera.stream) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (!this.hasAttribute('data-camera-error')) return;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await SmartCamera.getMedia({
|
|
225
|
+
audio: false,
|
|
226
|
+
video: {
|
|
227
|
+
...SmartCamera.environmentOptions,
|
|
228
|
+
aspectRatio: { ideal: 16 / 9 },
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error(error.constraint);
|
|
233
|
+
console.error(error.message);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.handleIDStream(SmartCamera.stream);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
_captureIDImage() {
|
|
240
|
+
const imageDetails = this._drawIDImage();
|
|
241
|
+
this._stopIDVideoStream();
|
|
242
|
+
|
|
243
|
+
this.dispatchEvent(
|
|
244
|
+
new CustomEvent('document-capture.publish', {
|
|
245
|
+
detail: {
|
|
246
|
+
...imageDetails,
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_drawIDImage(video = this._IDVideo) {
|
|
253
|
+
const canvas = document.createElement('canvas');
|
|
254
|
+
if (this.isPortraitCaptureView) {
|
|
255
|
+
canvas.width = video.videoWidth;
|
|
256
|
+
canvas.height = (canvas.width * 16) / 9;
|
|
257
|
+
|
|
258
|
+
const previewCanvas = document.createElement('canvas');
|
|
259
|
+
previewCanvas.width = canvas.width;
|
|
260
|
+
previewCanvas.height = canvas.height;
|
|
261
|
+
|
|
262
|
+
this.updatePortraitId(canvas, video, 1, 1);
|
|
263
|
+
this.updatePortraitId(previewCanvas, video);
|
|
264
|
+
const image = canvas.toDataURL('image/jpeg');
|
|
265
|
+
const previewImage = previewCanvas.toDataURL('image/jpeg');
|
|
266
|
+
return {
|
|
267
|
+
image,
|
|
268
|
+
originalHeight: canvas.height,
|
|
269
|
+
originalWidth: canvas.width,
|
|
270
|
+
previewImage,
|
|
271
|
+
...this.idCardRegion,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
canvas.width = 2240;
|
|
276
|
+
canvas.height = 1260;
|
|
277
|
+
|
|
278
|
+
const height = canvas.width / (video.videoWidth / video.videoHeight);
|
|
279
|
+
canvas.height = height;
|
|
280
|
+
|
|
281
|
+
const previewCanvas = document.createElement('canvas');
|
|
282
|
+
previewCanvas.height = canvas.height;
|
|
283
|
+
previewCanvas.width = canvas.width;
|
|
284
|
+
const isPortrait = video.videoWidth < video.videoHeight;
|
|
285
|
+
if (isPortrait) {
|
|
286
|
+
const intermediateCanvas = document.createElement('canvas');
|
|
287
|
+
previewCanvas.height = canvas.width / 1.75;
|
|
288
|
+
canvas.width = 2240;
|
|
289
|
+
canvas.height = canvas.width / 1.77;
|
|
290
|
+
this._capturePortraitToLandscapeImage(intermediateCanvas, video);
|
|
291
|
+
this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas, 1, 1);
|
|
292
|
+
this._drawLandscapeImageFromCanvas(previewCanvas, intermediateCanvas);
|
|
293
|
+
} else {
|
|
294
|
+
this._drawLandscapeImage(canvas, video, 1, 1);
|
|
295
|
+
this._drawLandscapeImage(previewCanvas, video);
|
|
296
|
+
}
|
|
297
|
+
const image = canvas.toDataURL('image/jpeg');
|
|
298
|
+
|
|
299
|
+
const previewImage = previewCanvas.toDataURL('image/jpeg');
|
|
300
|
+
return {
|
|
301
|
+
image,
|
|
302
|
+
originalHeight: canvas.height,
|
|
303
|
+
originalWidth: canvas.width,
|
|
304
|
+
previewImage,
|
|
305
|
+
...this.idCardRegion,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
_drawImage(canvas, enableImageTests = true, video = SmartCamera.stream) {
|
|
310
|
+
this.resetErrorMessage();
|
|
311
|
+
const context = canvas.getContext('2d');
|
|
312
|
+
|
|
313
|
+
context.drawImage(
|
|
314
|
+
video,
|
|
315
|
+
0,
|
|
316
|
+
0,
|
|
317
|
+
video.videoWidth,
|
|
318
|
+
video.videoHeight,
|
|
319
|
+
0,
|
|
320
|
+
0,
|
|
321
|
+
canvas.width,
|
|
322
|
+
canvas.height,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
if (enableImageTests) {
|
|
326
|
+
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
327
|
+
|
|
328
|
+
const hasEnoughColors = hasMoreThanNColors(imageData.data);
|
|
329
|
+
|
|
330
|
+
if (hasEnoughColors) {
|
|
331
|
+
return context;
|
|
332
|
+
}
|
|
333
|
+
throw new Error(
|
|
334
|
+
'Unable to capture webcam images - Please try another device',
|
|
335
|
+
);
|
|
336
|
+
} else {
|
|
337
|
+
return context;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
handleIDStream(stream) {
|
|
342
|
+
try {
|
|
343
|
+
const videoExists = this.shadowRoot.querySelector('canvas');
|
|
344
|
+
if (videoExists) {
|
|
345
|
+
// remove canvas
|
|
346
|
+
videoExists.remove();
|
|
347
|
+
}
|
|
348
|
+
let video = null;
|
|
349
|
+
let canvas = null;
|
|
350
|
+
video = document.createElement('video');
|
|
351
|
+
canvas = document.createElement('canvas');
|
|
352
|
+
const videoContainer = this.shadowRoot.querySelector(
|
|
353
|
+
'.id-video-container',
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
video.muted = true;
|
|
357
|
+
video.setAttribute('muted', 'true');
|
|
358
|
+
|
|
359
|
+
video.autoplay = true;
|
|
360
|
+
video.playsInline = true;
|
|
361
|
+
if ('srcObject' in video) {
|
|
362
|
+
video.srcObject = stream;
|
|
363
|
+
} else {
|
|
364
|
+
video.src = window.URL.createObjectURL(stream);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
canvas.width = videoContainer.clientWidth;
|
|
368
|
+
canvas.height = (videoContainer.clientWidth * 9) / 16;
|
|
369
|
+
if (this.isPortraitCaptureView) {
|
|
370
|
+
canvas.height = (videoContainer.clientWidth * 16) / 9;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
video.onloadedmetadata = () => {
|
|
374
|
+
video.play();
|
|
375
|
+
|
|
376
|
+
this.shadowRoot.querySelector('#loader').hidden = true;
|
|
377
|
+
this.shadowRoot.querySelector('.id-video').hidden = false;
|
|
378
|
+
this.shadowRoot.querySelector('.actions').hidden = false;
|
|
379
|
+
if (!videoExists) {
|
|
380
|
+
videoContainer.prepend(canvas);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const onVideoStart = () => {
|
|
385
|
+
if (video.paused || video.ended) return;
|
|
386
|
+
video.removeEventListener('playing', onVideoStart);
|
|
387
|
+
const aspectRatio = video.videoWidth / video.videoHeight;
|
|
388
|
+
const portrait = aspectRatio < 1;
|
|
389
|
+
if (this.isPortraitCaptureView) {
|
|
390
|
+
this.updatePortraitId(canvas, video);
|
|
391
|
+
requestAnimationFrame(onVideoStart);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (portrait) {
|
|
396
|
+
videoContainer.classList.add('mobile-camera-screen');
|
|
397
|
+
const intermediateCanvas = document.createElement('canvas');
|
|
398
|
+
this._capturePortraitToLandscapeImage(intermediateCanvas, video);
|
|
399
|
+
this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas);
|
|
400
|
+
} else {
|
|
401
|
+
this._drawLandscapeImage(canvas, video);
|
|
402
|
+
}
|
|
403
|
+
requestAnimationFrame(onVideoStart);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
video.addEventListener('playing', onVideoStart);
|
|
407
|
+
|
|
408
|
+
this._IDStream = stream;
|
|
409
|
+
this._IDVideo = video;
|
|
410
|
+
} catch (error) {
|
|
411
|
+
this.setAttribute(
|
|
412
|
+
'data-camera-error',
|
|
413
|
+
SmartCamera.handleCameraError(error),
|
|
414
|
+
);
|
|
415
|
+
if (error.name !== 'AbortError') {
|
|
416
|
+
console.error(error);
|
|
417
|
+
}
|
|
418
|
+
SmartCamera.stopMedia();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
_drawLandscapeImage(
|
|
423
|
+
canvas,
|
|
424
|
+
video = this._IDVideo,
|
|
425
|
+
scaleHeight = documentCaptureScale,
|
|
426
|
+
scaleWidth = documentCaptureScale,
|
|
427
|
+
) {
|
|
428
|
+
const heightScaleFactor = this.height
|
|
429
|
+
? this.height / video.videoHeight
|
|
430
|
+
: scaleHeight;
|
|
431
|
+
const widthScaleFactor = this.width
|
|
432
|
+
? this.width / video.videoWidth
|
|
433
|
+
: scaleWidth;
|
|
434
|
+
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
435
|
+
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
436
|
+
const width = video.videoWidth * widthScaleFactor;
|
|
437
|
+
const height = video.videoHeight * heightScaleFactor;
|
|
438
|
+
const startX = video.videoWidth * scaleWidthOffset;
|
|
439
|
+
const startY = video.videoHeight * scaleHeightOffset;
|
|
440
|
+
|
|
441
|
+
canvas
|
|
442
|
+
.getContext('2d')
|
|
443
|
+
.drawImage(
|
|
444
|
+
video,
|
|
445
|
+
startX,
|
|
446
|
+
startY,
|
|
447
|
+
width,
|
|
448
|
+
height,
|
|
449
|
+
0,
|
|
450
|
+
0,
|
|
451
|
+
canvas.width,
|
|
452
|
+
canvas.height,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
_capturePortraitToLandscapeImage(canvas, video = this._IDVideo) {
|
|
457
|
+
const { videoHeight, videoWidth } = video;
|
|
458
|
+
const cropWidth = videoWidth;
|
|
459
|
+
const cropHeight = (videoWidth * 9) / 16; // convert to landscape aspect ratio
|
|
460
|
+
const startX = 0;
|
|
461
|
+
const startY = (videoHeight - cropHeight) / 2;
|
|
462
|
+
|
|
463
|
+
canvas.width = cropWidth;
|
|
464
|
+
canvas.height = cropHeight;
|
|
465
|
+
|
|
466
|
+
canvas
|
|
467
|
+
.getContext('2d')
|
|
468
|
+
.drawImage(
|
|
469
|
+
video,
|
|
470
|
+
startX,
|
|
471
|
+
startY,
|
|
472
|
+
cropWidth,
|
|
473
|
+
cropHeight,
|
|
474
|
+
0,
|
|
475
|
+
0,
|
|
476
|
+
canvas.width,
|
|
477
|
+
canvas.height,
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
_drawLandscapeImageFromCanvas(
|
|
482
|
+
canvas,
|
|
483
|
+
sourceCanvas,
|
|
484
|
+
scaleHeight = documentCaptureScale,
|
|
485
|
+
scaleWidth = documentCaptureScale,
|
|
486
|
+
) {
|
|
487
|
+
const heightScaleFactor = this.height
|
|
488
|
+
? this.height / sourceCanvas.height
|
|
489
|
+
: scaleHeight;
|
|
490
|
+
const widthScaleFactor = this.width
|
|
491
|
+
? this.width / sourceCanvas.width
|
|
492
|
+
: scaleWidth;
|
|
493
|
+
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
494
|
+
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
495
|
+
const width = sourceCanvas.width * widthScaleFactor;
|
|
496
|
+
const height = sourceCanvas.height * heightScaleFactor;
|
|
497
|
+
const startX = sourceCanvas.width * scaleWidthOffset;
|
|
498
|
+
const startY = sourceCanvas.height * scaleHeightOffset;
|
|
499
|
+
|
|
500
|
+
canvas
|
|
501
|
+
.getContext('2d')
|
|
502
|
+
.drawImage(
|
|
503
|
+
sourceCanvas,
|
|
504
|
+
startX,
|
|
505
|
+
startY,
|
|
506
|
+
width,
|
|
507
|
+
height,
|
|
508
|
+
0,
|
|
509
|
+
0,
|
|
510
|
+
canvas.width,
|
|
511
|
+
canvas.height,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
_drawPortraitToLandscapeImage(canvas, video = this._IDVideo) {
|
|
516
|
+
const { videoHeight, videoWidth } = video;
|
|
517
|
+
const cropWidth = 600;
|
|
518
|
+
const cropHeight = 400;
|
|
519
|
+
|
|
520
|
+
canvas.width = cropWidth;
|
|
521
|
+
canvas.height = cropHeight;
|
|
522
|
+
|
|
523
|
+
const startX = (videoWidth - cropWidth) / 2;
|
|
524
|
+
const startY = (videoHeight - cropHeight) / 2;
|
|
525
|
+
|
|
526
|
+
canvas
|
|
527
|
+
.getContext('2d')
|
|
528
|
+
.drawImage(
|
|
529
|
+
video,
|
|
530
|
+
startX,
|
|
531
|
+
startY,
|
|
532
|
+
cropWidth,
|
|
533
|
+
cropHeight,
|
|
534
|
+
0,
|
|
535
|
+
0,
|
|
536
|
+
canvas.width,
|
|
537
|
+
canvas.height,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
updatePortraitId(
|
|
542
|
+
destinationCanvas,
|
|
543
|
+
video = this._IDVideo,
|
|
544
|
+
scaleHeight = documentCaptureScale,
|
|
545
|
+
scaleWidth = documentCaptureScale,
|
|
546
|
+
) {
|
|
547
|
+
const { videoWidth, videoHeight } = video;
|
|
548
|
+
|
|
549
|
+
if (videoWidth && videoHeight) {
|
|
550
|
+
const intermediateCanvas = document.createElement('canvas');
|
|
551
|
+
const aspectRatio = 9 / 16;
|
|
552
|
+
let cropWidth;
|
|
553
|
+
let cropHeight;
|
|
554
|
+
let offsetX;
|
|
555
|
+
let offsetY;
|
|
556
|
+
|
|
557
|
+
if (videoWidth / videoHeight > aspectRatio) {
|
|
558
|
+
// we scale the canvas to portrait aspect ratio
|
|
559
|
+
cropHeight = videoHeight;
|
|
560
|
+
cropWidth = cropHeight * aspectRatio;
|
|
561
|
+
offsetX = (videoWidth - cropWidth) / 2;
|
|
562
|
+
offsetY = 0;
|
|
563
|
+
} else {
|
|
564
|
+
// video already has portrait aspect ratio
|
|
565
|
+
cropWidth = videoWidth;
|
|
566
|
+
cropHeight = cropWidth;
|
|
567
|
+
offsetX = 0;
|
|
568
|
+
offsetY = 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
intermediateCanvas.height = cropHeight;
|
|
572
|
+
intermediateCanvas.width = cropWidth;
|
|
573
|
+
// draw the video frame onto the intermediate canvas
|
|
574
|
+
intermediateCanvas
|
|
575
|
+
.getContext('2d')
|
|
576
|
+
.drawImage(
|
|
577
|
+
video,
|
|
578
|
+
offsetX,
|
|
579
|
+
offsetY,
|
|
580
|
+
cropWidth,
|
|
581
|
+
cropHeight,
|
|
582
|
+
0,
|
|
583
|
+
0,
|
|
584
|
+
intermediateCanvas.width,
|
|
585
|
+
intermediateCanvas.height,
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
// draw the intermediate canvas onto the destination canvas
|
|
589
|
+
// we scale image based on the scaleHeight and scaleWidth
|
|
590
|
+
const heightScaleFactor = this.height
|
|
591
|
+
? this.height / cropWidth
|
|
592
|
+
: scaleHeight;
|
|
593
|
+
const widthScaleFactor = this.width
|
|
594
|
+
? this.width / cropHeight
|
|
595
|
+
: scaleWidth;
|
|
596
|
+
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
597
|
+
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
598
|
+
const width = cropWidth * widthScaleFactor;
|
|
599
|
+
const height = cropHeight * heightScaleFactor;
|
|
600
|
+
const startX = cropWidth * scaleWidthOffset;
|
|
601
|
+
const startY = cropHeight * scaleHeightOffset;
|
|
602
|
+
destinationCanvas
|
|
603
|
+
.getContext('2d')
|
|
604
|
+
.drawImage(
|
|
605
|
+
intermediateCanvas,
|
|
606
|
+
startX,
|
|
607
|
+
startY,
|
|
608
|
+
width,
|
|
609
|
+
height,
|
|
610
|
+
0,
|
|
611
|
+
0,
|
|
612
|
+
destinationCanvas.width,
|
|
613
|
+
destinationCanvas.height,
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
_stopIDVideoStream(stream = this._IDStream) {
|
|
619
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
setUpEventListeners() {
|
|
623
|
+
this.captureIDImage = this.shadowRoot.querySelector('#capture-id-image');
|
|
624
|
+
this.navigation = this.shadowRoot.querySelector('smileid-navigation');
|
|
625
|
+
|
|
626
|
+
if (SmartCamera.stream) {
|
|
627
|
+
this.handleIDStream(SmartCamera.stream);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
this.navigation.addEventListener('navigation.back', () => {
|
|
631
|
+
this.handleBackEvents();
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
this.navigation.addEventListener('navigation.close', () => {
|
|
635
|
+
this.handleCloseEvents();
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
this.captureIDImage.addEventListener('click', () => {
|
|
639
|
+
this._captureIDImage();
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
this.getUserMedia();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
get hideBack() {
|
|
646
|
+
return this.hasAttribute('hide-back-to-host');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
get showNavigation() {
|
|
650
|
+
return this.hasAttribute('show-navigation');
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
get themeColor() {
|
|
654
|
+
return this.getAttribute('theme-color') || '#001096';
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
get hideAttribution() {
|
|
658
|
+
return this.hasAttribute('hide-attribution');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
get documentCaptureModes() {
|
|
662
|
+
return this.getAttribute('document-capture-modes') || 'camera';
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
get supportBothCaptureModes() {
|
|
666
|
+
const value = this.documentCaptureModes;
|
|
667
|
+
return value.includes('camera') && value.includes('upload');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
get title() {
|
|
671
|
+
return (
|
|
672
|
+
this.getAttribute('title') ||
|
|
673
|
+
`${this.IdSides[this.sideOfId]} of ${this.documentName}`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
get height() {
|
|
678
|
+
return this.getAttribute('height');
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
get width() {
|
|
682
|
+
return this.getAttribute('width');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
get hidden() {
|
|
686
|
+
return this.getAttribute('hidden');
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
get sideOfId() {
|
|
690
|
+
return (this.getAttribute('side-of-id') || 'front').toLowerCase();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
get isFrontOfId() {
|
|
694
|
+
return this.sideOfId === 'front';
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
get isBackOfId() {
|
|
698
|
+
return !this.isFrontOfId;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
get documentType() {
|
|
702
|
+
return this.getAttribute('document-type') || '';
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
get documentName() {
|
|
706
|
+
return this.getAttribute('document-name') || 'Document';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
get isPortraitCaptureView() {
|
|
710
|
+
return this.getAttribute('document-type') === 'GREEN_BOOK';
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
get cameraError() {
|
|
714
|
+
return this.getAttribute('data-camera-error');
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
static get observedAttributes() {
|
|
718
|
+
return [
|
|
719
|
+
'data-camera-error',
|
|
720
|
+
'data-camera-ready',
|
|
721
|
+
'document-name',
|
|
722
|
+
'document-type',
|
|
723
|
+
'hidden',
|
|
724
|
+
'hide-back-to-host',
|
|
725
|
+
'show-navigation',
|
|
726
|
+
'title',
|
|
727
|
+
];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
attributeChangedCallback(name) {
|
|
731
|
+
switch (name) {
|
|
732
|
+
case 'data-camera-error':
|
|
733
|
+
case 'data-camera-ready':
|
|
734
|
+
case 'document-name':
|
|
735
|
+
case 'document-type':
|
|
736
|
+
case 'hidden':
|
|
737
|
+
case 'title':
|
|
738
|
+
this.connectedCallback();
|
|
739
|
+
break;
|
|
740
|
+
default:
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
handleBackEvents() {
|
|
746
|
+
this.dispatchEvent(new CustomEvent('document-capture.cancelled'));
|
|
747
|
+
SmartCamera.stopMedia();
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
handleCloseEvents() {
|
|
751
|
+
this.dispatchEvent(new CustomEvent('document-capture.close'));
|
|
752
|
+
SmartCamera.stopMedia();
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if ('customElements' in window && !customElements.get('document-capture')) {
|
|
757
|
+
window.customElements.define('document-capture', DocumentCapture);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export default DocumentCapture;
|