@jaak.ai/stamps 2.0.0-beta.3 → 2.0.0-beta.5
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 +665 -514
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +1991 -1057
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/my-component/my-component.css +483 -123
- package/dist/collection/components/my-component/my-component.js +915 -1120
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/CameraService.js +590 -0
- package/dist/collection/services/CameraService.js.map +1 -0
- package/dist/collection/services/DetectionService.js +387 -0
- package/dist/collection/services/DetectionService.js.map +1 -0
- package/dist/collection/services/EventBusService.js +42 -0
- package/dist/collection/services/EventBusService.js.map +1 -0
- package/dist/collection/services/LoggerService.js +40 -0
- package/dist/collection/services/LoggerService.js.map +1 -0
- package/dist/collection/services/ServiceContainer.js +66 -0
- package/dist/collection/services/ServiceContainer.js.map +1 -0
- package/dist/collection/services/StateManagerService.js +109 -0
- package/dist/collection/services/StateManagerService.js.map +1 -0
- package/dist/collection/services/factories/DeviceStrategyFactory.js +16 -0
- package/dist/collection/services/factories/DeviceStrategyFactory.js.map +1 -0
- package/dist/collection/services/interfaces/ICameraService.js +2 -0
- package/dist/collection/services/interfaces/ICameraService.js.map +1 -0
- package/dist/collection/services/interfaces/IDetectionService.js +2 -0
- package/dist/collection/services/interfaces/IDetectionService.js.map +1 -0
- package/dist/collection/services/interfaces/IEventBus.js +2 -0
- package/dist/collection/services/interfaces/IEventBus.js.map +1 -0
- package/dist/collection/services/interfaces/ILogger.js +2 -0
- package/dist/collection/services/interfaces/ILogger.js.map +1 -0
- package/dist/collection/services/interfaces/IStateManager.js +2 -0
- package/dist/collection/services/interfaces/IStateManager.js.map +1 -0
- package/dist/collection/services/strategies/HighPerformanceDeviceStrategy.js +30 -0
- package/dist/collection/services/strategies/HighPerformanceDeviceStrategy.js.map +1 -0
- package/dist/collection/services/strategies/IDeviceStrategy.js +2 -0
- package/dist/collection/services/strategies/IDeviceStrategy.js.map +1 -0
- package/dist/collection/services/strategies/LowMemoryDeviceStrategy.js +30 -0
- package/dist/collection/services/strategies/LowMemoryDeviceStrategy.js.map +1 -0
- package/dist/collection/types/component-types.js +2 -0
- package/dist/collection/types/component-types.js.map +1 -0
- package/dist/components/jaak-stamps.js +2011 -1082
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/jaak-stamps-webcomponent.js +1 -1
- package/dist/esm/jaak-stamps.entry.js +1991 -1057
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-9ed19fa4.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-9ed19fa4.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +89 -108
- package/dist/types/components.d.ts +39 -9
- package/dist/types/services/CameraService.d.ts +47 -0
- package/dist/types/services/DetectionService.d.ts +35 -0
- package/dist/types/services/EventBusService.d.ts +9 -0
- package/dist/types/services/LoggerService.d.ts +12 -0
- package/dist/types/services/ServiceContainer.d.ts +27 -0
- package/dist/types/services/StateManagerService.d.ts +15 -0
- package/dist/types/services/factories/DeviceStrategyFactory.d.ts +4 -0
- package/dist/types/services/interfaces/ICameraService.d.ts +34 -0
- package/dist/types/services/interfaces/IDetectionService.d.ts +31 -0
- package/dist/types/services/interfaces/IEventBus.d.ts +16 -0
- package/dist/types/services/interfaces/ILogger.d.ts +8 -0
- package/dist/types/services/interfaces/IStateManager.d.ts +35 -0
- package/dist/types/services/strategies/HighPerformanceDeviceStrategy.d.ts +6 -0
- package/dist/types/services/strategies/IDeviceStrategy.d.ts +21 -0
- package/dist/types/services/strategies/LowMemoryDeviceStrategy.d.ts +6 -0
- package/dist/types/types/component-types.d.ts +36 -0
- package/package.json +3 -3
- package/dist/jaak-stamps-webcomponent/p-14eb13d8.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-14eb13d8.entry.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,610 +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
|
-
- 🎯 **Guía visual** para posicionamiento óptimo del documento
|
|
9
|
-
- 🤖 **Clasificación inteligente** de documentos para determinar si requieren reverso
|
|
10
|
-
- 📋 **Captura adaptativa** - automáticamente detecta si el documento tiene reverso
|
|
11
|
-
- 🖼 **Doble salida** para cada lado: imagen completa y recorte del documento
|
|
12
|
-
- ✂️ **Recorte configurable** con margen personalizable alrededor del documento
|
|
13
|
-
- 🔧 **API programática** para control total del proceso
|
|
14
|
-
- 📱 **Responsive** y compatible con dispositivos móviles
|
|
15
|
-
- ⚡ **Optimización automática** para mejor rendimiento
|
|
8
|
+
## Description
|
|
16
9
|
|
|
17
|
-
|
|
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
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
### NPM/Yarn
|
|
18
33
|
|
|
19
|
-
### NPM
|
|
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
|
+
}>
|
|
97
|
+
```
|
|
98
|
+
|
|
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>
|
|
110
|
+
|
|
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
|
+
}>
|
|
32
146
|
```
|
|
33
147
|
|
|
34
|
-
|
|
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 |
|
|
35
154
|
|
|
36
|
-
###
|
|
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
|
-
// Precargar el modelo (opcional - mejora el rendimiento)
|
|
80
|
-
async function preloadModel() {
|
|
81
|
-
try {
|
|
82
|
-
const result = await detector.preloadModel();
|
|
83
|
-
console.log('Modelo precargado:', result);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error('Error al precargar:', error);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Obtener imágenes después de la captura
|
|
90
|
-
async function getImages() {
|
|
91
|
-
try {
|
|
92
|
-
const images = await detector.getCapturedImages();
|
|
93
|
-
console.log('Imágenes capturadas:', 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
|
-
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();
|
|
115
257
|
|
|
116
|
-
//
|
|
117
|
-
|
|
258
|
+
// Extend HTMLElement for TypeScript
|
|
259
|
+
declare global {
|
|
260
|
+
namespace JSX {
|
|
261
|
+
interface IntrinsicElements {
|
|
262
|
+
'jaak-stamps': any;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
118
266
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
}
|
|
276
|
+
|
|
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>
|
|
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
|
-
// El elemento ya está disponible después de la vista
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async preloadModel() {
|
|
184
|
-
try {
|
|
185
|
-
const result = await this.detectorRef.nativeElement.preloadModel();
|
|
186
|
-
console.log('Modelo precargado:', result);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
console.error('Error al precargar modelo:', 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 al iniciar captura:', 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('Imágenes obtenidas:', images);
|
|
214
|
-
} catch (error) {
|
|
215
|
-
console.error('Error al obtener imágenes:', 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('Componente listo en Angular:', event.detail);
|
|
227
|
-
// El componente está listo para iniciar detección
|
|
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
|
-
crop-margin={cropMargin}
|
|
293
|
-
use-document-classification={useDocumentClassification}
|
|
294
|
-
/>
|
|
295
|
-
);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
export default JaakStampsWrapper;
|
|
569
|
+
/* Status indicator colors */
|
|
570
|
+
--status-success-color: #28a745;
|
|
571
|
+
--status-warning-color: #ffc107;
|
|
572
|
+
--status-error-color: #dc3545;
|
|
573
|
+
}
|
|
299
574
|
```
|
|
300
575
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const result = await detectorRef.current?.preloadModel();
|
|
327
|
-
console.log('Modelo precargado:', result);
|
|
328
|
-
} catch (error) {
|
|
329
|
-
console.error('Error al precargar modelo:', error);
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const startCapture = async () => {
|
|
334
|
-
try {
|
|
335
|
-
await detectorRef.current?.startCapture();
|
|
336
|
-
} catch (error) {
|
|
337
|
-
console.error('Error al iniciar captura:', 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('Imágenes obtenidas:', images);
|
|
355
|
-
} catch (error) {
|
|
356
|
-
console.error('Error al obtener imágenes:', 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>Detector de Documentos - React</h1>
|
|
369
|
-
|
|
370
|
-
<JaakStampsWrapper
|
|
371
|
-
ref={detectorRef}
|
|
372
|
-
debug={false}
|
|
373
|
-
maskSize={90}
|
|
374
|
-
cropMargin={10}
|
|
375
|
-
useDocumentClassification={false}
|
|
376
|
-
onCaptureCompleted={handleCaptureCompleted}
|
|
377
|
-
onIsReady={handleIsReady}
|
|
378
|
-
/>
|
|
379
|
-
|
|
380
|
-
<div className="controls">
|
|
381
|
-
<button onClick={preloadModel}>Precargar Modelo</button>
|
|
382
|
-
<button onClick={startCapture}>Iniciar Captura</button>
|
|
383
|
-
<button onClick={stopCapture}>Detener</button>
|
|
384
|
-
<button onClick={resetCapture}>Reiniciar</button>
|
|
385
|
-
<button onClick={getImages} disabled={!isCompleted}>
|
|
386
|
-
Obtener Imágenes
|
|
387
|
-
</button>
|
|
388
|
-
<button onClick={checkStatus}>Verificar Estado</button>
|
|
389
|
-
</div>
|
|
390
|
-
|
|
391
|
-
{status && (
|
|
392
|
-
<div className="status">
|
|
393
|
-
<h3>Estado Actual:</h3>
|
|
394
|
-
<p>Video activo: {status.isVideoActive ? 'Sí' : 'No'}</p>
|
|
395
|
-
<p>Paso: {status.captureStep}</p>
|
|
396
|
-
<p>Mensaje: {status.statusMessage}</p>
|
|
397
|
-
<p>Proceso completado: {status.isProcessCompleted ? 'Sí' : 'No'}</p>
|
|
398
|
-
<p>Modelo precargado: {status.isModelPreloaded ? 'Sí' : 'No'}</p>
|
|
399
|
-
</div>
|
|
400
|
-
)}
|
|
401
|
-
|
|
402
|
-
{capturedData && (
|
|
403
|
-
<div className="results">
|
|
404
|
-
<h3>Resultados de la Captura:</h3>
|
|
405
|
-
<p>Total de imágenes: {capturedData.metadata.totalImages}</p>
|
|
406
|
-
<p>Timestamp: {capturedData.timestamp}</p>
|
|
407
|
-
|
|
408
|
-
{capturedData.front.cropped && (
|
|
409
|
-
<div>
|
|
410
|
-
<h4>Frente del Documento:</h4>
|
|
411
|
-
<img
|
|
412
|
-
src={capturedData.front.cropped}
|
|
413
|
-
alt="Frente del documento"
|
|
414
|
-
style={{ maxWidth: '300px', border: '1px solid #ccc', margin: '10px' }}
|
|
415
|
-
/>
|
|
416
|
-
</div>
|
|
417
|
-
)}
|
|
418
|
-
|
|
419
|
-
{capturedData.back.cropped && (
|
|
420
|
-
<div>
|
|
421
|
-
<h4>Reverso del Documento:</h4>
|
|
422
|
-
<img
|
|
423
|
-
src={capturedData.back.cropped}
|
|
424
|
-
alt="Reverso del documento"
|
|
425
|
-
style={{ maxWidth: '300px', border: '1px solid #ccc', margin: '10px' }}
|
|
426
|
-
/>
|
|
427
|
-
</div>
|
|
428
|
-
)}
|
|
429
|
-
</div>
|
|
430
|
-
)}
|
|
431
|
-
</header>
|
|
432
|
-
</div>
|
|
433
|
-
);
|
|
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;
|
|
434
601
|
}
|
|
435
602
|
|
|
436
|
-
|
|
603
|
+
/* Responsive design */
|
|
604
|
+
@media (max-width: 768px) {
|
|
605
|
+
jaak-stamps {
|
|
606
|
+
--camera-height: 300px;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
437
609
|
```
|
|
438
610
|
|
|
439
|
-
|
|
611
|
+
### Theme Support
|
|
440
612
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
| Propiedad | Tipo | Predeterminado | Descripción |
|
|
444
|
-
|-----------|------|---------------|-------------|
|
|
445
|
-
| `debug` | `boolean` | `false` | Activa el modo debug para mostrar información de depuración |
|
|
446
|
-
| `alignmentTolerance` | `number` | `10` | Tolerancia en píxeles para considerar un lado alineado |
|
|
447
|
-
| `maskSize` | `number` | `90` | Porcentaje del video que ocupará la máscara de detección (50-100) |
|
|
448
|
-
| `cropMargin` | `number` | `0` | Margen en píxeles agregado al recorte del documento (0-100) |
|
|
449
|
-
| `useDocumentClassification` | `boolean` | `false` | Habilita la clasificación automática de documentos para determinar si solicitar reverso. Solo carga el modelo de clasificación cuando está habilitada. |
|
|
450
|
-
| `preferredCamera` | `'auto' \| 'front' \| 'back'` | `'auto'` | Define qué cámara usar: `'auto'` (automático según dispositivo), `'front'` (cámara frontal), `'back'` (cámara trasera) |
|
|
451
|
-
|
|
452
|
-
### Métodos Públicos
|
|
453
|
-
|
|
454
|
-
| Método | Retorna | Descripción |
|
|
455
|
-
|--------|---------|-------------|
|
|
456
|
-
| `startCapture()` | `Promise<void>` | Inicia el proceso de detección y captura |
|
|
457
|
-
| `stopCapture()` | `Promise<void>` | Detiene el proceso y libera la cámara |
|
|
458
|
-
| `resetCapture()` | `Promise<void>` | Reinicia el proceso de captura |
|
|
459
|
-
| `preloadModel()` | `Promise<{success: boolean, message?: string, error?: string}>` | Precarga el modelo de detección |
|
|
460
|
-
| `getCapturedImages()` | `Promise<CapturedImages>` | Obtiene las imágenes capturadas (solo después de completar) |
|
|
461
|
-
| `isProcessCompleted()` | `Promise<boolean>` | Verifica si el proceso ha sido completado |
|
|
462
|
-
| `getStatus()` | `Promise<Status>` | Obtiene el estado actual del detector |
|
|
463
|
-
| `skipBackCapture()` | `Promise<void>` | Omite la captura del reverso (solo disponible en paso 'back') |
|
|
464
|
-
| `getCameraInfo()` | `Promise<CameraInfo>` | Obtiene información detallada sobre las cámaras disponibles |
|
|
465
|
-
| `setPreferredCamera(camera)` | `Promise<{success: boolean, selectedCamera: string, availableCameras: number}>` | Configura la cámara preferida ('auto', 'front', 'back') |
|
|
466
|
-
|
|
467
|
-
### Eventos
|
|
468
|
-
|
|
469
|
-
| Evento | Payload | Descripción |
|
|
470
|
-
|--------|---------|-------------|
|
|
471
|
-
| `captureCompleted` | `CapturedImages` | Se emite cuando se completa el proceso de captura |
|
|
472
|
-
| `isReady` | `boolean` | Se emite cuando el componente está listo para comenzar la detección (modelos cargados) |
|
|
473
|
-
|
|
474
|
-
### Tipos de Datos
|
|
613
|
+
The component automatically adapts to system dark/light mode preferences:
|
|
475
614
|
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
back: {
|
|
483
|
-
fullFrame: string | null; // Base64 de la imagen completa del reverso
|
|
484
|
-
cropped: string | null; // Base64 del documento recortado del reverso
|
|
485
|
-
};
|
|
486
|
-
timestamp: string; // ISO timestamp de la captura
|
|
487
|
-
metadata: {
|
|
488
|
-
totalImages: number; // Número total de imágenes capturadas
|
|
489
|
-
processCompleted: boolean; // Si el proceso fue completado
|
|
490
|
-
backCaptureSkipped?: boolean; // Si se omitió la captura del reverso
|
|
491
|
-
};
|
|
615
|
+
```css
|
|
616
|
+
/* Light theme (default) */
|
|
617
|
+
jaak-stamps {
|
|
618
|
+
--background-color: #ffffff;
|
|
619
|
+
--text-color: #333333;
|
|
620
|
+
--border-color: #dddddd;
|
|
492
621
|
}
|
|
493
622
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
+
}
|
|
501
630
|
}
|
|
502
631
|
```
|
|
503
632
|
|
|
504
|
-
##
|
|
505
|
-
|
|
506
|
-
###
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
###
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
- **Con clasificación deshabilitada**: Carga más rápida y menor uso de datos
|
|
551
|
-
- **Con clasificación habilitada**: Experiencia más inteligente pero requiere descargar modelo adicional
|
|
552
|
-
- **Carga bajo demanda**: El modelo de clasificación se carga solo cuando se captura el primer documento
|
|
553
|
-
|
|
554
|
-
## Requisitos del Sistema
|
|
555
|
-
|
|
556
|
-
- **Navegadores**: Chrome/Edge 88+, Firefox 85+, Safari 14+
|
|
557
|
-
- **Cámara**: Acceso a cámara trasera (móvil) o webcam (escritorio)
|
|
558
|
-
- **Conexión**: Conexión a internet para cargar los modelos de IA
|
|
559
|
-
|
|
560
|
-
## Consideraciones de Privacidad y Rendimiento
|
|
561
|
-
|
|
562
|
-
### Privacidad
|
|
563
|
-
- Las imágenes se procesan localmente en el navegador
|
|
564
|
-
- No se envían datos a servidores externos
|
|
565
|
-
- Los modelos de IA (detección y clasificación) se ejecutan completamente en el cliente
|
|
566
|
-
- La clasificación de documentos se realiza de forma privada y local
|
|
567
|
-
- Las imágenes capturadas solo están disponibles en el contexto de tu aplicación
|
|
568
|
-
|
|
569
|
-
### Optimización de Recursos
|
|
570
|
-
- **Carga condicional**: Solo descarga modelos necesarios según configuración
|
|
571
|
-
- **Modelo de detección**: ~15MB (siempre requerido)
|
|
572
|
-
- **Modelo de clasificación**: ~5MB (solo si `useDocumentClassification = true`)
|
|
573
|
-
- **Lazy loading**: Los modelos se cargan cuando realmente se necesitan
|
|
574
|
-
- **Cache del navegador**: Los modelos se almacenan localmente después de la primera descarga
|
|
575
|
-
|
|
576
|
-
## Desarrollo
|
|
577
|
-
|
|
578
|
-
### Clonar el repositorio
|
|
579
|
-
```bash
|
|
580
|
-
git clone [repository-url]
|
|
581
|
-
cd jaak-stamps-webcomponent
|
|
633
|
+
## Compatibility
|
|
634
|
+
|
|
635
|
+
### Browser Support
|
|
636
|
+
|
|
637
|
+
- **Chrome/Edge**: 80+
|
|
638
|
+
- **Firefox**: 75+
|
|
639
|
+
- **Safari**: 13+
|
|
640
|
+
- **iOS Safari**: 13+
|
|
641
|
+
- **Chrome Android**: 80+
|
|
642
|
+
|
|
643
|
+
### Framework Compatibility
|
|
644
|
+
|
|
645
|
+
- **Vanilla JavaScript**: Full support
|
|
646
|
+
- **React**: 16.8+
|
|
647
|
+
- **Angular**: 12+
|
|
648
|
+
- **Vue.js**: 3+
|
|
649
|
+
- **Svelte**: 3+
|
|
650
|
+
|
|
651
|
+
### Required Dependencies
|
|
652
|
+
|
|
653
|
+
- **Modern browser** with WebRTC support
|
|
654
|
+
- **Camera access** permission
|
|
655
|
+
- **HTTPS context** (required for camera access)
|
|
656
|
+
|
|
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');
|
|
582
679
|
```
|
|
583
680
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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');
|
|
587
696
|
```
|
|
588
697
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
+
}
|
|
592
714
|
```
|
|
593
715
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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>
|
|
597
728
|
```
|
|
598
729
|
|
|
599
|
-
###
|
|
600
|
-
|
|
601
|
-
|
|
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>
|
|
602
739
|
```
|
|
603
740
|
|
|
604
|
-
|
|
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
|
|
749
|
+
```
|
|
750
|
+
|
|
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.
|
|
605
756
|
|
|
606
|
-
|
|
757
|
+
---
|
|
607
758
|
|
|
608
|
-
##
|
|
759
|
+
## License
|
|
609
760
|
|
|
610
|
-
MIT
|
|
761
|
+
MIT © [Jaak AI](https://github.com/jaak-ai)
|