@jaak.ai/stamps 2.0.0-dev.53 → 2.0.0-dev.55

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 CHANGED
@@ -1,611 +1,761 @@
1
- # Jaak Stamps - Document Detector Web Component
1
+ # Jaak Stamps Web Component
2
2
 
3
- A web component for automatic detection and capture of identification documents using computer vision.
3
+ [![npm version](https://badge.fury.io/js/%40jaak.ai%2Fstamps.svg)](https://www.npmjs.com/package/@jaak.ai/stamps)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
5
 
5
- ## Features
6
+ [📖 Versión en Español](./README_ES.md)
6
7
 
7
- - Real-time automatic document detection
8
- - Visual guide for optimal document positioning
9
- - Intelligent document classification to determine if back capture is required
10
- - Adaptive capture - automatically detects if document requires back side
11
- - Dual output for each side: full frame and cropped document
12
- - Configurable cropping with customizable margin around document
13
- - Programmatic API for complete process control
14
- - Responsive and mobile device compatible
15
- - Automatic optimization for better performance
8
+ ## Description
9
+
10
+ Jaak Stamps is a powerful web component for document scanning and capture using camera input with AI-powered document detection. The component provides a complete solution for capturing high-quality document images with real-time detection feedback and automated cropping capabilities.
11
+
12
+ ### Key Features
13
+
14
+ - **AI-Powered Document Detection**: Automatically detects and frames documents in the camera view
15
+ - **Dual-Side Capture**: Supports both front and back document capture workflow
16
+ - **Real-time Preview**: Live camera feed with detection overlays and guidance
17
+ - **Multi-Camera Support**: Automatic camera selection with manual override options
18
+ - **Smart Cropping**: Automatic document boundary detection and cropping
19
+ - **Mobile Optimized**: Responsive design that works across all device sizes
20
+ - **Web Standards Compliant**: Built as a standard Web Component for maximum compatibility
21
+
22
+ ### Typical Use Cases
23
+
24
+ - **Identity Verification**: Capture ID cards, passports, and driver's licenses
25
+ - **Document Digitization**: Scan contracts, certificates, and legal documents
26
+ - **KYC/AML Processes**: Streamlined identity document capture for compliance
27
+ - **Form Processing**: Digitize paper forms and applications
28
+ - **Insurance Claims**: Capture damage reports and supporting documentation
16
29
 
17
30
  ## Installation
18
31
 
19
- ### NPM
32
+ ### NPM/Yarn
33
+
20
34
  ```bash
35
+ # Using npm
21
36
  npm install @jaak.ai/stamps
22
- ```
23
37
 
24
- ### Yarn
25
- ```bash
38
+ # Using yarn
26
39
  yarn add @jaak.ai/stamps
27
40
  ```
28
41
 
29
42
  ### CDN
43
+
30
44
  ```html
45
+ <!-- ES Module -->
31
46
  <script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
47
+
48
+ <!-- With Loader -->
49
+ <script type="module">
50
+ import { defineCustomElements } from 'https://unpkg.com/@jaak.ai/stamps/loader/index.js';
51
+ defineCustomElements();
52
+ </script>
53
+ ```
54
+
55
+ ## API Reference
56
+
57
+ ### Properties/Attributes
58
+
59
+ | Property | Type | Default | Description |
60
+ |----------|------|---------|-------------|
61
+ | `debug` | `boolean` | `false` | Enable debug mode with additional logging and overlays |
62
+ | `mask-size` | `number` | `80` | Size percentage of the document detection mask |
63
+ | `alignment-tolerance` | `number` | `15` | Tolerance level for document alignment detection |
64
+ | `capture-delay` | `number` | `1500` | Delay in milliseconds before automatic capture |
65
+ | `crop-margin` | `number` | `20` | Margin in pixels around detected document for cropping |
66
+ | `preferred-camera` | `'auto' \| 'front' \| 'back'` | `'auto'` | Preferred camera selection |
67
+ | `use-document-classification` | `boolean` | `false` | Enable AI document classification |
68
+ | `enable-back-document-timer` | `boolean` | `false` | Enable timer for back document capture |
69
+ | `back-document-timer-duration` | `number` | `20` | Duration in seconds for back document capture timer |
70
+
71
+ ### Methods
72
+
73
+ #### Camera Control
74
+
75
+ ```typescript
76
+ // Get information about available cameras
77
+ getCameraInfo(): Promise<CameraInfoResponse>
78
+
79
+ // Set preferred camera
80
+ setPreferredCamera(camera: "auto" | "front" | "back"): Promise<{
81
+ success: boolean;
82
+ selectedCamera: string;
83
+ availableCameras: number;
84
+ }>
85
+
86
+ // Control device torch/flashlight
87
+ setTorchEnabled(enabled: boolean): Promise<{
88
+ success: boolean;
89
+ enabled: boolean;
90
+ }>
91
+
92
+ // Focus camera at specific point
93
+ focusAtPoint(x: number, y: number): Promise<{
94
+ success: boolean;
95
+ coordinates: { x: number; y: number };
96
+ }>
32
97
  ```
33
98
 
34
- ## Basic Usage
99
+ #### Capture Control
100
+
101
+ ```typescript
102
+ // Start the capture process
103
+ startCapture(): Promise<void>
104
+
105
+ // Stop the capture process
106
+ stopCapture(): Promise<void>
107
+
108
+ // Reset capture state
109
+ resetCapture(): Promise<void>
35
110
 
36
- ### HTML Vanilla
111
+ // Skip back document capture
112
+ skipBackCapture(): Promise<void>
113
+ ```
114
+
115
+ #### Configuration
116
+
117
+ ```typescript
118
+ // Set capture delay
119
+ setCaptureDelay(delay: number): Promise<{
120
+ success: boolean;
121
+ captureDelay: number;
122
+ }>
123
+
124
+ // Get current capture delay
125
+ getCaptureDelay(): Promise<number>
126
+ ```
127
+
128
+ #### Status & Data
129
+
130
+ ```typescript
131
+ // Get current component status
132
+ getStatus(): Promise<StatusResponse>
133
+
134
+ // Get captured images
135
+ getCapturedImages(): Promise<CapturedImagesResponse>
136
+
137
+ // Check if process is completed
138
+ isProcessCompleted(): Promise<boolean>
139
+
140
+ // Preload AI model
141
+ preloadModel(): Promise<{
142
+ success: boolean;
143
+ message?: string;
144
+ error?: any;
145
+ }>
146
+ ```
147
+
148
+ ### Events
149
+
150
+ | Event | Detail Type | Description |
151
+ |-------|-------------|-------------|
152
+ | `isReady` | `boolean` | Fired when component is fully initialized and ready |
153
+ | `captureCompleted` | `object` | Fired when document capture process is completed |
154
+
155
+ ### Type Definitions
156
+
157
+ ```typescript
158
+ interface CameraInfoResponse {
159
+ availableCameras: Array<{
160
+ id: string;
161
+ label: string;
162
+ selected: boolean;
163
+ }>;
164
+ selectedCameraId: string | null;
165
+ deviceType: string;
166
+ isMultipleCamerasAvailable: boolean;
167
+ preferredFacing: 'environment' | 'user' | null;
168
+ }
169
+
170
+ interface CapturedImagesResponse {
171
+ front: {
172
+ fullFrame: string | null;
173
+ cropped: string | null;
174
+ };
175
+ back: {
176
+ fullFrame: string | null;
177
+ cropped: string | null;
178
+ };
179
+ metadata: {
180
+ totalImages: number;
181
+ processCompleted: boolean;
182
+ backCaptureSkipped: boolean;
183
+ };
184
+ }
185
+
186
+ interface StatusResponse {
187
+ isVideoActive: boolean;
188
+ captureStep: 'front' | 'back' | 'completed';
189
+ hasImages: boolean;
190
+ isProcessCompleted: boolean;
191
+ isModelPreloaded: boolean;
192
+ }
193
+ ```
194
+
195
+ ## Implementation Examples
196
+
197
+ ### Vanilla JavaScript
37
198
 
38
199
  ```html
39
200
  <!DOCTYPE html>
40
201
  <html>
41
202
  <head>
42
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
43
- <script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
203
+ <script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
44
204
  </head>
45
205
  <body>
46
- <jaak-stamps id="detector" debug="false" mask-size="80" crop-margin="20" capture-delay="1000" use-document-classification="false"></jaak-stamps>
206
+ <jaak-stamps
207
+ id="documentScanner"
208
+ preferred-camera="auto"
209
+ capture-delay="1500"
210
+ debug="false">
211
+ </jaak-stamps>
212
+
213
+ <script>
214
+ const scanner = document.getElementById('documentScanner');
47
215
 
48
- <button onclick="startCapture()">Start Capture</button>
49
- <button onclick="stopCapture()">Stop</button>
50
- <button onclick="resetCapture()">Reset</button>
51
-
52
- <script>
53
- const detector = document.getElementById('detector');
54
-
55
- // Listen for capture completed event
56
- detector.addEventListener('captureCompleted', (event) => {
57
- console.log('Capture completed:', event.detail);
58
- // event.detail contains the captured images
59
- });
60
-
61
- // Listen for component ready event
62
- detector.addEventListener('isReady', (event) => {
63
- console.log('Component ready:', event.detail);
64
- // event.detail indicates if the component is ready (true/false)
65
- });
66
-
67
- function startCapture() {
68
- detector.startCapture();
69
- }
70
-
71
- function stopCapture() {
72
- detector.stopCapture();
73
- }
74
-
75
- function resetCapture() {
76
- detector.resetCapture();
77
- }
78
-
79
- // Preload model (optional - improves performance)
80
- async function preloadModel() {
81
- try {
82
- const result = await detector.preloadModel();
83
- console.log('Model preloaded:', result);
84
- } catch (error) {
85
- console.error('Error preloading:', error);
86
- }
87
- }
88
-
89
- // Get images after capture
90
- async function getImages() {
91
- try {
92
- const images = await detector.getCapturedImages();
93
- console.log('Captured images:', images);
94
- } catch (error) {
95
- console.error('Error:', error.message);
96
- }
97
- }
98
- </script>
216
+ // Wait for component to be ready
217
+ scanner.addEventListener('isReady', () => {
218
+ console.log('Document scanner is ready');
219
+
220
+ // Start the capture process
221
+ scanner.startCapture();
222
+ });
223
+
224
+ // Handle capture completion
225
+ scanner.addEventListener('captureCompleted', async (event) => {
226
+ console.log('Capture completed:', event.detail);
227
+
228
+ // Get captured images
229
+ const images = await scanner.getCapturedImages();
230
+ console.log('Captured images:', images);
231
+
232
+ // Process the images as needed
233
+ if (images.front.cropped) {
234
+ // Display or upload the cropped front image
235
+ displayImage(images.front.cropped);
236
+ }
237
+ });
238
+
239
+ function displayImage(base64Image) {
240
+ const img = document.createElement('img');
241
+ img.src = `data:image/jpeg;base64,${base64Image}`;
242
+ document.body.appendChild(img);
243
+ }
244
+ </script>
99
245
  </body>
100
246
  </html>
101
247
  ```
102
248
 
103
- ### Angular
249
+ ### React
104
250
 
105
- #### 1. Install the component
106
- ```bash
107
- npm install @jaak.ai/stamps
108
- ```
251
+ ```tsx
252
+ import React, { useEffect, useRef, useState } from 'react';
253
+ import { defineCustomElements } from '@jaak.ai/stamps/loader';
109
254
 
110
- #### 2. Configure the module (app.module.ts)
111
- ```typescript
112
- import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
113
- import { BrowserModule } from '@angular/platform-browser';
114
- import { AppComponent } from './app.component';
255
+ // Define custom elements
256
+ defineCustomElements();
257
+
258
+ // Extend HTMLElement for TypeScript
259
+ declare global {
260
+ namespace JSX {
261
+ interface IntrinsicElements {
262
+ 'jaak-stamps': any;
263
+ }
264
+ }
265
+ }
115
266
 
116
- // Import the web component
117
- import '@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js';
267
+ interface CapturedImages {
268
+ front: { fullFrame: string | null; cropped: string | null };
269
+ back: { fullFrame: string | null; cropped: string | null };
270
+ metadata: {
271
+ totalImages: number;
272
+ processCompleted: boolean;
273
+ backCaptureSkipped: boolean;
274
+ };
275
+ }
118
276
 
119
- @NgModule({
120
- declarations: [AppComponent],
121
- imports: [BrowserModule],
122
- providers: [],
123
- bootstrap: [AppComponent],
124
- schemas: [CUSTOM_ELEMENTS_SCHEMA] // Allow custom elements
125
- })
126
- export class AppModule { }
277
+ const DocumentScanner: React.FC = () => {
278
+ const scannerRef = useRef<HTMLElement>(null);
279
+ const [isReady, setIsReady] = useState(false);
280
+ const [capturedImages, setCapturedImages] = useState<CapturedImages | null>(null);
281
+
282
+ useEffect(() => {
283
+ const scanner = scannerRef.current;
284
+ if (!scanner) return;
285
+
286
+ // Component ready handler
287
+ const handleReady = () => {
288
+ setIsReady(true);
289
+ };
290
+
291
+ // Capture completion handler
292
+ const handleCaptureCompleted = async () => {
293
+ const images = await (scanner as any).getCapturedImages();
294
+ setCapturedImages(images);
295
+ };
296
+
297
+ // Add event listeners
298
+ scanner.addEventListener('isReady', handleReady);
299
+ scanner.addEventListener('captureCompleted', handleCaptureCompleted);
300
+
301
+ return () => {
302
+ scanner.removeEventListener('isReady', handleReady);
303
+ scanner.removeEventListener('captureCompleted', handleCaptureCompleted);
304
+ };
305
+ }, []);
306
+
307
+ const startScan = async () => {
308
+ if (scannerRef.current) {
309
+ await (scannerRef.current as any).startCapture();
310
+ }
311
+ };
312
+
313
+ return (
314
+ <div>
315
+ <h2>Document Scanner</h2>
316
+
317
+ <jaak-stamps
318
+ ref={scannerRef}
319
+ preferred-camera="auto"
320
+ capture-delay={1500}
321
+ use-document-classification={true}
322
+ />
323
+
324
+ <div style={{ marginTop: '20px' }}>
325
+ <button onClick={startScan} disabled={!isReady}>
326
+ {isReady ? 'Start Scanning' : 'Loading...'}
327
+ </button>
328
+ </div>
329
+
330
+ {capturedImages && (
331
+ <div style={{ marginTop: '20px' }}>
332
+ <h3>Captured Images</h3>
333
+ {capturedImages.front.cropped && (
334
+ <div>
335
+ <h4>Front Document</h4>
336
+ <img
337
+ src={`data:image/jpeg;base64,${capturedImages.front.cropped}`}
338
+ alt="Front document"
339
+ style={{ maxWidth: '300px' }}
340
+ />
341
+ </div>
342
+ )}
343
+ {capturedImages.back.cropped && (
344
+ <div>
345
+ <h4>Back Document</h4>
346
+ <img
347
+ src={`data:image/jpeg;base64,${capturedImages.back.cropped}`}
348
+ alt="Back document"
349
+ style={{ maxWidth: '300px' }}
350
+ />
351
+ </div>
352
+ )}
353
+ </div>
354
+ )}
355
+ </div>
356
+ );
357
+ };
358
+
359
+ export default DocumentScanner;
127
360
  ```
128
361
 
129
- #### 3. Use in the component
362
+ ### Angular
363
+
130
364
  ```typescript
131
- // app.component.ts
132
- import { Component, ElementRef, ViewChild, OnInit } from '@angular/core';
365
+ // document-scanner.component.ts
366
+ import { Component, ElementRef, ViewChild, AfterViewInit, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
367
+ import { defineCustomElements } from '@jaak.ai/stamps/loader';
368
+
369
+ // Define custom elements
370
+ defineCustomElements();
371
+
372
+ interface CapturedImages {
373
+ front: { fullFrame: string | null; cropped: string | null };
374
+ back: { fullFrame: string | null; cropped: string | null };
375
+ metadata: {
376
+ totalImages: number;
377
+ processCompleted: boolean;
378
+ backCaptureSkipped: boolean;
379
+ };
380
+ }
133
381
 
134
382
  @Component({
135
- selector: 'app-root',
383
+ selector: 'app-document-scanner',
136
384
  template: `
137
- <div class="container">
138
- <h1>Document Detector</h1>
385
+ <div class="scanner-container">
386
+ <h2>Document Scanner</h2>
139
387
 
140
- <jaak-stamps
141
- #detector
142
- [debug]="false"
143
- [maskSize]="80"
144
- [cropMargin]="20"
145
- [useDocumentClassification]="false"
146
- (captureCompleted)="onCaptureCompleted($event)"
147
- (isReady)="onComponentReady($event)">
388
+ <jaak-stamps
389
+ #scanner
390
+ preferred-camera="auto"
391
+ [capture-delay]="1500"
392
+ [use-document-classification]="true">
148
393
  </jaak-stamps>
149
394
 
150
395
  <div class="controls">
151
- <button (click)="preloadModel()">Preload Model</button>
152
- <button (click)="startCapture()">Start Capture</button>
153
- <button (click)="stopCapture()">Stop</button>
154
- <button (click)="resetCapture()">Reset</button>
155
- <button (click)="getImages()" [disabled]="!isCompleted">Get Images</button>
396
+ <button
397
+ (click)="startScan()"
398
+ [disabled]="!isReady"
399
+ class="scan-button">
400
+ {{ isReady ? 'Start Scanning' : 'Loading...' }}
401
+ </button>
402
+
403
+ <button
404
+ (click)="resetScan()"
405
+ [disabled]="!isReady"
406
+ class="reset-button">
407
+ Reset
408
+ </button>
156
409
  </div>
157
410
 
158
- <div *ngIf="capturedData" class="results">
159
- <h3>Captured Images:</h3>
160
- <p>Total images: {{ capturedData.metadata.totalImages }}</p>
161
- <img *ngIf="capturedData.front.cropped" [src]="capturedData.front.cropped" alt="Front">
162
- <img *ngIf="capturedData.back.cropped" [src]="capturedData.back.cropped" alt="Back">
411
+ <div *ngIf="capturedImages" class="results">
412
+ <h3>Captured Images</h3>
413
+
414
+ <div *ngIf="capturedImages.front.cropped" class="image-result">
415
+ <h4>Front Document</h4>
416
+ <img
417
+ [src]="'data:image/jpeg;base64,' + capturedImages.front.cropped"
418
+ alt="Front document"
419
+ class="captured-image">
420
+ </div>
421
+
422
+ <div *ngIf="capturedImages.back.cropped" class="image-result">
423
+ <h4>Back Document</h4>
424
+ <img
425
+ [src]="'data:image/jpeg;base64,' + capturedImages.back.cropped"
426
+ alt="Back document"
427
+ class="captured-image">
428
+ </div>
429
+
430
+ <div class="metadata">
431
+ <p>Total Images: {{ capturedImages.metadata.totalImages }}</p>
432
+ <p>Process Completed: {{ capturedImages.metadata.processCompleted }}</p>
433
+ </div>
163
434
  </div>
164
435
  </div>
165
436
  `,
166
437
  styles: [`
167
- .container { max-width: 800px; margin: 0 auto; padding: 20px; }
168
- .controls { margin: 20px 0; }
169
- .controls button { margin: 0 10px; padding: 10px 20px; }
170
- .results img { max-width: 300px; margin: 10px; border: 1px solid #ccc; }
171
- `]
172
- })
173
- export class AppComponent implements OnInit {
174
- @ViewChild('detector', { static: true }) detectorRef!: ElementRef;
175
-
176
- capturedData: any = null;
177
- isCompleted = false;
178
-
179
- ngOnInit() {
180
- // The element is already available after the view
181
- }
182
-
183
- async preloadModel() {
184
- try {
185
- const result = await this.detectorRef.nativeElement.preloadModel();
186
- console.log('Model preloaded:', result);
187
- } catch (error) {
188
- console.error('Error preloading model:', error);
438
+ .scanner-container {
439
+ max-width: 600px;
440
+ margin: 0 auto;
441
+ padding: 20px;
189
442
  }
190
- }
191
-
192
- async startCapture() {
193
- try {
194
- await this.detectorRef.nativeElement.startCapture();
195
- } catch (error) {
196
- console.error('Error starting capture:', error);
443
+
444
+ .controls {
445
+ margin: 20px 0;
446
+ display: flex;
447
+ gap: 10px;
197
448
  }
198
- }
449
+
450
+ .scan-button, .reset-button {
451
+ padding: 10px 20px;
452
+ font-size: 16px;
453
+ border: none;
454
+ border-radius: 4px;
455
+ cursor: pointer;
456
+ }
457
+
458
+ .scan-button {
459
+ background-color: #007bff;
460
+ color: white;
461
+ }
462
+
463
+ .reset-button {
464
+ background-color: #6c757d;
465
+ color: white;
466
+ }
467
+
468
+ .scan-button:disabled, .reset-button:disabled {
469
+ opacity: 0.6;
470
+ cursor: not-allowed;
471
+ }
472
+
473
+ .results {
474
+ margin-top: 20px;
475
+ }
476
+
477
+ .image-result {
478
+ margin: 20px 0;
479
+ }
480
+
481
+ .captured-image {
482
+ max-width: 100%;
483
+ height: auto;
484
+ border: 1px solid #ddd;
485
+ border-radius: 4px;
486
+ }
487
+
488
+ .metadata {
489
+ background-color: #f8f9fa;
490
+ padding: 10px;
491
+ border-radius: 4px;
492
+ margin-top: 20px;
493
+ }
494
+ `],
495
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
496
+ })
497
+ export class DocumentScannerComponent implements AfterViewInit {
498
+ @ViewChild('scanner', { static: false }) scannerRef!: ElementRef;
199
499
 
200
- async stopCapture() {
201
- await this.detectorRef.nativeElement.stopCapture();
202
- }
500
+ isReady = false;
501
+ capturedImages: CapturedImages | null = null;
203
502
 
204
- async resetCapture() {
205
- await this.detectorRef.nativeElement.resetCapture();
206
- this.capturedData = null;
207
- this.isCompleted = false;
503
+ ngAfterViewInit() {
504
+ const scanner = this.scannerRef.nativeElement;
505
+
506
+ // Component ready handler
507
+ scanner.addEventListener('isReady', () => {
508
+ this.isReady = true;
509
+ });
510
+
511
+ // Capture completion handler
512
+ scanner.addEventListener('captureCompleted', async () => {
513
+ this.capturedImages = await scanner.getCapturedImages();
514
+ });
208
515
  }
209
516
 
210
- async getImages() {
211
- try {
212
- const images = await this.detectorRef.nativeElement.getCapturedImages();
213
- console.log('Images obtained:', images);
214
- } catch (error) {
215
- console.error('Error getting images:', error);
517
+ async startScan() {
518
+ if (this.scannerRef?.nativeElement) {
519
+ await this.scannerRef.nativeElement.startCapture();
216
520
  }
217
521
  }
218
522
 
219
- onCaptureCompleted(event: any) {
220
- console.log('Capture completed in Angular:', event.detail);
221
- this.capturedData = event.detail;
222
- this.isCompleted = true;
223
- }
224
-
225
- onComponentReady(event: any) {
226
- console.log('Component ready in Angular:', event.detail);
227
- // The component is ready to start detection
523
+ async resetScan() {
524
+ if (this.scannerRef?.nativeElement) {
525
+ await this.scannerRef.nativeElement.resetCapture();
526
+ this.capturedImages = null;
527
+ }
228
528
  }
229
529
  }
230
530
  ```
231
531
 
232
- ### React
532
+ ```typescript
533
+ // app.module.ts
534
+ import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
535
+ import { BrowserModule } from '@angular/platform-browser';
536
+ import { DocumentScannerComponent } from './document-scanner.component';
233
537
 
234
- #### 1. Install the component
235
- ```bash
236
- npm install @jaak.ai/stamps
538
+ @NgModule({
539
+ declarations: [DocumentScannerComponent],
540
+ imports: [BrowserModule],
541
+ schemas: [CUSTOM_ELEMENTS_SCHEMA], // Required for custom elements
542
+ exports: [DocumentScannerComponent]
543
+ })
544
+ export class DocumentScannerModule { }
237
545
  ```
238
546
 
239
- #### 2. Create wrapper component (optional but recommended)
240
- ```jsx
241
- // JaakStampsWrapper.jsx
242
- import React, { useRef, useEffect, useImperativeHandle, forwardRef } from 'react';
547
+ ## Style Guide
548
+
549
+ ### CSS Custom Properties
243
550
 
244
- // Import the web component
245
- import '@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js';
551
+ The component supports customization through CSS custom properties:
246
552
 
247
- const JaakStampsWrapper = forwardRef(({ debug = false, maskSize = 80, cropMargin = 20, useDocumentClassification = false, onCaptureCompleted, onIsReady }, ref) => {
248
- const elementRef = useRef(null);
553
+ ```css
554
+ jaak-stamps {
555
+ /* Camera view dimensions */
556
+ --camera-width: 100%;
557
+ --camera-height: 400px;
249
558
 
250
- useImperativeHandle(ref, () => ({
251
- preloadModel: () => elementRef.current?.preloadModel(),
252
- startCapture: () => elementRef.current?.startCapture(),
253
- stopCapture: () => elementRef.current?.stopCapture(),
254
- resetCapture: () => elementRef.current?.resetCapture(),
255
- getCapturedImages: () => elementRef.current?.getCapturedImages(),
256
- getStatus: () => elementRef.current?.getStatus(),
257
- skipBackCapture: () => elementRef.current?.skipBackCapture(),
258
- isProcessCompleted: () => elementRef.current?.isProcessCompleted()
259
- }));
559
+ /* Detection overlay colors */
560
+ --detection-overlay-color: rgba(0, 123, 255, 0.3);
561
+ --detection-border-color: #007bff;
562
+ --detection-border-width: 2px;
260
563
 
261
- useEffect(() => {
262
- const element = elementRef.current;
263
-
264
- const handleCaptureCompleted = (event) => {
265
- if (onCaptureCompleted) {
266
- onCaptureCompleted(event.detail);
267
- }
268
- };
269
-
270
- const handleIsReady = (event) => {
271
- if (onIsReady) {
272
- onIsReady(event.detail);
273
- }
274
- };
275
-
276
- if (element) {
277
- element.addEventListener('captureCompleted', handleCaptureCompleted);
278
- element.addEventListener('isReady', handleIsReady);
279
-
280
- return () => {
281
- element.removeEventListener('captureCompleted', handleCaptureCompleted);
282
- element.removeEventListener('isReady', handleIsReady);
283
- };
284
- }
285
- }, [onCaptureCompleted, onIsReady]);
564
+ /* UI element colors */
565
+ --button-background: #007bff;
566
+ --button-text-color: #ffffff;
567
+ --button-border-radius: 4px;
286
568
 
287
- return (
288
- <jaak-stamps
289
- ref={elementRef}
290
- debug={debug}
291
- mask-size={maskSize}
292
- crop-margin={cropMargin}
293
- use-document-classification={useDocumentClassification}
294
- />
295
- );
296
- });
569
+ /* Status indicator colors */
570
+ --status-success-color: #28a745;
571
+ --status-warning-color: #ffc107;
572
+ --status-error-color: #dc3545;
573
+ }
574
+ ```
297
575
 
298
- export default JaakStampsWrapper;
576
+ ### Custom CSS Classes
577
+
578
+ You can style the component container and add custom overlays:
579
+
580
+ ```css
581
+ /* Component container styling */
582
+ .document-scanner-container {
583
+ position: relative;
584
+ border: 1px solid #ddd;
585
+ border-radius: 8px;
586
+ overflow: hidden;
587
+ background: #000;
588
+ }
589
+
590
+ /* Custom loading overlay */
591
+ .scanner-loading {
592
+ position: absolute;
593
+ top: 50%;
594
+ left: 50%;
595
+ transform: translate(-50%, -50%);
596
+ background: rgba(0, 0, 0, 0.8);
597
+ color: white;
598
+ padding: 20px;
599
+ border-radius: 8px;
600
+ z-index: 1000;
601
+ }
602
+
603
+ /* Responsive design */
604
+ @media (max-width: 768px) {
605
+ jaak-stamps {
606
+ --camera-height: 300px;
607
+ }
608
+ }
299
609
  ```
300
610
 
301
- #### 3. Use in your React application
302
- ```jsx
303
- // App.jsx
304
- import React, { useRef, useState } from 'react';
305
- import JaakStampsWrapper from './components/JaakStampsWrapper';
306
-
307
- function App() {
308
- const detectorRef = useRef(null);
309
- const [capturedData, setCapturedData] = useState(null);
310
- const [isCompleted, setIsCompleted] = useState(false);
311
- const [status, setStatus] = useState(null);
312
-
313
- const handleCaptureCompleted = (data) => {
314
- console.log('Capture completed:', data);
315
- setCapturedData(data);
316
- setIsCompleted(true);
317
- };
318
-
319
- const handleIsReady = (isReady) => {
320
- console.log('Component ready:', isReady);
321
- // The component is ready to start detection
322
- };
323
-
324
- const preloadModel = async () => {
325
- try {
326
- const result = await detectorRef.current?.preloadModel();
327
- console.log('Model preloaded:', result);
328
- } catch (error) {
329
- console.error('Error preloading model:', error);
330
- }
331
- };
332
-
333
- const startCapture = async () => {
334
- try {
335
- await detectorRef.current?.startCapture();
336
- } catch (error) {
337
- console.error('Error starting capture:', error);
338
- }
339
- };
340
-
341
- const stopCapture = async () => {
342
- await detectorRef.current?.stopCapture();
343
- };
344
-
345
- const resetCapture = async () => {
346
- await detectorRef.current?.resetCapture();
347
- setCapturedData(null);
348
- setIsCompleted(false);
349
- };
350
-
351
- const getImages = async () => {
352
- try {
353
- const images = await detectorRef.current?.getCapturedImages();
354
- console.log('Images obtained:', images);
355
- } catch (error) {
356
- console.error('Error getting images:', error);
357
- }
358
- };
359
-
360
- const checkStatus = async () => {
361
- const currentStatus = await detectorRef.current?.getStatus();
362
- setStatus(currentStatus);
363
- };
364
-
365
- return (
366
- <div className="App">
367
- <header className="App-header">
368
- <h1>Document Detector - React</h1>
369
-
370
- <JaakStampsWrapper
371
- ref={detectorRef}
372
- debug={false}
373
- maskSize={80}
374
- cropMargin={20}
375
- useDocumentClassification={false}
376
- onCaptureCompleted={handleCaptureCompleted}
377
- onIsReady={handleIsReady}
378
- />
379
-
380
- <div className="controls">
381
- <button onClick={preloadModel}>Preload Model</button>
382
- <button onClick={startCapture}>Start Capture</button>
383
- <button onClick={stopCapture}>Stop</button>
384
- <button onClick={resetCapture}>Reset</button>
385
- <button onClick={getImages} disabled={!isCompleted}>
386
- Get Images
387
- </button>
388
- <button onClick={checkStatus}>Check Status</button>
389
- </div>
390
-
391
- {status && (
392
- <div className="status">
393
- <h3>Current Status:</h3>
394
- <p>Video active: {status.isVideoActive ? 'Yes' : 'No'}</p>
395
- <p>Step: {status.captureStep}</p>
396
- <p>Process completed: {status.isProcessCompleted ? 'Yes' : 'No'}</p>
397
- <p>Model preloaded: {status.isModelPreloaded ? 'Yes' : 'No'}</p>
398
- </div>
399
- )}
400
-
401
- {capturedData && (
402
- <div className="results">
403
- <h3>Capture Results:</h3>
404
- <p>Total images: {capturedData.metadata.totalImages}</p>
405
- <p>Timestamp: {capturedData.timestamp}</p>
406
-
407
- {capturedData.front.cropped && (
408
- <div>
409
- <h4>Document Front:</h4>
410
- <img
411
- src={capturedData.front.cropped}
412
- alt="Document front"
413
- style={{ maxWidth: '300px', border: '1px solid #ccc', margin: '10px' }}
414
- />
415
- </div>
416
- )}
417
-
418
- {capturedData.back.cropped && (
419
- <div>
420
- <h4>Document Back:</h4>
421
- <img
422
- src={capturedData.back.cropped}
423
- alt="Document back"
424
- style={{ maxWidth: '300px', border: '1px solid #ccc', margin: '10px' }}
425
- />
426
- </div>
427
- )}
428
- </div>
429
- )}
430
- </header>
431
- </div>
432
- );
611
+ ### Theme Support
612
+
613
+ The component automatically adapts to system dark/light mode preferences:
614
+
615
+ ```css
616
+ /* Light theme (default) */
617
+ jaak-stamps {
618
+ --background-color: #ffffff;
619
+ --text-color: #333333;
620
+ --border-color: #dddddd;
433
621
  }
434
622
 
435
- export default App;
623
+ /* Dark theme */
624
+ @media (prefers-color-scheme: dark) {
625
+ jaak-stamps {
626
+ --background-color: #1a1a1a;
627
+ --text-color: #ffffff;
628
+ --border-color: #444444;
629
+ }
630
+ }
436
631
  ```
437
632
 
438
- ## API Reference
633
+ ## Compatibility
439
634
 
440
- ### Properties/Attributes
635
+ ### Browser Support
441
636
 
442
- | Property | Type | Default | Description |
443
- |----------|------|---------|-------------|
444
- | `debug` | `boolean` | `false` | Enables debug mode to show debugging information |
445
- | `alignmentTolerance` | `number` | `15` | Tolerance in pixels to consider a side aligned |
446
- | `maskSize` | `number` | `80` | Percentage of video that the detection mask will occupy (50-100) |
447
- | `cropMargin` | `number` | `20` | Margin in pixels added to the document crop (0-100) |
448
- | `captureDelay` | `number` | `1500` | Time in milliseconds the document must remain in position before automatically capturing (0-10000) |
449
- | `useDocumentClassification` | `boolean` | `false` | Enables automatic document classification to determine if back capture is required. Only loads the classification model when enabled. |
450
- | `preferredCamera` | `'auto' \| 'front' \| 'back'` | `'auto'` | Defines which camera to use: `'auto'` (automatic based on device), `'front'` (front camera), `'back'` (back camera) |
451
-
452
- ### Public Methods
453
-
454
- | Method | Returns | Description |
455
- |--------|---------|-------------|
456
- | `startCapture()` | `Promise<void>` | Starts the detection and capture process |
457
- | `stopCapture()` | `Promise<void>` | Stops the process and releases the camera |
458
- | `resetCapture()` | `Promise<void>` | Resets the capture process |
459
- | `preloadModel()` | `Promise<{success: boolean, message?: string, error?: string}>` | Preloads the detection model |
460
- | `getCapturedImages()` | `Promise<CapturedImages>` | Gets the captured images (only after completion) |
461
- | `isProcessCompleted()` | `Promise<boolean>` | Checks if the process has been completed |
462
- | `getStatus()` | `Promise<Status>` | Gets the current detector status |
463
- | `skipBackCapture()` | `Promise<void>` | Skips back capture (only available in 'back' step) |
464
- | `getCameraInfo()` | `Promise<CameraInfo>` | Gets detailed information about available cameras |
465
- | `setPreferredCamera(camera)` | `Promise<{success: boolean, selectedCamera: string, availableCameras: number}>` | Sets the preferred camera ('auto', 'front', 'back') |
466
- | `setCaptureDelay(delay)` | `Promise<{success: boolean, captureDelay: number}>` | Sets the wait time before automatically capturing (0-10000ms) |
467
- | `getCaptureDelay()` | `Promise<number>` | Gets the current wait time configured for automatic capture |
468
- | `setTorchEnabled(enabled)` | `Promise<{success: boolean, enabled: boolean}>` | Enables or disables the camera torch/flash |
469
- | `focusAtPoint(x, y)` | `Promise<{success: boolean, coordinates: {x: number, y: number}}>` | Focuses the camera at specific coordinates |
637
+ - **Chrome/Edge**: 80+
638
+ - **Firefox**: 75+
639
+ - **Safari**: 13+
640
+ - **iOS Safari**: 13+
641
+ - **Chrome Android**: 80+
470
642
 
471
- ### Events
643
+ ### Framework Compatibility
472
644
 
473
- | Event | Payload | Description |
474
- |-------|---------|-------------|
475
- | `captureCompleted` | `CapturedImages` | Emitted when the capture process is completed |
476
- | `isReady` | `boolean` | Emitted when the component is ready to start detection (models loaded) |
645
+ - **Vanilla JavaScript**: Full support
646
+ - **React**: 16.8+
647
+ - **Angular**: 12+
648
+ - **Vue.js**: 3+
649
+ - **Svelte**: 3+
477
650
 
478
- ### Data Types
651
+ ### Required Dependencies
479
652
 
480
- ```typescript
481
- interface CapturedImages {
482
- front: {
483
- fullFrame: string | null; // Base64 of the complete front image
484
- cropped: string | null; // Base64 of the cropped front document
485
- };
486
- back: {
487
- fullFrame: string | null; // Base64 of the complete back image
488
- cropped: string | null; // Base64 of the cropped back document
489
- };
490
- timestamp: string; // ISO timestamp of capture
491
- metadata: {
492
- totalImages: number; // Total number of captured images
493
- processCompleted: boolean; // If the process was completed
494
- backCaptureSkipped?: boolean; // If back capture was skipped
495
- };
496
- }
653
+ - **Modern browser** with WebRTC support
654
+ - **Camera access** permission
655
+ - **HTTPS context** (required for camera access)
497
656
 
498
- interface Status {
499
- isVideoActive: boolean; // If the camera is active
500
- captureStep: 'front' | 'back' | 'completed'; // Current process step
501
- hasImages: boolean; // If there are captured images
502
- isProcessCompleted: boolean; // If the process is completed
503
- isModelPreloaded: boolean; // If the model is preloaded
504
- }
657
+ ### Optional Dependencies
658
+
659
+ - **Web Workers** support for enhanced performance
660
+ - **WebAssembly** support for AI model acceleration
661
+
662
+ ## Troubleshooting
663
+
664
+ ### Common Issues
665
+
666
+ #### Camera Not Working
667
+
668
+ **Problem**: Camera preview is black or not showing
669
+
670
+ **Solutions**:
671
+ ```javascript
672
+ // Check camera permissions
673
+ const scanner = document.querySelector('jaak-stamps');
674
+ const cameraInfo = await scanner.getCameraInfo();
675
+ console.log('Available cameras:', cameraInfo.availableCameras);
676
+
677
+ // Try switching cameras
678
+ await scanner.setPreferredCamera('back');
505
679
  ```
506
680
 
507
- ## Workflow
508
-
509
- ### Standard Flow (useDocumentClassification = false)
510
- 1. **Initialization**: Component prepares for detection
511
- 2. **Start capture**: Detection model loads automatically
512
- 3. **Front capture**: User positions document and it captures automatically
513
- 4. **Back request**: Always requests to flip document for back capture
514
- 5. **Skip back button**: Available to manually skip back capture
515
- 6. **Completed**: Emits `captureCompleted` event with corresponding images
516
-
517
- ### Smart Classification Flow (useDocumentClassification = true)
518
- 1. **Initialization**: Component prepares for detection
519
- 2. **Start capture**: Detection and classification models load automatically
520
- 3. **Front capture**: User positions document and it captures automatically
521
- 4. **Automatic classification**: System determines if document requires back capture
522
- 5. **Adaptive flow**:
523
- - **If passport**: Process completes automatically (no back side)
524
- - **If other document**: Requests to flip document for back capture
525
- 6. **Completed**: Emits `captureCompleted` event with corresponding images
526
-
527
- ### Preload Flow (Recommended)
528
- 1. **Model preload**: Call `preloadModel()` to load models in memory
529
- 2. **Optimized start**: When starting capture, uses already loaded models
530
- 3. **Front capture**: Faster detection and classification with preloaded models
531
- 4. **Smart flow**:
532
- - **Document without back**: Process completed immediately
533
- - **Document with back**: Requests back capture
534
- 5. **Completed**: Emits `captureCompleted` event with all images
535
-
536
- ### Document Classification
537
-
538
- #### Classification Enabled Mode (`useDocumentClassification = true`)
539
- When enabled, the system automatically determines if the document requires back capture:
540
- - **Passports**: Automatically skips back capture
541
- - **Other documents**: Require capture of both sides (front and back)
542
- - **Skip back button**: Always available for manual flexibility
543
- - **Optimized loading**: Classification model downloads only when needed
544
-
545
- #### Standard Mode (`useDocumentClassification = false`, default)
546
- When disabled, behavior is lighter and more direct:
547
- - **All documents**: Always requests back capture
548
- - **Skip back button**: Available to manually skip capture
549
- - **No classification**: Only loads detection model
550
- - **Lower bandwidth usage**: Downloads only detection model
551
-
552
- #### Performance Considerations
553
- - **Classification disabled**: Faster loading and lower data usage
554
- - **Classification enabled**: Smarter experience but requires additional model download
555
- - **On-demand loading**: Classification model loads only when first document is captured
556
-
557
- ## System Requirements
558
-
559
- - **Browsers**: Chrome/Edge 88+, Firefox 85+, Safari 14+
560
- - **Camera**: Access to rear camera (mobile) or webcam (desktop)
561
- - **Connection**: Internet connection to load detection models
562
-
563
- ## Privacy and Performance Considerations
564
-
565
- ### Privacy
566
- - Images are processed locally in the browser
567
- - No data is sent to external servers
568
- - All processing runs completely on the client side
569
- - Document classification is performed privately and locally
570
- - Captured images are only available within your application context
571
-
572
- ### Resource Optimization
573
- - **Conditional loading**: Only downloads necessary models based on configuration
574
- - **Lazy loading**: Models load when actually needed
575
- - **Browser cache**: Models are stored locally after first download
576
-
577
- ## Development
578
-
579
- ### Clone the repository
580
- ```bash
581
- git clone [repository-url]
582
- cd jaak-stamps-webcomponent
681
+ #### Slow Performance
682
+
683
+ **Problem**: Component is slow or laggy
684
+
685
+ **Solutions**:
686
+ ```javascript
687
+ // Preload the AI model
688
+ const scanner = document.querySelector('jaak-stamps');
689
+ await scanner.preloadModel();
690
+
691
+ // Reduce capture delay for faster response
692
+ await scanner.setCaptureDelay(1000);
693
+
694
+ // Disable debug mode in production
695
+ scanner.setAttribute('debug', 'false');
583
696
  ```
584
697
 
585
- ### Install dependencies
586
- ```bash
587
- npm install
698
+ #### No Images Captured
699
+
700
+ **Problem**: Capture completes but no images returned
701
+
702
+ **Solutions**:
703
+ ```javascript
704
+ // Check component status
705
+ const status = await scanner.getStatus();
706
+ console.log('Component status:', status);
707
+
708
+ // Verify capture process completion
709
+ const isCompleted = await scanner.isProcessCompleted();
710
+ if (isCompleted) {
711
+ const images = await scanner.getCapturedImages();
712
+ console.log('Images:', images);
713
+ }
588
714
  ```
589
715
 
590
- ### Local development
591
- ```bash
592
- npm start
716
+ #### Memory Issues on Mobile
717
+
718
+ **Problem**: Component crashes on mobile devices
719
+
720
+ **Solutions**:
721
+ ```html
722
+ <!-- Reduce memory usage -->
723
+ <jaak-stamps
724
+ mask-size="60"
725
+ crop-margin="15"
726
+ capture-delay="2000">
727
+ </jaak-stamps>
593
728
  ```
594
729
 
595
- ### Build for production
596
- ```bash
597
- npm run build
730
+ ### Frequently Asked Questions
731
+
732
+ **Q: Can I use this component without HTTPS?**
733
+ A: No, camera access requires HTTPS in production. Use `localhost` for development.
734
+
735
+ **Q: How do I handle multiple document types?**
736
+ A: Enable document classification:
737
+ ```html
738
+ <jaak-stamps use-document-classification="true"></jaak-stamps>
598
739
  ```
599
740
 
600
- ### Run tests
601
- ```bash
602
- npm test
741
+ **Q: Can I customize the detection area?**
742
+ A: Yes, use the `mask-size` property to adjust the detection frame size.
743
+
744
+ **Q: How do I get only the cropped images?**
745
+ A: Access the `cropped` property from the captured images:
746
+ ```javascript
747
+ const images = await scanner.getCapturedImages();
748
+ const croppedFront = images.front.cropped; // Base64 string
603
749
  ```
604
750
 
605
- ## Support
751
+ **Q: Is the component accessible?**
752
+ A: Yes, the component includes ARIA labels and keyboard navigation support.
753
+
754
+ **Q: Can I use this in a Progressive Web App (PWA)?**
755
+ A: Yes, the component is fully compatible with PWAs and works offline after initial load.
606
756
 
607
- To report bugs or request features, please create an issue in the project repository.
757
+ ---
608
758
 
609
759
  ## License
610
760
 
611
- MIT License - see LICENSE file for details.
761
+ MIT © [Jaak AI](https://github.com/jaak-ai)