@smileid/web-components 1.0.1-beta → 1.4.3

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 (36) hide show
  1. package/components/camera-permission/CameraPermission.js +6 -2
  2. package/components/camera-permission/CameraPermission.stories.js +10 -4
  3. package/components/document/src/DocumentCaptureScreens.js +15 -9
  4. package/components/document/src/DocumentCaptureScreens.stories.js +16 -12
  5. package/components/document/src/document-capture/DocumentCapture.js +295 -217
  6. package/components/document/src/document-capture/DocumentCapture.stories.js +9 -2
  7. package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +33 -33
  8. package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +8 -1
  9. package/components/document/src/document-capture-review/DocumentCaptureReview.js +15 -31
  10. package/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +12 -2
  11. package/components/end-user-consent/src/EndUserConsent.js +14 -31
  12. package/components/end-user-consent/src/EndUserConsent.stories.js +8 -2
  13. package/components/navigation/src/Navigation.js +10 -2
  14. package/components/selfie/src/SelfieCaptureScreens.js +36 -10
  15. package/components/selfie/src/SelfieCaptureScreens.stories.js +13 -4
  16. package/components/selfie/src/selfie-capture/SelfieCapture.js +94 -23
  17. package/components/selfie/src/selfie-capture/SelfieCapture.stories.js +14 -1
  18. package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +50 -44
  19. package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +8 -1
  20. package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +3 -4
  21. package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +8 -1
  22. package/components/signature-pad/package-lock.json +3009 -0
  23. package/components/signature-pad/package.json +6 -6
  24. package/components/signature-pad/src/SignaturePad.js +5 -1
  25. package/components/signature-pad/src/SignaturePad.stories.js +10 -2
  26. package/components/smart-camera-web/src/SmartCameraWeb.js +53 -8
  27. package/components/smart-camera-web/src/SmartCameraWeb.stories.js +22 -9
  28. package/components/totp-consent/src/TotpConsent.js +19 -5
  29. package/cypress/e2e/smart-camera-web-agent-mode.cy.js +144 -0
  30. package/cypress/e2e/smart-camera-web-complete-flow.cy.js +221 -0
  31. package/cypress/e2e/smart-camera-web.cy.js +1 -1
  32. package/cypress/pages/smart-camera-web-agent-mode.html +36 -0
  33. package/cypress/pages/smart-camera-web-complete-flow.html +42 -0
  34. package/domain/camera/src/SmartCamera.js +28 -0
  35. package/package.json +8 -8
  36. 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
