@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.
Files changed (44) hide show
  1. package/README.md +15 -0
  2. package/components/README.md +1 -1
  3. package/components/camera-permission/CameraPermission.js +6 -2
  4. package/components/camera-permission/CameraPermission.stories.js +10 -4
  5. package/components/document/src/DocumentCaptureScreens.js +41 -11
  6. package/components/document/src/DocumentCaptureScreens.stories.js +16 -12
  7. package/components/document/src/README.md +11 -8
  8. package/components/document/src/document-capture/DocumentCapture.js +299 -231
  9. package/components/document/src/document-capture/DocumentCapture.stories.js +9 -2
  10. package/components/document/src/document-capture/README.md +5 -4
  11. package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.js +33 -33
  12. package/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +8 -1
  13. package/components/document/src/document-capture-review/DocumentCaptureReview.js +15 -31
  14. package/components/document/src/document-capture-review/DocumentCaptureReview.stories.js +12 -2
  15. package/components/document/src/document-capture-review/README.md +3 -3
  16. package/components/end-user-consent/src/EndUserConsent.js +14 -31
  17. package/components/end-user-consent/src/EndUserConsent.stories.js +8 -2
  18. package/components/navigation/src/Navigation.js +10 -2
  19. package/components/selfie/README.md +28 -4
  20. package/components/selfie/src/SelfieCaptureScreens.js +36 -10
  21. package/components/selfie/src/SelfieCaptureScreens.stories.js +13 -4
  22. package/components/selfie/src/selfie-capture/SelfieCapture.js +95 -23
  23. package/components/selfie/src/selfie-capture/SelfieCapture.stories.js +14 -1
  24. package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.js +50 -44
  25. package/components/selfie/src/selfie-capture-instructions/SelfieCaptureInstructions.stories.js +8 -2
  26. package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.js +3 -4
  27. package/components/selfie/src/selfie-capture-review/SelfieCaptureReview.stories.js +8 -1
  28. package/components/signature-pad/package-lock.json +3009 -0
  29. package/components/signature-pad/package.json +6 -6
  30. package/components/signature-pad/src/SignaturePad.js +5 -1
  31. package/components/signature-pad/src/SignaturePad.stories.js +10 -2
  32. package/components/smart-camera-web/src/README.md +207 -0
  33. package/components/smart-camera-web/src/SmartCameraWeb.js +56 -7
  34. package/components/smart-camera-web/src/SmartCameraWeb.stories.js +27 -7
  35. package/components/totp-consent/src/TotpConsent.js +15 -3
  36. package/cypress/e2e/smart-camera-web-agent-mode.cy.js +144 -0
  37. package/cypress/e2e/smart-camera-web-complete-flow.cy.js +221 -0
  38. package/cypress/e2e/smart-camera-web.cy.js +1 -1
  39. package/cypress/pages/smart-camera-web-agent-mode.html +36 -0
  40. package/cypress/pages/smart-camera-web-complete-flow.html +42 -0
  41. package/cypress/pages/smart-camera-web.html +1 -1
  42. package/domain/camera/src/SmartCamera.js +28 -0
  43. package/package.json +8 -8
  44. 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,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 = 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
- console.warn('this.idCardRegion', this.idCardRegion);
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: croppedCanvas.toDataURL('image/jpeg'),
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: canvas.toDataURL('image/jpeg'),
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('video');
415
- let video = null;
342
+ const videoExists = this.shadowRoot.querySelector('canvas');
416
343
  if (videoExists) {
417
- video = this.shadowRoot.querySelector('video');
418
- } else {
419
- video = document.createElement('video');
344
+ // remove canvas
345
+ videoExists.remove();
420
346
  }
421
- const videoContainer = this.shadowRoot.querySelector('.id-video');
422
- const MIN_WIDTH = 272;
423
- const width =
424
- videoContainer.clientWidth < MIN_WIDTH
425
- ? MIN_WIDTH
426
- : videoContainer.clientWidth;
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
- const {
451
- aspectRatio,
452
- offsetHeight,
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
- videoContainer.style.height = `${videoHeight}px`;
462
- }
463
- videoContainer.style.width = `${videoWidth}px`;
464
- videoContainer.style.maxHeight = `${videoHeight}px`;
465
- video.style.height = `${videoHeight}px`;
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
- videoOverlay = document.createElement('div');
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
- _calculateVideoOffset(video) {
520
- const offset = 30;
521
- const aspectRatio = video.videoWidth / video.videoHeight;
522
- const calculatedAspectRatio = this.isPortraitCaptureView
523
- ? PORTRAIT_ID_PREVIEW_WIDTH / PORTRAIT_ID_PREVIEW_HEIGHT
524
- : fixedAspectRatio;
525
- const portrait = aspectRatio < 1;
526
- const videoWidth = video.clientWidth;
527
- const videoHeight = video.clientWidth / calculatedAspectRatio;
528
- const originalWidth = video.videoWidth;
529
- 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
+ }
530
442
 
531
- const offsetHeight = videoHeight * ((portrait ? 5 : offset) / 100);
532
- const offsetWidth = videoWidth * (offset / 100);
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
- return {
535
- aspectRatio,
536
- offsetHeight,
537
- offsetWidth,
538
- originalHeight,
539
- originalWidth,
540
- videoHeight,
541
- videoWidth,
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') || '#043C93';
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
  }