@smileid/web-components 1.0.0-beta → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/components/README.md +1 -1
- package/components/camera-permission/CameraPermission.js +6 -2
- package/components/camera-permission/CameraPermission.stories.js +10 -4
- package/components/document/src/DocumentCaptureScreens.js +41 -11
- package/components/document/src/DocumentCaptureScreens.stories.js +16 -12
- package/components/document/src/README.md +11 -8
- package/components/document/src/document-capture/DocumentCapture.js +299 -231
- package/components/document/src/document-capture/DocumentCapture.stories.js +9 -2
- package/components/document/src/document-capture/README.md +5 -4
- package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +33 -33
- package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +8 -1
- package/components/document/src/document-capture-review/DocumentCaptureReview.js +15 -31
- package/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +12 -2
- package/components/document/src/document-capture-review/README.md +3 -3
- package/components/end-user-consent/src/EndUserConsent.js +14 -31
- package/components/end-user-consent/src/EndUserConsent.stories.js +8 -2
- package/components/navigation/src/Navigation.js +10 -2
- package/components/selfie/README.md +28 -4
- package/components/selfie/src/SelfieCaptureScreens.js +36 -10
- package/components/selfie/src/SelfieCaptureScreens.stories.js +13 -4
- package/components/selfie/src/selfie-capture/SelfieCapture.js +95 -23
- package/components/selfie/src/selfie-capture/SelfieCapture.stories.js +14 -1
- package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +50 -44
- package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +8 -2
- package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +3 -4
- package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +8 -1
- package/components/signature-pad/package-lock.json +3009 -0
- package/components/signature-pad/package.json +6 -6
- package/components/signature-pad/src/SignaturePad.js +5 -1
- package/components/signature-pad/src/SignaturePad.stories.js +10 -2
- package/components/smart-camera-web/src/README.md +207 -0
- package/components/smart-camera-web/src/SmartCameraWeb.js +56 -7
- package/components/smart-camera-web/src/SmartCameraWeb.stories.js +27 -7
- package/components/totp-consent/src/TotpConsent.js +15 -3
- package/cypress/e2e/smart-camera-web-agent-mode.cy.js +144 -0
- package/cypress/e2e/smart-camera-web-complete-flow.cy.js +221 -0
- package/cypress/e2e/smart-camera-web.cy.js +1 -1
- package/cypress/pages/smart-camera-web-agent-mode.html +36 -0
- package/cypress/pages/smart-camera-web-complete-flow.html +42 -0
- package/cypress/pages/smart-camera-web.html +1 -1
- package/domain/camera/src/SmartCamera.js +28 -0
- package/package.json +8 -8
- package/styles/src/styles.js +14 -3
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import SmartCamera from '../../../../domain/camera/src/SmartCamera';
|
|
2
2
|
import styles from '../../../../styles/src/styles';
|
|
3
|
-
import {
|
|
4
|
-
PORTRAIT_ID_PREVIEW_HEIGHT,
|
|
5
|
-
PORTRAIT_ID_PREVIEW_WIDTH,
|
|
6
|
-
} from '../../../../domain/constants/src/Constants';
|
|
7
3
|
import '../../../navigation/src';
|
|
8
4
|
|
|
9
5
|
function hasMoreThanNColors(data, n = 16) {
|
|
@@ -20,7 +16,6 @@ function hasMoreThanNColors(data, n = 16) {
|
|
|
20
16
|
|
|
21
17
|
function templateString() {
|
|
22
18
|
return `
|
|
23
|
-
${styles}
|
|
24
19
|
<style>
|
|
25
20
|
.visually-hidden {
|
|
26
21
|
border: 0;
|
|
@@ -42,12 +37,12 @@ function templateString() {
|
|
|
42
37
|
width: 100%;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
.id-video.mobile-camera-screen {
|
|
46
41
|
display: flex;
|
|
47
42
|
align-items: stretch;
|
|
48
43
|
justify-content: center;
|
|
49
|
-
max-height:
|
|
50
|
-
height:
|
|
44
|
+
max-height: 300px;
|
|
45
|
+
height: 15rem;
|
|
51
46
|
width: 100%;
|
|
52
47
|
overflow: visible;
|
|
53
48
|
margin: 0 auto;
|
|
@@ -55,7 +50,7 @@ function templateString() {
|
|
|
55
50
|
|
|
56
51
|
@media (max-width: 600px) {
|
|
57
52
|
.section {
|
|
58
|
-
width:
|
|
53
|
+
width: 99%;
|
|
59
54
|
height: 100vh;
|
|
60
55
|
justify-content: center;
|
|
61
56
|
}
|
|
@@ -88,25 +83,22 @@ function templateString() {
|
|
|
88
83
|
}
|
|
89
84
|
|
|
90
85
|
.id-video {
|
|
91
|
-
width:
|
|
86
|
+
width: 99%;
|
|
92
87
|
text-align: center;
|
|
93
88
|
position: relative;
|
|
94
89
|
overflow: hidden;
|
|
95
90
|
}
|
|
96
|
-
|
|
97
|
-
position: absolute;
|
|
98
|
-
border-style: solid;
|
|
99
|
-
border-color: rgba(0, 0, 0, 0.48);
|
|
100
|
-
box-sizing: border-box;
|
|
101
|
-
inset: -1px;
|
|
102
|
-
}
|
|
103
|
-
|
|
91
|
+
|
|
104
92
|
.id-video-container {
|
|
105
93
|
margin: auto;
|
|
106
94
|
padding: 0px;
|
|
107
95
|
}
|
|
108
96
|
}
|
|
109
|
-
|
|
97
|
+
.id-video-container {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
align-items: center;
|
|
101
|
+
}
|
|
110
102
|
.id-video {
|
|
111
103
|
width: 100%;
|
|
112
104
|
text-align: center;
|
|
@@ -129,6 +121,12 @@ function templateString() {
|
|
|
129
121
|
border-radius: 0.25rem;
|
|
130
122
|
inset: -1px;
|
|
131
123
|
}
|
|
124
|
+
canvas {
|
|
125
|
+
border-width: 0.25rem;
|
|
126
|
+
border-color: #9394ab;
|
|
127
|
+
border-style: solid;
|
|
128
|
+
border-radius: 0.25rem;
|
|
129
|
+
}
|
|
132
130
|
|
|
133
131
|
.description {
|
|
134
132
|
align-self: center;
|
|
@@ -162,24 +160,25 @@ function templateString() {
|
|
|
162
160
|
padding-top: 10px;
|
|
163
161
|
}
|
|
164
162
|
</style>
|
|
163
|
+
${styles(this.themeColor)}
|
|
165
164
|
<div id='document-capture-screen' class='flow center flex-column'>
|
|
166
|
-
<smileid-navigation ${this.showNavigation ? 'show-navigation' : ''} ${this.hideBack ? 'hide-back' : ''}></smileid-navigation>
|
|
167
|
-
<h2 class='text-base font-bold color
|
|
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>
|
|
168
167
|
<div class="circle-progress" id="loader">
|
|
169
168
|
${this.cameraError ? '' : '<p class="spinner"></p>'}
|
|
170
169
|
${this.cameraError ? `<p style="--flow-space: 4rem" class='color-red | center'>${this.cameraError}</p>` : '<p style="--flow-space: 4rem">Checking permissions</p>'}
|
|
171
170
|
</div>
|
|
172
171
|
<div class='section | flow ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}'>
|
|
173
172
|
<div class='id-video-container'>
|
|
174
|
-
<div class='id-video ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' >
|
|
173
|
+
<div class='id-video ${this.isPortraitCaptureView ? 'portrait' : 'landscape'}' hidden>
|
|
175
174
|
</div>
|
|
176
175
|
<div class='video-footer sticky'>
|
|
177
|
-
<h2 class='text-base font-bold color
|
|
178
|
-
<h4 class='text-base font-normal color
|
|
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>
|
|
179
178
|
<div class='actions' hidden>
|
|
180
179
|
<button id='capture-id-image' class='button icon-btn | center' type='button'>
|
|
181
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">
|
|
182
|
-
<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="
|
|
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}"/>
|
|
183
182
|
</svg>
|
|
184
183
|
<span class='visually-hidden'>Capture Document</span>
|
|
185
184
|
</button>
|
|
@@ -192,7 +191,8 @@ function templateString() {
|
|
|
192
191
|
`;
|
|
193
192
|
}
|
|
194
193
|
|
|
195
|
-
const
|
|
194
|
+
const documentCaptureScale = 0.6;
|
|
195
|
+
|
|
196
196
|
class DocumentCapture extends HTMLElement {
|
|
197
197
|
constructor() {
|
|
198
198
|
super();
|
|
@@ -223,10 +223,14 @@ class DocumentCapture extends HTMLElement {
|
|
|
223
223
|
try {
|
|
224
224
|
await SmartCamera.getMedia({
|
|
225
225
|
audio: false,
|
|
226
|
-
video:
|
|
226
|
+
video: {
|
|
227
|
+
...SmartCamera.environmentOptions,
|
|
228
|
+
aspectRatio: { ideal: 16 / 9 },
|
|
229
|
+
},
|
|
227
230
|
});
|
|
228
231
|
} catch (error) {
|
|
229
232
|
console.error(error.constraint);
|
|
233
|
+
console.error(error.message);
|
|
230
234
|
}
|
|
231
235
|
|
|
232
236
|
this.handleIDStream(SmartCamera.stream);
|
|
@@ -249,67 +253,21 @@ class DocumentCapture extends HTMLElement {
|
|
|
249
253
|
const canvas = document.createElement('canvas');
|
|
250
254
|
if (this.isPortraitCaptureView) {
|
|
251
255
|
canvas.width = video.videoWidth;
|
|
252
|
-
canvas.height =
|
|
253
|
-
|
|
254
|
-
// Draw the video frame onto the canvas
|
|
255
|
-
const ctx = canvas.getContext('2d');
|
|
256
|
-
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
257
|
-
|
|
258
|
-
// Get the dimensions of the video preview frame
|
|
259
|
-
const previewWidth = PORTRAIT_ID_PREVIEW_WIDTH;
|
|
260
|
-
const previewHeight = PORTRAIT_ID_PREVIEW_HEIGHT;
|
|
261
|
-
|
|
262
|
-
// Define the padding value
|
|
263
|
-
const paddingPercent = 1.1; // 110% of the preview dimensions because we previously scaled the image, old value was 50%;
|
|
264
|
-
const paddedWidth = previewWidth * (1 + paddingPercent);
|
|
265
|
-
const paddedHeight = previewHeight * (1 + paddingPercent);
|
|
266
|
-
|
|
267
|
-
// Calculate the dimensions of the cropped image based on the padded preview frame dimensions
|
|
268
|
-
const cropWidth = paddedWidth;
|
|
269
|
-
const cropHeight = paddedHeight;
|
|
270
|
-
const cropLeft = (canvas.width - cropWidth) / 2;
|
|
271
|
-
const cropTop = (canvas.height - cropHeight) / 2;
|
|
272
|
-
|
|
273
|
-
// Create a new canvas element for the cropped image
|
|
274
|
-
const croppedCanvas = document.createElement('canvas');
|
|
275
|
-
croppedCanvas.width = cropWidth;
|
|
276
|
-
croppedCanvas.height = cropHeight;
|
|
277
|
-
|
|
278
|
-
// Draw the cropped image onto the new canvas
|
|
279
|
-
const croppedCtx = croppedCanvas.getContext('2d');
|
|
280
|
-
croppedCtx.drawImage(
|
|
281
|
-
canvas,
|
|
282
|
-
cropLeft,
|
|
283
|
-
cropTop,
|
|
284
|
-
cropWidth,
|
|
285
|
-
cropHeight,
|
|
286
|
-
0,
|
|
287
|
-
0,
|
|
288
|
-
cropWidth,
|
|
289
|
-
cropHeight,
|
|
290
|
-
);
|
|
256
|
+
canvas.height = (canvas.width * 16) / 9;
|
|
291
257
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const videoContainer = this.shadowRoot.querySelector(
|
|
296
|
-
'.id-video-container',
|
|
297
|
-
);
|
|
298
|
-
const oldCroppedImage = videoContainer.querySelector(
|
|
299
|
-
'image#preview-cropped-image',
|
|
300
|
-
);
|
|
301
|
-
if (oldCroppedImage) {
|
|
302
|
-
videoContainer.removeChild(oldCroppedImage);
|
|
303
|
-
}
|
|
304
|
-
const croppedImage = document.createElement('img');
|
|
305
|
-
croppedImage.id = 'preview-cropped-image';
|
|
306
|
-
croppedImage.src = image;
|
|
307
|
-
videoContainer.appendChild(croppedImage);
|
|
258
|
+
const previewCanvas = document.createElement('canvas');
|
|
259
|
+
previewCanvas.width = canvas.width;
|
|
260
|
+
previewCanvas.height = canvas.height;
|
|
308
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');
|
|
309
266
|
return {
|
|
310
|
-
image
|
|
267
|
+
image,
|
|
311
268
|
originalHeight: canvas.height,
|
|
312
269
|
originalWidth: canvas.width,
|
|
270
|
+
previewImage,
|
|
313
271
|
...this.idCardRegion,
|
|
314
272
|
};
|
|
315
273
|
}
|
|
@@ -317,63 +275,33 @@ class DocumentCapture extends HTMLElement {
|
|
|
317
275
|
canvas.width = 2240;
|
|
318
276
|
canvas.height = 1260;
|
|
319
277
|
|
|
320
|
-
const context = canvas.getContext('2d');
|
|
321
|
-
|
|
322
|
-
const { aspectRatio } = this._calculateVideoOffset(video);
|
|
323
|
-
|
|
324
|
-
if (aspectRatio < 1) {
|
|
325
|
-
canvas.width = video.videoWidth;
|
|
326
|
-
canvas.height = video.videoHeight;
|
|
327
|
-
|
|
328
|
-
// Draw the video frame onto the canvas
|
|
329
|
-
const ctx = canvas.getContext('2d');
|
|
330
|
-
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
331
|
-
|
|
332
|
-
const paddedWidth = canvas.width;
|
|
333
|
-
const paddedHeight = canvas.width / fixedAspectRatio;
|
|
334
|
-
|
|
335
|
-
// Calculate the dimensions of the cropped image based on the padded preview frame dimensions
|
|
336
|
-
const cropWidth = paddedWidth;
|
|
337
|
-
const cropHeight = paddedHeight;
|
|
338
|
-
const cropLeft = 0;
|
|
339
|
-
const cropTop = canvas.height / 2 - paddedHeight / 2;
|
|
340
|
-
|
|
341
|
-
// Create a new canvas element for the cropped image
|
|
342
|
-
const croppedCanvas = document.createElement('canvas');
|
|
343
|
-
croppedCanvas.width = cropWidth;
|
|
344
|
-
croppedCanvas.height = cropHeight;
|
|
345
|
-
|
|
346
|
-
// Draw the cropped image onto the new canvas
|
|
347
|
-
const croppedCtx = croppedCanvas.getContext('2d');
|
|
348
|
-
croppedCtx.drawImage(
|
|
349
|
-
canvas,
|
|
350
|
-
cropLeft,
|
|
351
|
-
cropTop,
|
|
352
|
-
cropWidth,
|
|
353
|
-
cropHeight,
|
|
354
|
-
0,
|
|
355
|
-
0,
|
|
356
|
-
cropWidth,
|
|
357
|
-
cropHeight,
|
|
358
|
-
);
|
|
359
|
-
const image = croppedCanvas.toDataURL('image/jpeg');
|
|
360
|
-
|
|
361
|
-
return {
|
|
362
|
-
image,
|
|
363
|
-
originalHeight: canvas.height,
|
|
364
|
-
originalWidth: canvas.width,
|
|
365
|
-
...this.idCardRegion,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
278
|
const height = canvas.width / (video.videoWidth / video.videoHeight);
|
|
370
279
|
canvas.height = height;
|
|
371
|
-
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
372
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');
|
|
373
300
|
return {
|
|
374
|
-
image
|
|
301
|
+
image,
|
|
375
302
|
originalHeight: canvas.height,
|
|
376
303
|
originalWidth: canvas.width,
|
|
304
|
+
previewImage,
|
|
377
305
|
...this.idCardRegion,
|
|
378
306
|
};
|
|
379
307
|
}
|
|
@@ -411,28 +339,19 @@ class DocumentCapture extends HTMLElement {
|
|
|
411
339
|
}
|
|
412
340
|
|
|
413
341
|
handleIDStream(stream) {
|
|
414
|
-
const videoExists = this.shadowRoot.querySelector('
|
|
415
|
-
let video = null;
|
|
342
|
+
const videoExists = this.shadowRoot.querySelector('canvas');
|
|
416
343
|
if (videoExists) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
video = document.createElement('video');
|
|
344
|
+
// remove canvas
|
|
345
|
+
videoExists.remove();
|
|
420
346
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
video.style.width = `${width}px`;
|
|
428
|
-
video.style.height = '0px';
|
|
429
|
-
video.style.display = 'block';
|
|
347
|
+
let video = null;
|
|
348
|
+
let canvas = null;
|
|
349
|
+
video = document.createElement('video');
|
|
350
|
+
canvas = document.createElement('canvas');
|
|
351
|
+
const videoContainer = this.shadowRoot.querySelector('.id-video-container');
|
|
352
|
+
|
|
430
353
|
video.muted = true;
|
|
431
354
|
video.setAttribute('muted', 'true');
|
|
432
|
-
if (this.isPortraitCaptureView) {
|
|
433
|
-
video.style.objectFit = 'cover';
|
|
434
|
-
video.style.scale = '1';
|
|
435
|
-
}
|
|
436
355
|
|
|
437
356
|
video.autoplay = true;
|
|
438
357
|
video.playsInline = true;
|
|
@@ -442,104 +361,245 @@ class DocumentCapture extends HTMLElement {
|
|
|
442
361
|
video.src = window.URL.createObjectURL(stream);
|
|
443
362
|
}
|
|
444
363
|
|
|
364
|
+
canvas.width = videoContainer.clientWidth;
|
|
365
|
+
canvas.height = (videoContainer.clientWidth * 9) / 16;
|
|
366
|
+
if (this.isPortraitCaptureView) {
|
|
367
|
+
canvas.height = (videoContainer.clientWidth * 16) / 9;
|
|
368
|
+
}
|
|
369
|
+
|
|
445
370
|
video.onloadedmetadata = () => {
|
|
446
371
|
video.play();
|
|
372
|
+
|
|
373
|
+
this.shadowRoot.querySelector('#loader').hidden = true;
|
|
374
|
+
this.shadowRoot.querySelector('.id-video').hidden = false;
|
|
375
|
+
this.shadowRoot.querySelector('.actions').hidden = false;
|
|
376
|
+
if (!videoExists) {
|
|
377
|
+
videoContainer.prepend(canvas);
|
|
378
|
+
}
|
|
447
379
|
};
|
|
448
380
|
|
|
449
381
|
const onVideoStart = () => {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
offsetWidth,
|
|
454
|
-
videoHeight,
|
|
455
|
-
videoWidth,
|
|
456
|
-
} = this._calculateVideoOffset(video);
|
|
382
|
+
if (video.paused || video.ended) return;
|
|
383
|
+
video.removeEventListener('playing', onVideoStart);
|
|
384
|
+
const aspectRatio = video.videoWidth / video.videoHeight;
|
|
457
385
|
const portrait = aspectRatio < 1;
|
|
386
|
+
if (this.isPortraitCaptureView) {
|
|
387
|
+
this.updatePortraitId(canvas, video);
|
|
388
|
+
requestAnimationFrame(onVideoStart);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
458
391
|
|
|
459
392
|
if (portrait) {
|
|
460
393
|
videoContainer.classList.add('mobile-camera-screen');
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const idCardRegionWidth = videoWidth - offsetWidth;
|
|
467
|
-
const idCardRegionHeight = videoHeight - offsetHeight;
|
|
468
|
-
|
|
469
|
-
const rightLeftBorderSize = 20;
|
|
470
|
-
const topBottomBorderSize = 20;
|
|
471
|
-
this.idCardRegion = {
|
|
472
|
-
height: idCardRegionHeight,
|
|
473
|
-
rightLeftBorderSize,
|
|
474
|
-
topBottomBorderSize,
|
|
475
|
-
width: idCardRegionWidth,
|
|
476
|
-
x: offsetWidth / 2,
|
|
477
|
-
y: offsetHeight / 2,
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
let videoOverlay = videoContainer.querySelector('.video-overlay');
|
|
481
|
-
if (videoOverlay) {
|
|
482
|
-
videoOverlay.remove();
|
|
394
|
+
const intermediateCanvas = document.createElement('canvas');
|
|
395
|
+
this._capturePortraitToLandscapeImage(intermediateCanvas, video);
|
|
396
|
+
this._drawLandscapeImageFromCanvas(canvas, intermediateCanvas);
|
|
397
|
+
} else {
|
|
398
|
+
this._drawLandscapeImage(canvas, video);
|
|
483
399
|
}
|
|
484
|
-
|
|
485
|
-
const shadeColor = 'white';
|
|
486
|
-
videoOverlay.classList.add('video-overlay');
|
|
487
|
-
|
|
488
|
-
videoOverlay.style.borderLeft = `${rightLeftBorderSize}px solid ${shadeColor}`;
|
|
489
|
-
videoOverlay.style.borderRight = `${rightLeftBorderSize}px solid ${shadeColor}`;
|
|
490
|
-
videoOverlay.style.borderTop = `${topBottomBorderSize}px solid ${shadeColor}`;
|
|
491
|
-
videoOverlay.style.borderBottom = `${topBottomBorderSize}px solid ${shadeColor}`;
|
|
492
|
-
videoOverlay.style.top = '0px';
|
|
493
|
-
videoOverlay.style.bottom = '0px';
|
|
494
|
-
videoOverlay.style.left = '0px';
|
|
495
|
-
videoOverlay.style.right = '0px';
|
|
496
|
-
videoOverlay.style.inset = '-1px';
|
|
497
|
-
|
|
498
|
-
const innerBorder = document.createElement('div');
|
|
499
|
-
innerBorder.classList.add('inner-border');
|
|
500
|
-
videoOverlay.appendChild(innerBorder);
|
|
501
|
-
videoContainer.appendChild(videoOverlay);
|
|
502
|
-
this.videoOverlay = videoOverlay;
|
|
503
|
-
this.shadowRoot.querySelector('#loader').hidden = true;
|
|
504
|
-
this.shadowRoot.querySelector('.id-video').hidden = false;
|
|
505
|
-
this.shadowRoot.querySelector('.actions').hidden = false;
|
|
506
|
-
video.removeEventListener('playing', onVideoStart);
|
|
400
|
+
requestAnimationFrame(onVideoStart);
|
|
507
401
|
};
|
|
508
402
|
|
|
509
403
|
video.addEventListener('playing', onVideoStart);
|
|
510
404
|
|
|
511
|
-
if (!videoExists) {
|
|
512
|
-
videoContainer.prepend(video);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
405
|
this._IDStream = stream;
|
|
516
406
|
this._IDVideo = video;
|
|
517
407
|
}
|
|
518
408
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
|
|
409
|
+
_drawLandscapeImage(
|
|
410
|
+
canvas,
|
|
411
|
+
video = this._IDVideo,
|
|
412
|
+
scaleHeight = documentCaptureScale,
|
|
413
|
+
scaleWidth = documentCaptureScale,
|
|
414
|
+
) {
|
|
415
|
+
const heightScaleFactor = this.height
|
|
416
|
+
? this.height / video.videoHeight
|
|
417
|
+
: scaleHeight;
|
|
418
|
+
const widthScaleFactor = this.width
|
|
419
|
+
? this.width / video.videoWidth
|
|
420
|
+
: scaleWidth;
|
|
421
|
+
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
422
|
+
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
423
|
+
const width = video.videoWidth * widthScaleFactor;
|
|
424
|
+
const height = video.videoHeight * heightScaleFactor;
|
|
425
|
+
const startX = video.videoWidth * scaleWidthOffset;
|
|
426
|
+
const startY = video.videoHeight * scaleHeightOffset;
|
|
427
|
+
|
|
428
|
+
canvas
|
|
429
|
+
.getContext('2d')
|
|
430
|
+
.drawImage(
|
|
431
|
+
video,
|
|
432
|
+
startX,
|
|
433
|
+
startY,
|
|
434
|
+
width,
|
|
435
|
+
height,
|
|
436
|
+
0,
|
|
437
|
+
0,
|
|
438
|
+
canvas.width,
|
|
439
|
+
canvas.height,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
530
442
|
|
|
531
|
-
|
|
532
|
-
const
|
|
443
|
+
_capturePortraitToLandscapeImage(canvas, video = this._IDVideo) {
|
|
444
|
+
const { videoHeight, videoWidth } = video;
|
|
445
|
+
const cropWidth = videoWidth;
|
|
446
|
+
const cropHeight = (videoWidth * 9) / 16; // convert to landscape aspect ratio
|
|
447
|
+
const startX = 0;
|
|
448
|
+
const startY = (videoHeight - cropHeight) / 2;
|
|
449
|
+
|
|
450
|
+
canvas.width = cropWidth;
|
|
451
|
+
canvas.height = cropHeight;
|
|
452
|
+
|
|
453
|
+
canvas
|
|
454
|
+
.getContext('2d')
|
|
455
|
+
.drawImage(
|
|
456
|
+
video,
|
|
457
|
+
startX,
|
|
458
|
+
startY,
|
|
459
|
+
cropWidth,
|
|
460
|
+
cropHeight,
|
|
461
|
+
0,
|
|
462
|
+
0,
|
|
463
|
+
canvas.width,
|
|
464
|
+
canvas.height,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
533
467
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
468
|
+
_drawLandscapeImageFromCanvas(
|
|
469
|
+
canvas,
|
|
470
|
+
sourceCanvas,
|
|
471
|
+
scaleHeight = documentCaptureScale,
|
|
472
|
+
scaleWidth = documentCaptureScale,
|
|
473
|
+
) {
|
|
474
|
+
const heightScaleFactor = this.height
|
|
475
|
+
? this.height / sourceCanvas.height
|
|
476
|
+
: scaleHeight;
|
|
477
|
+
const widthScaleFactor = this.width
|
|
478
|
+
? this.width / sourceCanvas.width
|
|
479
|
+
: scaleWidth;
|
|
480
|
+
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
481
|
+
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
482
|
+
const width = sourceCanvas.width * widthScaleFactor;
|
|
483
|
+
const height = sourceCanvas.height * heightScaleFactor;
|
|
484
|
+
const startX = sourceCanvas.width * scaleWidthOffset;
|
|
485
|
+
const startY = sourceCanvas.height * scaleHeightOffset;
|
|
486
|
+
|
|
487
|
+
canvas
|
|
488
|
+
.getContext('2d')
|
|
489
|
+
.drawImage(
|
|
490
|
+
sourceCanvas,
|
|
491
|
+
startX,
|
|
492
|
+
startY,
|
|
493
|
+
width,
|
|
494
|
+
height,
|
|
495
|
+
0,
|
|
496
|
+
0,
|
|
497
|
+
canvas.width,
|
|
498
|
+
canvas.height,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
_drawPortraitToLandscapeImage(canvas, video = this._IDVideo) {
|
|
503
|
+
const { videoHeight, videoWidth } = video;
|
|
504
|
+
const cropWidth = 600;
|
|
505
|
+
const cropHeight = 400;
|
|
506
|
+
|
|
507
|
+
canvas.width = cropWidth;
|
|
508
|
+
canvas.height = cropHeight;
|
|
509
|
+
|
|
510
|
+
const startX = (videoWidth - cropWidth) / 2;
|
|
511
|
+
const startY = (videoHeight - cropHeight) / 2;
|
|
512
|
+
|
|
513
|
+
canvas
|
|
514
|
+
.getContext('2d')
|
|
515
|
+
.drawImage(
|
|
516
|
+
video,
|
|
517
|
+
startX,
|
|
518
|
+
startY,
|
|
519
|
+
cropWidth,
|
|
520
|
+
cropHeight,
|
|
521
|
+
0,
|
|
522
|
+
0,
|
|
523
|
+
canvas.width,
|
|
524
|
+
canvas.height,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
updatePortraitId(
|
|
529
|
+
destinationCanvas,
|
|
530
|
+
video = this._IDVideo,
|
|
531
|
+
scaleHeight = documentCaptureScale,
|
|
532
|
+
scaleWidth = documentCaptureScale,
|
|
533
|
+
) {
|
|
534
|
+
const { videoWidth, videoHeight } = video;
|
|
535
|
+
|
|
536
|
+
if (videoWidth && videoHeight) {
|
|
537
|
+
const intermediateCanvas = document.createElement('canvas');
|
|
538
|
+
const aspectRatio = 9 / 16;
|
|
539
|
+
let cropWidth;
|
|
540
|
+
let cropHeight;
|
|
541
|
+
let offsetX;
|
|
542
|
+
let offsetY;
|
|
543
|
+
|
|
544
|
+
if (videoWidth / videoHeight > aspectRatio) {
|
|
545
|
+
// we scale the canvas to portrait aspect ratio
|
|
546
|
+
cropHeight = videoHeight;
|
|
547
|
+
cropWidth = cropHeight * aspectRatio;
|
|
548
|
+
offsetX = (videoWidth - cropWidth) / 2;
|
|
549
|
+
offsetY = 0;
|
|
550
|
+
} else {
|
|
551
|
+
// video already has portrait aspect ratio
|
|
552
|
+
cropWidth = videoWidth;
|
|
553
|
+
cropHeight = cropWidth;
|
|
554
|
+
offsetX = 0;
|
|
555
|
+
offsetY = 0;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
intermediateCanvas.height = cropHeight;
|
|
559
|
+
intermediateCanvas.width = cropWidth;
|
|
560
|
+
// draw the video frame onto the intermediate canvas
|
|
561
|
+
intermediateCanvas
|
|
562
|
+
.getContext('2d')
|
|
563
|
+
.drawImage(
|
|
564
|
+
video,
|
|
565
|
+
offsetX,
|
|
566
|
+
offsetY,
|
|
567
|
+
cropWidth,
|
|
568
|
+
cropHeight,
|
|
569
|
+
0,
|
|
570
|
+
0,
|
|
571
|
+
intermediateCanvas.width,
|
|
572
|
+
intermediateCanvas.height,
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
// draw the intermediate canvas onto the destination canvas
|
|
576
|
+
// we scale image based on the scaleHeight and scaleWidth
|
|
577
|
+
const heightScaleFactor = this.height
|
|
578
|
+
? this.height / cropWidth
|
|
579
|
+
: scaleHeight;
|
|
580
|
+
const widthScaleFactor = this.width
|
|
581
|
+
? this.width / cropHeight
|
|
582
|
+
: scaleWidth;
|
|
583
|
+
const scaleHeightOffset = (1 - scaleHeight) / 2;
|
|
584
|
+
const scaleWidthOffset = (1 - scaleWidth) / 2;
|
|
585
|
+
const width = cropWidth * widthScaleFactor;
|
|
586
|
+
const height = cropHeight * heightScaleFactor;
|
|
587
|
+
const startX = cropWidth * scaleWidthOffset;
|
|
588
|
+
const startY = cropHeight * scaleHeightOffset;
|
|
589
|
+
destinationCanvas
|
|
590
|
+
.getContext('2d')
|
|
591
|
+
.drawImage(
|
|
592
|
+
intermediateCanvas,
|
|
593
|
+
startX,
|
|
594
|
+
startY,
|
|
595
|
+
width,
|
|
596
|
+
height,
|
|
597
|
+
0,
|
|
598
|
+
0,
|
|
599
|
+
destinationCanvas.width,
|
|
600
|
+
destinationCanvas.height,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
543
603
|
}
|
|
544
604
|
|
|
545
605
|
_stopIDVideoStream(stream = this._IDStream) {
|
|
@@ -578,7 +638,7 @@ class DocumentCapture extends HTMLElement {
|
|
|
578
638
|
}
|
|
579
639
|
|
|
580
640
|
get themeColor() {
|
|
581
|
-
return this.getAttribute('theme-color') || '#
|
|
641
|
+
return this.getAttribute('theme-color') || '#001096';
|
|
582
642
|
}
|
|
583
643
|
|
|
584
644
|
get hideAttribution() {
|
|
@@ -601,6 +661,14 @@ class DocumentCapture extends HTMLElement {
|
|
|
601
661
|
);
|
|
602
662
|
}
|
|
603
663
|
|
|
664
|
+
get height() {
|
|
665
|
+
return this.getAttribute('height');
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
get width() {
|
|
669
|
+
return this.getAttribute('width');
|
|
670
|
+
}
|
|
671
|
+
|
|
604
672
|
get hidden() {
|
|
605
673
|
return this.getAttribute('hidden');
|
|
606
674
|
}
|