- .id-video.mobile-camera-screen {
40
+ .id-video.mobile-camera-screen {
46
41
  display: flex;
47
42
  align-items: stretch;
48
43
  justify-content: center;
49
- max-height: 200px;
50
- height: 10rem;
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: 100%;
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: 100%;
86
+ width: 99%;
92
87
  text-align: center;
93
88
  position: relative;
94
89
  overflow: hidden;
95
90
  }
96
- .video-overlay {
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-digital-blue'>${this.documentName}</h2>
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-digital-blue reset-margin-block id-side'>${this.title}</h2>
178
- <h4 class='text-base font-normal color-digital-blue description reset-margin-block'>Make sure all corners are visible and there is no glare.</h4>
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="#001096"/>
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 fixedAspectRatio = 1.53;
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: SmartCamera.environmentOptions,
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,49 +253,16 @@ 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 = video.videoHeight;
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 image = croppedCanvas.toDataURL('image/jpeg');
293
- const previewImage = image;
258
+ const previewCanvas = document.createElement('canvas');
259
+ previewCanvas.width = canvas.width;
260
+ previewCanvas.height = canvas.height;
294
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');
295
266
  return {
296
267
  image,
297
268
  originalHeight: canvas.height,
@@ -304,66 +275,33 @@ class DocumentCapture extends HTMLElement {
304
275
  canvas.width = 2240;
305
276
  canvas.height = 1260;
306
277
 
307
- const context = canvas.getContext('2d');
308
-
309
- const { aspectRatio } = this._calculateVideoOffset(video);
310
-
311
- if (aspectRatio < 1) {
312
- canvas.width = video.videoWidth;
313
- canvas.height = video.videoHeight;
314
-
315
- // Draw the video frame onto the canvas
316
- const ctx = canvas.getContext('2d');
317
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
318
-
319
- const paddedWidth = canvas.width;
320
- const paddedHeight = canvas.width / fixedAspectRatio;
321
-
322
- // Calculate the dimensions of the cropped image based on the padded preview frame dimensions
323
- const cropWidth = paddedWidth;
324
- const cropHeight = paddedHeight;
325
- const cropLeft = 0;
326
- const cropTop = canvas.height / 2 - paddedHeight / 2;
327
-
328
- // Create a new canvas element for the cropped image
329
- const croppedCanvas = document.createElement('canvas');
330
- croppedCanvas.width = cropWidth;
331
- croppedCanvas.height = cropHeight;
332
-
333
- // Draw the cropped image onto the new canvas
334
- const croppedCtx = croppedCanvas.getContext('2d');
335
- croppedCtx.drawImage(
336
- canvas,
337
- cropLeft,
338
- cropTop,
339
- cropWidth,
340
- cropHeight,
341
- 0,
342
- 0,
343
- cropWidth,
344
- cropHeight,
345
- );
346
- const image = croppedCanvas.toDataURL('image/jpeg');
347
-
348
- return {
349
- image,
350
- originalHeight: canvas.height,
351
- originalWidth: canvas.width,
352
- previewImage: image,
353
- ...this.idCardRegion,
354
- };
355
- }
356
-
357
278
  const height = canvas.width / (video.videoWidth / video.videoHeight);
358
279
  canvas.height = height;
359
- context.drawImage(video, 0, 0, canvas.width, canvas.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
+ }
360
297
  const image = canvas.toDataURL('image/jpeg');
361
298
 
299
+ const previewImage = previewCanvas.toDataURL('image/jpeg');
362
300
  return {
363
301
  image,
364
302
  originalHeight: canvas.height,
365
303
  originalWidth: canvas.width,
366
- previewImage: image,
304
+ previewImage,
367
305
  ...this.idCardRegion,
368
306
  };
369
307
  }
@@ -401,28 +339,19 @@ class DocumentCapture extends HTMLElement {
401
339
  }
402
340
 
403
341
  handleIDStream(stream) {
404
- const videoExists = this.shadowRoot.querySelector('video');
405
- let video = null;
342
+ const videoExists = this.shadowRoot.querySelector('canvas');
406
343
  if (videoExists) {
407
- video = this.shadowRoot.querySelector('video');
408
- } else {
409
- video = document.createElement('video');
344
+ // remove canvas
345
+ videoExists.remove();
410
346
  }
411
- const videoContainer = this.shadowRoot.querySelector('.id-video');
412
- const MIN_WIDTH = 272;
413
- const width =
414
- videoContainer.clientWidth < MIN_WIDTH
415
- ? MIN_WIDTH
416
- : videoContainer.clientWidth;
417
- video.style.width = `${width}px`;
418
- video.style.height = '0px';
419
- 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
+
420
353
  video.muted = true;
421
354
  video.setAttribute('muted', 'true');
422
- if (this.isPortraitCaptureView) {
423
- video.style.objectFit = 'cover';
424
- video.style.scale = '1';
425
- }
426
355
 
427
356
  video.autoplay = true;
428
357
  video.playsInline = true;
@@ -432,104 +361,245 @@ class DocumentCapture extends HTMLElement {
432
361
  video.src = window.URL.createObjectURL(stream);
433
362
  }
434
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
+
435
370
  video.onloadedmetadata = () => {
436
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
+ }
437
379
  };
438
380
 
439
381
  const onVideoStart = () => {
440
- const {
441
- aspectRatio,
442
- offsetHeight,
443
- offsetWidth,
444
- videoHeight,
445
- videoWidth,
446
- } = this._calculateVideoOffset(video);
382
+ if (video.paused || video.ended) return;
383
+ video.removeEventListener('playing', onVideoStart);
384
+ const aspectRatio = video.videoWidth / video.videoHeight;
447
385
  const portrait = aspectRatio < 1;
386
+ if (this.isPortraitCaptureView) {
387
+ this.updatePortraitId(canvas, video);
388
+ requestAnimationFrame(onVideoStart);
389
+ return;
390
+ }
448
391
 
449
392
  if (portrait) {
450
393
  videoContainer.classList.add('mobile-camera-screen');
451
- videoContainer.style.height = `${videoHeight}px`;
452
- }
453
- videoContainer.style.width = `${videoWidth}px`;
454
- videoContainer.style.maxHeight = `${videoHeight}px`;
455
- video.style.height = `${videoHeight}px`;
456
- const idCardRegionWidth = videoWidth - offsetWidth;
457
- const idCardRegionHeight = videoHeight - offsetHeight;
458
-
459
- const rightLeftBorderSize = 20;
460
- const topBottomBorderSize = 20;
461
- this.idCardRegion = {
462
- height: idCardRegionHeight,
463
- rightLeftBorderSize,
464
- topBottomBorderSize,
465
- width: idCardRegionWidth,
466
- x: offsetWidth / 2,
467
- y: offsetHeight / 2,
468
- };
469
-
470
- let videoOverlay = videoContainer.querySelector('.video-overlay');
471
- if (videoOverlay) {
472
- 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);
473
399
  }
474
- videoOverlay = document.createElement('div');
475
- const shadeColor = 'white';
476
- videoOverlay.classList.add('video-overlay');
477
-
478
- videoOverlay.style.borderLeft = `${rightLeftBorderSize}px solid ${shadeColor}`;
479
- videoOverlay.style.borderRight = `${rightLeftBorderSize}px solid ${shadeColor}`;
480
- videoOverlay.style.borderTop = `${topBottomBorderSize}px solid ${shadeColor}`;
481
- videoOverlay.style.borderBottom = `${topBottomBorderSize}px solid ${shadeColor}`;
482
- videoOverlay.style.top = '0px';
483
- videoOverlay.style.bottom = '0px';
484
- videoOverlay.style.left = '0px';
485
- videoOverlay.style.right = '0px';
486
- videoOverlay.style.inset = '-1px';
487
-
488
- const innerBorder = document.createElement('div');
489
- innerBorder.classList.add('inner-border');
490
- videoOverlay.appendChild(innerBorder);
491
- videoContainer.appendChild(videoOverlay);
492
- this.videoOverlay = videoOverlay;
493
- this.shadowRoot.querySelector('#loader').hidden = true;
494
- this.shadowRoot.querySelector('.id-video').hidden = false;
495
- this.shadowRoot.querySelector('.actions').hidden = false;
496
- video.removeEventListener('playing', onVideoStart);
400
+ requestAnimationFrame(onVideoStart);
497
401
  };
498
402
 
499
403
  video.addEventListener('playing', onVideoStart);
500
404
 
501
- if (!videoExists) {
502
- videoContainer.prepend(video);
503
- }
504
-
505
405
  this._IDStream = stream;
506
406
  this._IDVideo = video;
507
407
  }
508
408
 
509
- _calculateVideoOffset(video) {
510
- const offset = 30;
511
- const aspectRatio = video.videoWidth / video.videoHeight;
512
- const calculatedAspectRatio = this.isPortraitCaptureView
513
- ? PORTRAIT_ID_PREVIEW_WIDTH / PORTRAIT_ID_PREVIEW_HEIGHT
514
- : fixedAspectRatio;
515
- const portrait = aspectRatio < 1;
516
- const videoWidth = video.clientWidth;
517
- const videoHeight = video.clientWidth / calculatedAspectRatio;
518
- const originalWidth = video.videoWidth;
519
- const originalHeight = video.videoWidth / calculatedAspectRatio;
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
+ }
442
+
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
+ }
467
+
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
+ }
520
501
 
