@jaak.ai/stamps 2.0.0-dev.54 → 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 +660 -510
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,611 +1,761 @@
|
|
|
1
|
-
# Jaak Stamps
|
|
1
|
+
# Jaak Stamps Web Component
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@jaak.ai/stamps)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
[📖 Versión en Español](./README_ES.md)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
###
|
|
249
|
+
### React
|
|
104
250
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```
|
|
251
|
+
```tsx
|
|
252
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
253
|
+
import { defineCustomElements } from '@jaak.ai/stamps/loader';
|
|
109
254
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
362
|
+
### Angular
|
|
363
|
+
|
|
130
364
|
```typescript
|
|
131
|
-
//
|
|
132
|
-
import { Component, ElementRef, ViewChild,
|
|
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-
|
|
383
|
+
selector: 'app-document-scanner',
|
|
136
384
|
template: `
|
|
137
|
-
<div class="container">
|
|
138
|
-
<
|
|
385
|
+
<div class="scanner-container">
|
|
386
|
+
<h2>Document Scanner</h2>
|
|
139
387
|
|
|
140
|
-
<jaak-stamps
|
|
141
|
-
#
|
|
142
|
-
|
|
143
|
-
[
|
|
144
|
-
[
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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="
|
|
159
|
-
<h3>Captured Images
|
|
160
|
-
|
|
161
|
-
<
|
|
162
|
-
|
|
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 {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
500
|
+
isReady = false;
|
|
501
|
+
capturedImages: CapturedImages | null = null;
|
|
203
502
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from 'react';
|
|
547
|
+
## Style Guide
|
|
548
|
+
|
|
549
|
+
### CSS Custom Properties
|
|
243
550
|
|
|
244
|
-
|
|
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
|
-
|
|
248
|
-
|
|
553
|
+
```css
|
|
554
|
+
jaak-stamps {
|
|
555
|
+
/* Camera view dimensions */
|
|
556
|
+
--camera-width: 100%;
|
|
557
|
+
--camera-height: 400px;
|
|
249
558
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
633
|
+
## Compatibility
|
|
439
634
|
|
|
440
|
-
###
|
|
635
|
+
### Browser Support
|
|
441
636
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
###
|
|
643
|
+
### Framework Compatibility
|
|
472
644
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
645
|
+
- **Vanilla JavaScript**: Full support
|
|
646
|
+
- **React**: 16.8+
|
|
647
|
+
- **Angular**: 12+
|
|
648
|
+
- **Vue.js**: 3+
|
|
649
|
+
- **Svelte**: 3+
|
|
477
650
|
|
|
478
|
-
###
|
|
651
|
+
### Required Dependencies
|
|
479
652
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
###
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
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
|
-
|
|
757
|
+
---
|
|
608
758
|
|
|
609
759
|
## License
|
|
610
760
|
|
|
611
|
-
MIT
|
|
761
|
+
MIT © [Jaak AI](https://github.com/jaak-ai)
|