521
- const offsetHeight = videoHeight * ((portrait ? 5 : offset) / 100);
522
- const offsetWidth = videoWidth * (offset / 100);
502
+ _drawPortraitToLandscapeImage(canvas, video = this._IDVideo) {
503
+ const { videoHeight, videoWidth } = video;
504
+ const cropWidth = 600;
505
+ const cropHeight = 400;
523
506
 
524
- return {
525
- aspectRatio,
526
- offsetHeight,
527
- offsetWidth,
528
- originalHeight,
529
- originalWidth,
530
- videoHeight,
531
- videoWidth,
532
- };
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
+ }
533
603
  }
534
604
 
535
605
  _stopIDVideoStream(stream = this._IDStream) {
@@ -568,7 +638,7 @@ class DocumentCapture extends HTMLElement {
568
638
  }
569
639
 
570
640
  get themeColor() {
571
- return this.getAttribute('theme-color') || '#043C93';
641
+ return this.getAttribute('theme-color') || '#001096';
572
642
  }
573
643
 
574
644
  get hideAttribution() {
@@ -591,6 +661,14 @@ class DocumentCapture extends HTMLElement {
591
661
  );
592
662
  }
593
663
 
664
+ get height() {
665
+ return this.getAttribute('height');
666
+ }
667
+
668
+ get width() {
669
+ return this.getAttribute('width');
670
+ }
671
+
594
672
  get hidden() {
595
673
  return this.getAttribute('hidden');
596
674
  }
@@ -2,14 +2,21 @@ import SmartCamera from '../../../../domain/camera/src/SmartCamera';
2
2
  import './index';
3
3
 
4
4
  const meta = {
5
+ args: {
6
+ 'theme-color': '#001096',
7
+ },
8
+ argTypes: {
9
+ 'theme-color': { control: 'color' },
10
+ },
5
11
  component: 'document-capture',
6
- render: ({ documentType }) => `
12
+ render: (args) => `
7
13
  <document-capture
8
14
  show-navigation
9
15
  document-capture-modes="camera,upload"
10
16
  document-name="Driver's License"
11
17
  side-of-id="Front"
12
- document-type="${documentType}"
18
+ document-type="${args.documentType}"
19
+ theme-color='${args['theme-color']}'
13
20
  >
14
21
  </document-capture>
15
22
  `,