@andresxid/xid_camera_ocr_sdk_react 0.2.0 → 0.3.0
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/CHANGELOG.md +22 -0
- package/README.md +37 -9
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -11
- package/dist/index.d.ts +45 -11
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
The photo is exactly what is inside the guide.
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Behavior change (BREAKING): the captured JPEG passed to `onCaptured` and `onUpload` is now the region inside the on-screen guide rectangle, cropped from the full-resolution frame. There is no perspective warp at capture anymore, even when the detector found the document corners; framing quality depends on the user. If the guide cannot be mapped, the full frame is used and the shape hint is `null`.
|
|
10
|
+
- The crop applies only when the guide is on screen. New option `cropToGuide` (default `false` in the session and the headless hook, which keep the full-frame snapshot); `<XidCameraOcr />` sets it to `true` unless `hideStatusOverlay` is set. Headless users may opt in if their video is `object-fit: cover` filling its box with a centered guide drawn with the newly exported `guideBox` and `guideRatioFor`.
|
|
11
|
+
- The crop reads the guide ratio at capture time, so changing `documentTypes` after mount is honored (new `guideRatio` session option; the hook supplies it).
|
|
12
|
+
- The guide element pins `box-sizing: content-box` (a host `border-box` reset can no longer shrink the inner area) and no longer transitions width or height, so the drawn guide never lags the crop.
|
|
13
|
+
- The photo is always horizontal and upright (document-shaped), whether the phone is held in portrait or landscape; the crop is computed at capture time, so rotation mid-session is handled. Its long side is at most 1024 px (`standard`) or 1600 px (`high`) and is never upscaled.
|
|
14
|
+
- The guide ratio is fixed by the first entry of `documentTypes` (`PASSPORT_MX` → TD3, otherwise ID-1). It no longer changes shape when detection flips the type hint. Detection keeps running only for the quality hints and the optional auto-shutter.
|
|
15
|
+
- The camera is requested with an ideal `aspectRatio` matching the viewport orientation (9:16 portrait, 16:9 landscape) instead of always 16:9, with the same ideal resolution.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Full demo: the results panel shows the captured photo with its pixel dimensions and size in KB.
|
|
20
|
+
|
|
21
|
+
### Notes
|
|
22
|
+
|
|
23
|
+
- The `warpDocument`, `warpDocumentImageData` and `warpedSnapshotToBlob` exports are kept for compatibility; the session no longer uses them.
|
|
24
|
+
|
|
3
25
|
## 0.2.0
|
|
4
26
|
|
|
5
27
|
Manual capture by default and a shutter that can live anywhere.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @andresxid/xid_camera_ocr_sdk_react
|
|
2
2
|
|
|
3
|
-
React component and headless hook that capture an identity document in the browser: camera, on-device OpenCV edge detection, quality hints and a manual shutter (an optional auto-shutter is available). It returns a
|
|
3
|
+
React component and headless hook that capture an identity document in the browser: camera, on-device OpenCV edge detection, quality hints and a manual shutter (an optional auto-shutter is available). It returns a JPEG of exactly the on-screen guide region to your `onUpload` callback and knows nothing about your backend.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -32,13 +32,13 @@ export function Capture() {
|
|
|
32
32
|
}
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
`onUpload` receives the
|
|
35
|
+
`onUpload` receives the JPEG `Blob` of the guide region (see Captured photo) and a shape hint (`'INE' | 'PASSPORT_MX' | 'OTHER' | null`). It must return the backend JSON. The hint is a card shape, not a document-type claim: your backend decides the real type.
|
|
36
36
|
|
|
37
37
|
Need your own UI? `useXidCameraOcr(options)` is the headless hook. It returns `videoRef`, `status`, `frame`, `result`, `error`, `start`, `stop`, `captureNow` and `retake`; attach `videoRef` to a `<video playsInline muted>` and render whatever you like. `XidCameraOcrSession` is the framework-free class underneath both.
|
|
38
38
|
|
|
39
39
|
## Manual capture and custom shutter
|
|
40
40
|
|
|
41
|
-
The user takes the photo. A fixed guide rectangle with a dark mask is always shown while the camera is live; the detector keeps running only for the quality hints (`blurry`, `glare`, `too_dark`, `too_small`, `no_document`) and
|
|
41
|
+
The user takes the photo. A fixed guide rectangle with a dark mask is always shown while the camera is live; the detector keeps running only for the quality hints (`blurry`, `glare`, `too_dark`, `too_small`, `no_document`) and for the optional auto-shutter. To capture automatically, pass `autoCapture: true`.
|
|
42
42
|
|
|
43
43
|
To put the shutter anywhere, hide the built-in one and drive the component through its `ref`:
|
|
44
44
|
|
|
@@ -73,6 +73,18 @@ export function Capture() {
|
|
|
73
73
|
|
|
74
74
|
`XidCameraOcrHandle` has `capture(): Promise<void>` (same as the built-in shutter; ignored unless the camera is live), `retake(): void` (same as the built-in retake; ignored unless the run is `done` or `error`) and `start(): Promise<void>`. Errors behave like the built-in buttons: they are not thrown and reach `onError`. `hideShutter` hides only the shutter; the retake button stays.
|
|
75
75
|
|
|
76
|
+
## Captured photo
|
|
77
|
+
|
|
78
|
+
The JPEG passed to `onCaptured` and `onUpload` is exactly the region inside the guide rectangle you see on screen, cropped from the full-resolution camera frame at the moment of capture. Nothing outside the guide is uploaded.
|
|
79
|
+
|
|
80
|
+
- **Crop only, no perspective correction.** The SDK does not deskew or warp the document, even when the detector found its corners. If the card is tilted, rotated or only partly inside the guide, the photo shows it that way: framing quality depends on the user, so keep the hints visible.
|
|
81
|
+
- **Always horizontal.** The guide is document-shaped (wider than tall) and the photo keeps that shape and stays upright whether the phone is held in portrait or landscape. The crop is computed at capture time, so rotating the phone mid-session is handled.
|
|
82
|
+
- **Only with the built-in guide.** `<XidCameraOcr />` crops because it draws the guide itself. With `hideStatusOverlay` the guide is not drawn, so the component takes the full frame instead. The hook and `XidCameraOcrSession` also take the full frame unless you pass `cropToGuide: true`. Opting in is correct only when your `<video>` is `object-fit: cover` filling its box and you draw a centered guide with the exported `guideBox(container, guideRatioFor(documentTypes))` (same maths the crop uses).
|
|
83
|
+
- **Fixed guide ratio.** The guide ratio comes from the first entry of `documentTypes`: `PASSPORT_MX` uses the TD3 passport ratio (125:88), anything else the ID-1 card ratio (85.6:53.98). Detection never reshapes the guide.
|
|
84
|
+
- **Size.** The crop is downscaled so its long side is at most 1024 px (`captureResolution: 'standard'`) or 1600 px (`'high'`); it is never upscaled, so a low-resolution camera yields a smaller photo. JPEG quality is `captureQuality`.
|
|
85
|
+
- **Camera orientation.** The camera is requested with an ideal aspect that matches the viewport: 9:16 in a portrait viewport and 16:9 in a landscape one, so an upright phone does not waste most of the sensor frame. It is read when the camera starts; the browser may still deliver another shape.
|
|
86
|
+
- **Fallback.** If the guide cannot be mapped (the video has no layout yet) or the crop fails, the full unmodified camera frame is uploaded and the shape hint is `null`.
|
|
87
|
+
|
|
76
88
|
## Backend pattern
|
|
77
89
|
|
|
78
90
|
The browser never holds a credential. It posts the JPEG to **your** backend, and your backend signs and forwards the request as `multipart/form-data` with the file in the `document` field:
|
|
@@ -131,9 +143,9 @@ Pass a `theme` object. The component writes a `--xco-*` custom property to its r
|
|
|
131
143
|
|
|
132
144
|
| Variable | Default | Used by |
|
|
133
145
|
|---|---|---|
|
|
134
|
-
| `--xco-accent` | `#22c55e` |
|
|
135
|
-
| `--xco-warning` | `#eab308` |
|
|
136
|
-
| `--xco-danger` | `#ef4444` |
|
|
146
|
+
| `--xco-accent` | `#22c55e` | Guide border when the frame is ready to capture, spinner arc |
|
|
147
|
+
| `--xco-warning` | `#eab308` | Guide border while the document is seen but the frame is not yet good |
|
|
148
|
+
| `--xco-danger` | `#ef4444` | Guide border with no document, and on error |
|
|
137
149
|
| `--xco-background` | `#000` | Camera container background |
|
|
138
150
|
| `--xco-mask` | `rgba(0,0,0,0.4)` | Dim area outside the guide |
|
|
139
151
|
| `--xco-hint-bg` | `rgba(0,0,0,0.7)` | Hint banner background |
|
|
@@ -200,10 +212,11 @@ The tips card (`tip_light`, `tip_glare`, `tip_focus`, `tips_dismiss`) shows whil
|
|
|
200
212
|
| `onError` | `(err: Error) => void` | | Camera, detector or upload failure |
|
|
201
213
|
| `autoStart` | `boolean` | `true` | Start the stream on mount |
|
|
202
214
|
| `autoCapture` | `boolean` | `false` | Opt-in auto-shutter on a stable, good frame. By default the user takes the photo with the shutter |
|
|
203
|
-
| `documentTypes` | `DocumentType[]` | `['INE', 'PASSPORT_MX']` |
|
|
215
|
+
| `documentTypes` | `DocumentType[]` | `['INE', 'PASSPORT_MX']` | The first entry fixes the guide and photo ratio (`PASSPORT_MX` → TD3, otherwise ID-1); does not filter detection |
|
|
216
|
+
| `cropToGuide` | `boolean` | `false` (component: `true` unless `hideStatusOverlay`) | Crop the photo to the guide. Headless opt-in requires an `object-fit: cover` video and a centered guide drawn with `guideBox` (see Captured photo) |
|
|
204
217
|
| `stableFramesRequired` | `number` | `10` | Stable frames before auto-capture (only with `autoCapture: true`) |
|
|
205
218
|
| `captureQuality` | `number` | `0.92` | JPEG quality, 0 to 1 |
|
|
206
|
-
| `captureResolution` | `'standard' \| 'high'` | `'standard'` |
|
|
219
|
+
| `captureResolution` | `'standard' \| 'high'` | `'standard'` | Max long side of the photo, 1024 px or 1600 px (never upscaled) |
|
|
207
220
|
| `opencvUrl` | `string` | packaged `opencv.js` | Self-hosted `dist/opencv.js`; required with `workerUrl` for CJS and non-Vite bundlers (see Known limitations) |
|
|
208
221
|
| `workerUrl` | `string` | packaged `cv-worker.js` | Self-hosted `dist/cv-worker.js`, spawned as a module worker; required with `opencvUrl` for CJS and non-Vite bundlers |
|
|
209
222
|
| `theme` | `XidCameraOcrTheme` | `{}` | See Theming |
|
|
@@ -244,9 +257,24 @@ Why each directive:
|
|
|
244
257
|
- `img-src 'self' blob:`: the component itself loads no images. `blob:` is only needed if your page previews the captured `Blob` with `URL.createObjectURL`; drop it otherwise.
|
|
245
258
|
- `connect-src 'self'`: your upload endpoint on the same origin. Add your API origin here if it differs.
|
|
246
259
|
|
|
260
|
+
## Upgrading
|
|
261
|
+
|
|
262
|
+
### From 0.1.x
|
|
263
|
+
|
|
264
|
+
- **No auto-shutter by default.** The user now takes the photo. To keep the 0.1.x behavior, pass `autoCapture: true`.
|
|
265
|
+
- **Fixed guide.** The guide rectangle is always shown and the detected-corner outline is gone.
|
|
266
|
+
- **Different photo.** The upload is now the guide region, cropped and not deskewed, instead of the perspective-corrected document. See Captured photo.
|
|
267
|
+
- Everything in "From 0.2.x" below also applies.
|
|
268
|
+
|
|
269
|
+
### From 0.2.x
|
|
270
|
+
|
|
271
|
+
- **Different photo.** `onCaptured` and `onUpload` now receive only the guide region (always horizontal) instead of the warped document or the full frame. If your backend relied on the full frame, pass `hideStatusOverlay` (no guide, full frame) or use the hook without `cropToGuide`.
|
|
272
|
+
- **Guide ratio no longer follows detection.** It is fixed by the first entry of `documentTypes`; put `PASSPORT_MX` first for a passport-only flow.
|
|
273
|
+
- **Headless users:** the hook and `XidCameraOcrSession` keep uploading the full frame unless you opt in with `cropToGuide: true`.
|
|
274
|
+
|
|
247
275
|
## Known limitations
|
|
248
276
|
|
|
249
|
-
- **Detection quality (0.4.0 gate not met).** The detector was inherited from 0.4.0 and has not been improved here. On the detection eval, hand-occlusion IoU is 0.492 against a 0.90 threshold and clutter IoU is 0.534 against 0.80. The false-positive rate is unmeasured. Keep the manual shutter reachable
|
|
277
|
+
- **Detection quality (0.4.0 gate not met).** The detector was inherited from 0.4.0 and has not been improved here. On the detection eval, hand-occlusion IoU is 0.492 against a 0.90 threshold and clutter IoU is 0.534 against 0.80. The false-positive rate is unmeasured. Keep the manual shutter reachable: detection only drives the quality hints and the optional auto-shutter, so do not rely on it to find the document.
|
|
250
278
|
- **Phone validation.** Phone validation (iOS Safari / Android Chrome) is in progress; results will be published in a later release.
|
|
251
279
|
- **CJS build needs `workerUrl` and `opencvUrl`.** The CJS bundle resolves `import.meta.url` against the document, not the package. CJS consumers must serve `dist/cv-worker.js` and `dist/opencv.js` from their own origin (copy them from `node_modules/@andresxid/xid_camera_ocr_sdk_react/dist/`) and pass both URLs; otherwise the detector cannot start and only manual capture works. ESM consumers on Vite need nothing.
|
|
252
280
|
- **Non-Vite bundlers are untested** (webpack, Rollup alone, esbuild, Next.js). Host `dist/cv-worker.js` and `dist/opencv.js` yourself and pass both `workerUrl` and `opencvUrl`; otherwise only manual capture works.
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var xt=()=>typeof document>"u"?new URL(`file:${__filename}`).href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?document.currentScript.src:new URL("main.js",document.baseURI).href,C=xt();async function ce(e,t){let r={audio:false,video:{facingMode:{ideal:"environment"},width:{ideal:1920,min:640},height:{ideal:1080,min:480},aspectRatio:{ideal:1.7777777777777777}}};if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw new Error("Camera unavailable: getUserMedia requires a secure context (HTTPS or localhost) and a browser with MediaDevices support.");let n;try{n=await navigator.mediaDevices.getUserMedia(t??r);}catch(s){if(!t)n=await navigator.mediaDevices.getUserMedia({audio:false,video:true});else throw s}e.srcObject=n,e.muted=true,e.playsInline=true,e.autoplay=true;let a=n.getVideoTracks()[0],i=a.getCapabilities?.();return i&&"focusMode"in i&&i.focusMode?.includes("continuous")&&a.applyConstraints({advanced:[{focusMode:"continuous"}]}).catch(()=>{}),await e.play().catch(()=>{}),n}function ue(e,t){e.srcObject===t&&(e.srcObject=null),t?.getTracks().forEach(r=>r.stop());}async function de(e,t=.92){let r=e.videoWidth,n=e.videoHeight;if(!r||!n)throw new Error("Video stream not ready (videoWidth=0)");let a=document.createElement("canvas");a.width=r,a.height=n;let i=a.getContext("2d");if(!i)throw new Error("2D canvas context unavailable");return i.drawImage(e,0,0,r,n),new Promise((s,o)=>{a.toBlob(l=>l?s(l):o(new Error("Canvas toBlob returned null")),"image/jpeg",t);})}var Q=null;function pe(e,t=640){let r=e.videoWidth,n=e.videoHeight;if(!r||!n)return null;let a=Math.round(t),i=Math.round(n*t/r);Q??=document.createElement("canvas"),(Q.width!==a||Q.height!==i)&&(Q.width=a,Q.height=i);let s=Q.getContext("2d",{willReadFrequently:true});return s?(s.drawImage(e,0,0,a,i),s.getImageData(0,0,a,i)):null}function _e(e){let t=e.videoWidth,r=e.videoHeight;if(!t||!r)return null;let n=document.createElement("canvas");n.width=t,n.height=r;let a=n.getContext("2d",{willReadFrequently:true});return a?(a.drawImage(e,0,0,t,r),a.getImageData(0,0,t,r)):null}function Ae(e,t=.92){let r=document.createElement("canvas");r.width=e.width,r.height=e.height;let n=r.getContext("2d");return n?(n.putImageData(e,0,0),new Promise((a,i)=>r.toBlob(s=>s?a(s):i(new Error("Canvas toBlob returned null")),"image/jpeg",t))):Promise.reject(new Error("2D canvas context unavailable"))}function me(e){let t=e.getCapabilities;return typeof t!="function"?false:t.call(e).torch===true}async function he(e,t){try{return await e.applyConstraints({advanced:[{torch:t}]}),!0}catch{return false}}var St="@andresxid/xid_camera_ocr_sdk_react";function Fe(e,t){return e.pathname.endsWith(`/${St}/${t}`)}function Pe(e){return new URL(e,C)}function Oe(){return new URL("@andresxid/xid_camera_ocr_sdk_react/opencv.js",C)}function ye(){let e=Oe();return Fe(e,"opencv.js")?Pe("./opencv.js").href:e.href}function Ie(e){return e!==void 0?new Worker(e,{type:"module"}):Fe(Oe(),"opencv.js")?new Worker(Pe("./cv-worker.js"),{type:"module"}):new Worker(new URL("@andresxid/xid_camera_ocr_sdk_react/cv-worker.js",C),{type:"module"})}function De(e){if(!e||typeof e!="object")return null;let t=e;switch(t.type){case "ready":return {type:"ready"};case "init-error":return typeof t.error=="string"?{type:"init-error",error:t.error}:null;case "error":return typeof t.id=="number"&&typeof t.error=="string"?{type:"error",id:t.id,error:t.error}:null;case "analyze-result":return typeof t.id=="number"&&t.result&&typeof t.result=="object"?{type:"analyze-result",id:t.id,result:t.result}:null;case "warp-result":return typeof t.id=="number"&&typeof t.width=="number"&&typeof t.height=="number"&&t.buffer instanceof ArrayBuffer?{type:"warp-result",id:t.id,width:t.width,height:t.height,buffer:t.buffer}:null;default:return null}}function He(e){let t=new Uint8ClampedArray(e.data.byteLength);return t.set(e.data),t.buffer}var wt=e=>e instanceof Error?e:new Error(String(e)),j=class{initTimeoutMs;worker=null;readyPromise=null;rejectInit=null;initTimer=null;ready=false;pending=new Map;nextId=1;constructor(t={}){this.initTimeoutMs=t.initTimeoutMs??3e4;}init(t=ye(),r){if(this.readyPromise)return this.readyPromise;let n;try{n=Ie(r);}catch(i){return Promise.reject(wt(i))}this.worker=n;let a=new Promise((i,s)=>{this.rejectInit=s,this.initTimer=setTimeout(()=>this.fail(new Error(`OpenCV worker not ready within ${this.initTimeoutMs} ms`)),this.initTimeoutMs),n.addEventListener("message",o=>{if(this.worker!==n)return;let l=De(o.data);if(!l)return;if(l.type==="ready"){this.clearInitTimer(),this.rejectInit=null,this.ready=true,i();return}if(l.type==="init-error"){this.fail(new Error(l.error));return}let c=this.pending.get(l.id);c&&(this.pending.delete(l.id),l.type==="error"?c.reject(new Error(l.error)):c.resolve(l));}),n.addEventListener("error",o=>{this.worker===n&&this.fail(new Error(o.message||"Worker error"));}),n.addEventListener("messageerror",o=>{if(this.worker!==n)return;let l=new Error("Worker message could not be deserialized"),c=o.data?.id,d=typeof c=="number"?this.pending.get(c):void 0;if(typeof c=="number"&&d){this.pending.delete(c),d.reject(l);return}this.fail(l);});});return this.readyPromise=a,n.postMessage({type:"init",opencvUrl:t}),a}isReady(){return this.ready&&this.worker!==null}async analyze(t,r={}){let n=He(t),a=r.prior,i=await this.request(s=>({type:"analyze",id:s,width:t.width,height:t.height,buffer:n,...a?{prior:a}:{}}),[n]);if(i.type!=="analyze-result")throw new Error(`Unexpected worker response: ${i.type}`);return i.result}async warp(t,r,n,a=1024){let i=He(t),s=await this.request(o=>({type:"warp",id:o,width:t.width,height:t.height,buffer:i,corners:r,docType:n,longSide:a}),[i]);if(s.type!=="warp-result")throw new Error(`Unexpected worker response: ${s.type}`);return new ImageData(new Uint8ClampedArray(s.buffer),s.width,s.height)}terminate(){this.rejectInit=null,this.shutdown(new Error("Worker terminated"));}fail(t){let r=this.rejectInit;this.rejectInit=null,this.shutdown(t),r?.(t);}clearInitTimer(){this.initTimer!==null&&clearTimeout(this.initTimer),this.initTimer=null;}shutdown(t){this.clearInitTimer(),this.worker?.terminate(),this.worker=null,this.readyPromise=null,this.ready=false;let r=[...this.pending.values()];this.pending.clear();for(let n of r)n.reject(t);}request(t,r){let n=this.worker;if(!n||!this.ready)return Promise.reject(new Error("CVWorkerClient not ready"));let a=t(this.nextId++),i=new Promise((s,o)=>this.pending.set(a.id,{resolve:s,reject:o}));return n.postMessage(a,r),i}};var I=1.585772508336421,H=125/88,Rt=1.503,Tt=.05,ee=1.25,fe=1.75,vt=.25;function be(e){if(e.length!==4)throw new Error(`orderCorners expects 4 points, got ${e.length}`);let t=(e[0].x+e[1].x+e[2].x+e[3].x)/4,r=(e[0].y+e[1].y+e[2].y+e[3].y)/4,n=[...e].sort((i,s)=>Math.atan2(i.y-r,i.x-t)-Math.atan2(s.y-r,s.x-t)),a=0;for(let i=1;i<4;i++){let s=n[i],o=n[a];(s.x+s.y<o.x+o.y||s.x+s.y===o.x+o.y&&s.y<o.y)&&(a=i);}return [0,1,2,3].map(i=>n[(a+i)%4])}function L(e){let t=0;for(let r=0;r<e.length;r++){let n=e[r],a=e[(r+1)%e.length];t+=n.x*a.y-a.x*n.y;}return Math.abs(t)/2}function te(e){let[t,r,n,a]=e;return {top:Math.hypot(r.x-t.x,r.y-t.y),right:Math.hypot(n.x-r.x,n.y-r.y),bottom:Math.hypot(n.x-a.x,n.y-a.y),left:Math.hypot(a.x-t.x,a.y-t.y)}}function kt(e){let t=[0,0,0,0];for(let r=0;r<4;r++){let n=e[(r+3)%4],a=e[r],i=e[(r+1)%4],s=n.x-a.x,o=n.y-a.y,l=i.x-a.x,c=i.y-a.y,d=Math.hypot(s,o),p=Math.hypot(l,c);if(d===0||p===0){t[r]=0;continue}let m=Math.min(1,Math.max(-1,(s*l+o*c)/(d*p)));t[r]=Math.acos(m)*180/Math.PI;}return t}function re(e,t=70,r=110){let n=te(e);return n.top<=0||n.right<=0||n.bottom<=0||n.left<=0||!Et(e)||L(e)<=0?false:kt(e).every(a=>a>=t&&a<=r)}function Et(e){let t=0;for(let r=0;r<4;r++){let n=e[r],a=e[(r+1)%4],i=e[(r+2)%4],s=(a.x-n.x)*(i.y-a.y)-(a.y-n.y)*(i.x-a.x);if(s===0)return false;let o=Math.sign(s);if(t===0)t=o;else if(o!==t)return false}return true}function z(e){let t=te(e),r=(t.top+t.bottom)/2,n=(t.left+t.right)/2,a=Math.min(r,n);return a<=0?0:Math.max(r,n)/a}function Mt(e){if(!Number.isFinite(e))return 0;let t=Math.min(Math.abs(e-I),Math.abs(e-H));return Math.max(0,1-t/vt)}function Le(e){return .5*e.areaRatio+.3*e.rectangularity+.2*Mt(e.aspect)}function ne(e){let t=e>=Rt?I:H;return Math.abs(e-t)/t<=Tt?t===I?"INE":"PASSPORT_MX":"OTHER"}function ge(e){return e>=ee&&e<=fe}function Ue(e,t){return e==="INE"?I:e==="PASSPORT_MX"?H:t}var V=1024,Qe=1600;function qe(e,t,r,n,a=V){let i=At(Ue(n,z(r))),s=Math.max(1,Math.round(a/i)),o=te(r),l=o.top+o.bottom<o.left+o.right,c=n==="INE"||n==="PASSPORT_MX",d=l&&c?[r[3],r[0],r[1],r[2]]:r,p=!l||c,m=p?a:s,u=p?s:a,x=null,S=null,y=null,b=null,h=null,f=null;try{return x=e.matFromImageData(t),S=new e.Mat,y=e.matFromArray(4,1,e.CV_32FC2,[d[0].x,d[0].y,d[1].x,d[1].y,d[2].x,d[2].y,d[3].x,d[3].y]),b=e.matFromArray(4,1,e.CV_32FC2,[0,0,m,0,m,u,0,u]),h=e.getPerspectiveTransform(y,b),e.warpPerspective(x,S,h,new e.Size(m,u),e.INTER_LINEAR,e.BORDER_CONSTANT,new e.Scalar(0,0,0,255)),S.channels()!==4&&(f=new e.Mat,e.cvtColor(S,f,e.COLOR_BGR2RGBA)),Ft(new Uint8ClampedArray((f??S).data),m,u)}finally{for(let T of [f,h,b,y,S,x])T?.delete();}}function Ne(e,t,r,n,a=V){let i=t.getContext("2d",{willReadFrequently:true});if(!i)throw new Error("2D canvas context unavailable");let s=i.getImageData(0,0,t.width,t.height),o=qe(e,s,r,n,a),l=document.createElement("canvas");l.width=o.width,l.height=o.height;let c=l.getContext("2d");if(!c)throw new Error("2D canvas context unavailable (output)");return c.putImageData(o,0,0),l}async function _t(e,t,r,n,a=.92,i=V){let s=r?Ne(e,t,r,n,i):t;return new Promise((o,l)=>{s.toBlob(c=>c?o(c):l(new Error("Canvas toBlob returned null")),"image/jpeg",a);})}function At(e){return Number.isFinite(e)?Math.min(fe,Math.max(ee,e)):ee}function Ft(e,t,r){return typeof globalThis.ImageData=="function"?new ImageData(e,t,r):{data:e,width:t,height:r,colorSpace:"srgb"}}var M={brightnessMin:.18,brightnessMax:.9,blurMin:60,glareMax:.04,fillMin:.25,glarePixel:248};function G(e,t,r){let n=new Int32Array(r*2);for(let a=0;a<r;a++){let i=a+.5,s=1/0,o=-1/0;for(let l=0;l<4;l++){let c=e[l],d=e[(l+1)%4],p=Math.min(c.y,d.y),m=Math.max(c.y,d.y);if(i<p||i>m||m===p)continue;let u=c.x+(i-c.y)*(d.x-c.x)/(d.y-c.y);u<s&&(s=u),u>o&&(o=u);}if(s===1/0){n[a*2]=0,n[a*2+1]=0;continue}n[a*2]=Math.max(0,Math.ceil(s-.5)),n[a*2+1]=Math.min(t,Math.floor(o+.5));}return n}function xe(e,t){let{data:r,width:n,height:a}=e,i=G(t,n,a),s=M.glarePixel,o=0,l=0,c=0,d=0,p=0,m=0;for(let f=0;f<a;f++){let T=i[f*2],R=i[f*2+1];if(T>=R)continue;let k=0,v=0;f>0&&f<a-1&&(k=Math.max(T,i[(f-1)*2],i[(f+1)*2])+1,v=Math.min(R,i[(f-1)*2+1],i[(f+1)*2+1])-1);let w=f*n;for(let E=T;E<R;E++){let F=w+E,_=r[F];if(o+=_,_>=s&&l++,c++,E>=k&&E<v){let O=4*_-r[F-n]-r[F+n]-r[F-1]-r[F+1];d+=O,p+=O*O,m++;}}}let u=c>0?o/c/255:0,x=c>0?l/c:0,S=m>0?Math.max(0,p/m-(d/m)**2):0,y=n*a,b=y>0?L(t)/y:0,h=u>=M.brightnessMin&&u<=M.brightnessMax&&S>=M.blurMin&&x<=M.glareMax&&b>=M.fillMin;return {brightness:u,blurScore:S,glareRatio:x,docFillRatio:b,ok:h}}function Xe(e){let{data:t}=e;if(t.length===0)return 0;let r=0;for(let n=0;n<t.length;n++)r+=t[n];return r/t.length/255}function Se(e){if(!e.detected||!e.quality)return "no_document";let t=e.quality;return !Number.isFinite(t.docFillRatio)||t.docFillRatio<M.fillMin?"too_small":!Number.isFinite(t.brightness)||t.brightness<M.brightnessMin?"too_dark":!Number.isFinite(t.glareRatio)||t.glareRatio>M.glareMax||t.brightness>M.brightnessMax?"glare":!Number.isFinite(t.blurScore)||t.blurScore<M.blurMin?"blurry":e.stableFrames<e.stableFramesRequired?"hold_still":"ready"}var Pt=M.brightnessMin;function Be(e){return !e.torchSupported||e.torchOn?false:(e.quality?e.quality.brightness:e.frameBrightness)<Pt}var Ce=Object.freeze({idle:"Toca para activar la c\xE1mara",starting:"Abriendo la c\xE1mara\u2026",loading_detector:"Preparando la c\xE1mara\u2026",no_document:"Coloca tu identificaci\xF3n dentro del recuadro",too_small:"Acerca m\xE1s tu identificaci\xF3n",too_dark:"Hay poca luz, busca un lugar m\xE1s iluminado",glare:"Hay un reflejo, inclina un poco tu identificaci\xF3n",blurry:"Espera a que la c\xE1mara enfoque",hold_still:"Mant\xE9n la c\xE1mara quieta",ready:"\xA1Perfecto! Toma la foto",capturing:"Tomando la foto\u2026",uploading:"Revisando tu identificaci\xF3n\u2026",done:"\xA1Listo!",error:"Algo sali\xF3 mal, int\xE9ntalo de nuevo",detector_unavailable:"Toma la foto con el bot\xF3n",retake:"Tomar otra vez",capture:"Tomar foto",tip_light:"Busca un lugar con buena luz",tip_glare:"Evita reflejos: no apuntes una l\xE1mpara directo a tu identificaci\xF3n",tip_focus:"Espera a que la c\xE1mara enfoque y luego toca el bot\xF3n",tips_dismiss:"Entendido"}),Ot=Object.freeze({idle:"Ready to start",starting:"Starting camera\u2026",loading_detector:"Loading detector\u2026",no_document:"Place the document inside the frame",too_small:"Move closer to the document",too_dark:"Find more light",glare:"Tilt the document to avoid glare",blurry:"Hold still to focus",hold_still:"Hold the camera steady",ready:"Perfect! Take the photo",capturing:"Capturing\u2026",uploading:"Processing document\u2026",done:"Done",error:"Something went wrong \u2014 try again",detector_unavailable:"Detector unavailable \u2014 capture manually",retake:"Capture again",capture:"Capture now",tip_light:"Find a well-lit place",tip_glare:"Avoid glare: don't point a lamp straight at your ID",tip_focus:"Wait for the camera to focus, then tap the button",tips_dismiss:"Got it"}),It=["loading_detector","capturing","uploading","done","error"];function Dt(e){return It.includes(e)}function Y(e,t){return t?.[e]??Ce[e]}function we(e){let t={...Ce};for(let r of Object.keys(t))t[r]=Y(r,e);return t}function ae(e,t,r){return e==="idle"||e==="stopped"?Y("idle",r):e==="starting"?Y("starting",r):Dt(e)?Y(e,r):Y(t??"no_document",r)}var Ht=.05;function Lt(e,t,r){let n=r.width/t.width,a=r.height/t.height;return e.map(i=>({x:i.x*n,y:i.y*a}))}function Ut(e,t){return e.map(r=>({x:Math.min(t.width,Math.max(0,r.x)),y:Math.min(t.height,Math.max(0,r.y))}))}function We(e,t,r){let n=Ut(Lt(e,t,r),r);return !re(n)||L(n)<Ht*r.width*r.height?null:n}var je={stableFrames:0,isStable:false,quad:null,typeHint:null},K=class{required;maxRatio;switchFrames;current=je;candidate=null;constructor(t={}){this.required=t.stableFramesRequired??10,this.maxRatio=t.maxDisplacementRatio??.015,this.switchFrames=Math.max(1,t.hintSwitchFrames??3);}get snapshot(){return this.current}reset(){this.current=je,this.candidate=null;}update(t,r){if(!t)return this.reset(),this.current;let n=Math.hypot(r.width,r.height)*this.maxRatio,a=this.current,i=a.quad!==null&&a.quad.every((l,c)=>Math.hypot(t.quad[c].x-l.x,t.quad[c].y-l.y)<=n),s=a.typeHint??t.typeHint,o=i?a.stableFrames+1:1;if(t.typeHint===s)this.candidate=null;else {let l=this.candidate?.typeHint===t.typeHint?this.candidate.frames+1:1;this.candidate={typeHint:t.typeHint,frames:l},l>=this.switchFrames&&(s=t.typeHint,o=1,this.candidate=null);}return this.current={stableFrames:o,isStable:o>=this.required,quad:t.quad,typeHint:s},this.current}};var qt=640,Nt=10,ze=1500,Xt=35e3,Bt=5,Wt={createWorkerClient:e=>new j(e),attachCamera:e=>ce(e),detachCamera:ue,sampleFrame:pe,grabFrame:_e,encodeJpeg:Ae,snapshotToBlob:de,supportsTorch:me,setTorch:he,now:()=>performance.now()},Ve=e=>e.provenance==="reduced"||e.provenance==="lowres",Ge=e=>e instanceof Error?e:new Error(String(e)),Ye=e=>e.map(t=>({x:t.x,y:t.y})),$=class{opts;deps;stream=null;track=null;cv=null;generation=0;timer=null;frameCallback=null;inFlight=null;warmupUntil=0;capturing=false;autoShutterSpent=false;torchSupported=false;torchOn=false;torchRejected=false;weakRun=false;prior=null;stability;state={status:"idle",lastFrame:null,lastResult:null,lastError:null};constructor(t,r={}){this.opts={videoElement:t.videoElement,documentTypes:t.documentTypes??["INE","PASSPORT_MX"],autoCapture:t.autoCapture??false,stableFramesRequired:t.stableFramesRequired??10,captureQuality:t.captureQuality??.92,captureResolution:t.captureResolution??"standard",opencvUrl:t.opencvUrl,workerUrl:t.workerUrl,onUpload:t.onUpload,onFrameAnalyzed:t.onFrameAnalyzed,onCaptured:t.onCaptured,onResult:t.onResult,onError:t.onError},this.deps={...Wt,...r},this.stability=new K({stableFramesRequired:this.opts.stableFramesRequired});}async start(){if(!["idle","stopped","error"].includes(this.state.status))throw new Error(`Cannot start session from status=${this.state.status}`);this.release();let t=++this.generation;this.setStatus("starting"),this.state.lastError=null;try{let r=await this.deps.attachCamera(this.opts.videoElement);if(t!==this.generation){this.deps.detachCamera(this.opts.videoElement,r);return}this.stream=r,this.track=r.getVideoTracks()[0]??null,this.torchSupported=this.track?this.deps.supportsTorch(this.track):!1,this.warmupUntil=0,this.setStatus("loading_detector");let n=this.deps.createWorkerClient({initTimeoutMs:Xt});this.cv=n,n.init(this.opts.opencvUrl,this.opts.workerUrl).then(()=>{t!==this.generation||this.cv!==n||(this.state.status==="loading_detector"&&this.setStatus("streaming"),this.scheduleNext(t,0));},a=>this.detectorUnavailable(t,n,a));}catch(r){throw t===this.generation&&this.emitError(r),r}}stop(){this.generation++,this.release(),this.setStatus("stopped");}async captureNow(){if(this.capturing)return;let t=this.state.status;if(t!=="streaming"&&t!=="loading_detector"&&!(t==="done"&&this.stream))throw new Error(`Cannot capture from status=${this.state.status}`);let r=this.stability.snapshot,n=this.state.lastFrame?.frameSize??null,a=r.quad&&n?this.fullFrameQuad(r.quad,n):null;await this.doCapture(this.generation,a?r.typeHint:null,a);}getState(){return {...this.state}}scheduleNext(t,r){this.timer=setTimeout(()=>{if(this.timer=null,t!==this.generation)return;let n=this.opts.videoElement;typeof n.requestVideoFrameCallback=="function"?this.frameCallback=n.requestVideoFrameCallback(()=>{this.frameCallback=null,this.tick(t);}):this.tick(t);},r);}async tick(t){if(t!==this.generation)return;let r=this.deps.now();try{await this.analyzeFrame(t);}catch(n){t===this.generation&&console.warn("[xid-camera-ocr] analyzeFrame error:",n);}t!==this.generation||!this.cv||this.scheduleNext(t,Math.max(0,1e3/Nt-(this.deps.now()-r)));}async analyzeFrame(t){let r=this.cv;if(this.capturing||!r?.isReady())return;let n=this.deps.sampleFrame(this.opts.videoElement,qt);if(!n)return;let a={width:n.width,height:n.height};this.warmupUntil===0&&(this.warmupUntil=this.deps.now()+ze);let i=this.prior&&this.prior.width===n.width&&this.prior.height===n.height?this.prior.quad:null,s=r.analyze(n,i?{prior:i}:{});this.inFlight=s;let o;try{o=await s;}catch(u){if(t===this.generation&&this.cv===r&&!r.isReady()){this.detectorUnavailable(t,r,u);return}throw u}finally{this.inFlight===s&&(this.inFlight=null);}if(t!==this.generation||this.capturing)return;this.prior=o.detected&&o.corners?{quad:Ye(o.corners),...a}:null;let c=!(this.deps.now()<this.warmupUntil)&&o.detected&&o.corners&&o.documentType?{quad:o.corners,typeHint:o.documentType}:null,d=this.stability.update(c,a);this.weakRun=d.stableFrames<=1?c!==null&&Ve(o):this.weakRun||Ve(o);let p=this.opts.stableFramesRequired+(this.weakRun?Bt:0);if(await this.updateTorch(t,o),t!==this.generation)return;let m={detected:o.detected,documentType:o.documentType,confidence:Math.min(d.stableFrames/p,1),aspectRatio:o.aspectRatio,quality:o.quality,corners:o.corners?Ye(o.corners):null,frameSize:a,hint:Se({detected:o.detected,quality:o.quality,stableFrames:d.stableFrames,stableFramesRequired:p}),...this.torchSupported?{torch:this.torchOn}:{}};if(this.state.lastFrame=m,this.opts.onFrameAnalyzed?.(m),t===this.generation&&this.opts.autoCapture&&!this.autoShutterSpent&&d.quad&&d.stableFrames>=p&&o.quality?.ok){let u=this.fullFrameQuad(d.quad,a);if(!u){this.stability.reset(),this.weakRun=false;return}await this.doCapture(t,d.typeHint,u);}}detectorUnavailable(t,r,n){if(t!==this.generation||this.cv!==r)return;r.terminate(),this.cv=null,this.inFlight=null,this.prior=null,this.stability.reset(),this.weakRun=false,this.autoShutterSpent=true,this.state.status==="loading_detector"&&this.setStatus("streaming");let a=Ge(n);this.state.lastError=a;let i={detected:false,documentType:null,confidence:0,aspectRatio:null,quality:null,corners:null,frameSize:null,hint:"detector_unavailable",...this.torchSupported?{torch:this.torchOn}:{}};this.state.lastFrame=i,this.opts.onFrameAnalyzed?.(i),t===this.generation&&this.opts.onError?.(a);}async updateTorch(t,r){if(!this.track||this.torchRejected||!Be({torchSupported:this.torchSupported,torchOn:this.torchOn,quality:r.quality,frameBrightness:r.frameBrightness}))return;let n=await this.deps.setTorch(this.track,true);t===this.generation&&(this.torchOn=n,n||(this.torchRejected=true));}fullFrameQuad(t,r){let n=this.opts.videoElement;return !n.videoWidth||!n.videoHeight?null:We(t,r,{width:n.videoWidth,height:n.videoHeight})}async doCapture(t,r,n){if(!this.capturing){this.capturing=true,this.autoShutterSpent=true,this.prior=null,this.stability.reset(),this.weakRun=false,this.setStatus("capturing");try{let{blob:a,type:i}=await this.captureBlob(t,r,n);if(t!==this.generation)return;this.opts.onCaptured?.(a,i),this.setStatus("uploading");let s=await this.opts.onUpload(a,i);if(t!==this.generation)return;this.state.lastResult=s,this.setStatus("done"),this.opts.onResult?.(s);}catch(a){t===this.generation&&this.emitError(a);}finally{t===this.generation&&(this.stability.reset(),this.weakRun=false,this.warmupUntil=this.deps.now()+ze,this.capturing=false);}}}async captureBlob(t,r,n){let a=this.opts.videoElement,i=async()=>({blob:await this.deps.snapshotToBlob(a,this.opts.captureQuality),type:null}),s=this.cv;if(!s?.isReady()||!n)return i();try{await this.inFlight?.catch(()=>{});let o=this.deps.grabFrame(a);if(!o||!s.isReady())return i();let l=this.opts.captureResolution==="high"?Qe:V,c=await s.warp(o,n,r,l);return {blob:await this.deps.encodeJpeg(c,this.opts.captureQuality),type:r}}catch(o){if(t!==this.generation)throw o;return console.warn("[xid-camera-ocr] warp failed, using plain snapshot:",o),i()}}release(){this.prior=null,this.timer!==null&&clearTimeout(this.timer),this.timer=null,this.frameCallback!==null&&(this.opts.videoElement.cancelVideoFrameCallback?.(this.frameCallback),this.frameCallback=null),this.track&&this.torchOn&&this.deps.setTorch(this.track,false),this.torchOn=false,this.torchRejected=false,this.stream&&this.deps.detachCamera(this.opts.videoElement,this.stream),this.stream=null,this.track=null,this.cv?.terminate(),this.cv=null,this.inFlight=null,this.stability.reset(),this.weakRun=false,this.capturing=false,this.autoShutterSpent=false;}setStatus(t){this.state.status=t;}emitError(t){let r=Ge(t);this.state.lastError=r,this.setStatus("error"),this.opts.onError?.(r);}};var Ke=.08,jt=.95,zt=.02,q=8,Vt=2,$e=8,Je=.33,oe=2,Gt=2,Yt=1,Kt=Gt+oe,$t=2,Jt=2*(oe+1),Zt=10,Ze=1.1,nt=oe+1,er=6,at=.92,tr=1/at,rr=.85,nr=2,ar=.05,et=2,tt=64;function ot(e){return {detected:false,documentType:null,aspectRatio:null,corners:null,boundingBox:null,quality:null,frameBrightness:e,score:null}}function or(e,t,r,n={}){let a={brightness:0};try{return ir(e,t,a,n.prior??null)}catch(i){return console.warn("[xid-camera-ocr] detectDocumentCV failed:",i),ot(a.brightness)}}function ir(e,t,r,n){let{width:a,height:i}=t,s=[],o=l=>(s.push(l),l);try{let l=o(e.matFromImageData(t)),c=o(new e.Mat),d=o(new e.Mat),p={equalized:o(new e.Mat),filtered:o(new e.Mat),padded:o(new e.Mat),edges:o(new e.Mat),dilated:o(new e.Mat),kernel:o(e.Mat.ones(3,3,e.CV_8U)),contours:o(new e.MatVector),hierarchy:o(new e.Mat),hull:o(new e.Mat),approx:o(new e.Mat),clahe:o(new e.CLAHE(Vt,new e.Size($e,$e)))};e.cvtColor(l,c,e.COLOR_RGBA2GRAY),r.brightness=Xe(Re(c,a,i));let m=[],u=rt(e,c,p,a,i,m,n),x=!1,S=Math.round(a/et),y=Math.round(i/et);if(!u&&S>=tt&&y>=tt&&(e.resize(c,d,new e.Size(S,y),0,0,e.INTER_AREA),u=rt(e,d,p,a,i,m,n),x=u!==null),u&&pr(u,m,Re(c,a,i))&&(u=null),!u)return ot(r.brightness);let b=x?"lowres":u.reduced?"reduced":"direct",h=u.quad.map(w=>w.x),f=u.quad.map(w=>w.y),T=Math.min(...h),R=Math.max(...h),k=Math.min(...f),v=Math.max(...f);return {detected:!0,documentType:ne(u.aspect),aspectRatio:u.aspect,corners:u.quad,boundingBox:{x:T/a,y:k/i,width:(R-T)/a,height:(v-k)/i},quality:xe(Re(c,a,i),lt(u.quad,nt*u.scale)),frameBrightness:r.brightness,score:b==="direct"?u.score:u.score*rr,provenance:b}}finally{for(let l of s)l.delete();}}function rt(e,t,r,n,a,i,s){let o=t.cols,l=t.rows,c=n/o,d=a/l,p=o*l,m=h=>({x:h.x*c,y:h.y*d}),{hull:u,approx:x,contours:S}=r;r.clahe.apply(t,r.equalized),e.bilateralFilter(r.equalized,r.filtered,9,75,75,e.BORDER_DEFAULT),e.copyMakeBorder(r.filtered,r.padded,q,q,q,q,e.BORDER_CONSTANT,new e.Scalar(0));let y=fr(r.padded.data);e.Canny(r.padded,r.edges,Math.max(10,(1-Je)*y),Math.min(255,(1+Je)*y)),e.dilate(r.edges,r.dilated,r.kernel,new e.Point(-1,-1),oe),e.findContours(r.dilated,S,r.hierarchy,e.RETR_LIST,e.CHAIN_APPROX_SIMPLE);let b=null;for(let h=0;h<S.size();h++){let f=S.get(h);try{let T=e.boundingRect(f);if(T.width*T.height<p*Ke)continue;e.convexHull(f,u,!1,!0);let R=e.contourArea(u);if(R<p*Ke||R>p*jt||(e.approxPolyDP(u,x,zt*e.arcLength(u,!0),!0),x.rows<4||x.rows>er||!e.isContourConvex(x)))continue;let k=x.rows!==4,v;if(!k)v=lr(x,o,l);else {let U=cr(it(x));if(!U)continue;v=U.map(gt=>st(gt,o,l));let D=R/L(v);if(!(D>=at&&D<=tr))continue}let w=R*c*d,E=v.map(m),F=Kt*c;if(yr(E,n,a,F)>Yt){dr(E,n,a,F)||i.push({pts:E,area:w});continue}let _=be(E);if(!re(_))continue;let O=e.minAreaRect(k?u:x),N=O.size.width*O.size.height,le=N>0?Math.min(1,R/N):0,X=z(_);if(!ge(X))continue;let B=Le({areaRatio:R/p,rectangularity:le,aspect:X}),W=s?B-sr(_,s,n,a):B;(!b||W>b.rank)&&(b={quad:_,score:B,rank:W,aspect:X,area:w,scale:c,reduced:k});}finally{f.delete();}}return b}function sr(e,t,r,n){let a=1/0;for(let i=0;i<4;i++){let s=0;for(let o=0;o<4;o++){let l=e[(o+i)%4];s=Math.max(s,Math.hypot(l.x-t[o].x,l.y-t[o].y));}a=Math.min(a,s);}return nr*Math.min(a/Math.hypot(r,n),ar)}function Re(e,t,r){return {data:e.data,width:t,height:r}}function it(e){let t=e.data32S,r=[];for(let n=0;n<e.rows;n++)r.push({x:t[n*2]-q,y:t[n*2+1]-q});return r}function st(e,t,r){return {x:Math.min(t,Math.max(0,e.x)),y:Math.min(r,Math.max(0,e.y))}}function lr(e,t,r){return it(e).map(n=>st(n,t,r))}function cr(e){let t=e;for(;t.length>4;){let r=t.length,n=-1,a=1/0,i=null;for(let o=0;o<r;o++){let l=t[(o+r-1)%r],c=t[o],d=t[(o+1)%r],p=t[(o+2)%r],m=ur(l,c,p,d);if(!m)continue;let u=Math.abs((m.x-c.x)*(d.y-c.y)-(m.y-c.y)*(d.x-c.x))/2;u<a&&(a=u,n=o,i=m);}if(n<0||!i)return null;let s=[];for(let o=0;o<r;o++)o===n?s.push(i):o!==(n+1)%r&&s.push(t[o]);t=s;}return t}function ur(e,t,r,n){let a=t.x-e.x,i=t.y-e.y,s=n.x-r.x,o=n.y-r.y,l=a*o-i*s;if(Math.abs(l)<1e-9)return null;let c=((r.x-e.x)*o-(r.y-e.y)*s)/l,d=((r.x-e.x)*i-(r.y-e.y)*a)/l;return c<1||d<1?null:{x:e.x+c*a,y:e.y+c*i}}function dr(e,t,r,n){let a=e.map(s=>s.x),i=e.map(s=>s.y);return Math.min(...a)<=n&&Math.max(...a)>=t-n||Math.min(...i)<=n&&Math.max(...i)>=r-n}function pr(e,t,r){let n=Jt*e.scale;if(!t.some(o=>o.area>=$t*e.area&&e.quad.every(l=>hr(o.pts,l,n))))return false;if(ne(e.aspect)==="OTHER")return true;let{inner:i,ring:s}=mr(r,lt(e.quad,nt*e.scale),e.quad);return i<s-Zt}function mr(e,t,r){let{data:n,width:a,height:i}=e,s=(r[0].x+r[1].x+r[2].x+r[3].x)/4,o=(r[0].y+r[1].y+r[2].y+r[3].y)/4,l=r.map(y=>({x:s+(y.x-s)*Ze,y:o+(y.y-o)*Ze})),c=G(l,a,i),d=G(r,a,i),p=G(t,a,i),m=0,u=0,x=0,S=0;for(let y=0;y<i;y++){let b=c[y*2],h=c[y*2+1];if(b>=h)continue;let f=d[y*2],T=d[y*2+1],R=p[y*2],k=p[y*2+1],v=y*a;for(let w=b;w<h;w++){let E=n[v+w];w>=R&&w<k?(m+=E,u++):(w<f||w>=T)&&(x+=E,S++);}}return {inner:u>0?m/u:0,ring:S>0?x/S:0}}function hr(e,t,r){let n=0;for(let i=0;i<e.length;i++){let s=e[i],o=e[(i+1)%e.length];n+=s.x*o.y-o.x*s.y;}let a=Math.sign(n);if(a===0)return false;for(let i=0;i<e.length;i++){let s=e[i],o=e[(i+1)%e.length],l=Math.hypot(o.x-s.x,o.y-s.y);if(l===0)continue;if(a*((o.x-s.x)*(t.y-s.y)-(o.y-s.y)*(t.x-s.x))/l<r)return false}return true}function lt(e,t){return e.map((r,n)=>{let a=e[(n+3)%4],i=e[(n+1)%4],s=a.x-r.x,o=a.y-r.y,l=i.x-r.x,c=i.y-r.y,d=Math.hypot(s,o),p=Math.hypot(l,c),m=s/d+l/p,u=o/d+c/p,x=Math.hypot(m,u),S=Math.sin(Math.acos(Math.min(1,Math.max(-1,(s*l+o*c)/(d*p))))/2),y=t/S/x;return {x:r.x+m*y,y:r.y+u*y}})}function yr(e,t,r,n){let a=0;for(let i of e)(i.x<=n||i.x>=t-n||i.y<=n||i.y>=r-n)&&a++;return a}function fr(e){let t=new Uint32Array(256);for(let n=0;n<e.length;n++)t[e[n]]++;let r=0;for(let n=0;n<256;n++)if(r+=t[n],r>=e.length/2)return n;return 128}function ve(e){let t=react.useRef(null),r=react.useRef(null),[n,a]=react.useState("idle"),[i,s]=react.useState(null),[o,l]=react.useState(null),[c,d]=react.useState(null),p=react.useRef(e);p.current=e;let m=react.useCallback(()=>{if(r.current)return r.current;if(!t.current)throw new Error("videoRef not attached yet");let b=new $({videoElement:t.current,documentTypes:p.current.documentTypes,autoCapture:p.current.autoCapture,stableFramesRequired:p.current.stableFramesRequired,captureQuality:p.current.captureQuality,captureResolution:p.current.captureResolution,opencvUrl:p.current.opencvUrl,workerUrl:p.current.workerUrl,onUpload:(h,f)=>p.current.onUpload(h,f),onFrameAnalyzed:h=>{s(h),r.current===b&&a(b.getState().status),p.current.onFrameAnalyzed?.(h);},onCaptured:(h,f)=>{a("capturing"),p.current.onCaptured?.(h,f);},onResult:h=>{l(h),a("done"),p.current.onResult?.(h);},onError:h=>{d(h),r.current===b&&a(b.getState().status),p.current.onError?.(h);}});return r.current=b,b},[]),u=react.useCallback(async()=>{let b=m();await b.start(),r.current===b&&a(b.getState().status);},[m]),x=react.useCallback(()=>{r.current?.stop(),a("stopped");},[]),S=react.useCallback(async()=>{await r.current?.captureNow();},[]),y=react.useCallback(async()=>{x(),l(null),d(null),s(null),await u();},[x,u]);return react.useEffect(()=>(e.autoStart!==false&&t.current&&u().catch(()=>{}),()=>{r.current?.stop(),r.current=null;}),[]),{videoRef:t,status:n,frame:i,result:o,error:c,start:u,stop:x,captureNow:S,retake:y}}function xr({status:e,showTips:t,dismissed:r,completedOnce:n}){return t&&!r&&(e==="error"||!n&&(e==="starting"||e==="loading_detector"))}function ct(e,t){let[r,n]=react.useState(false),[a,i]=react.useState(e==="done"),[s,o]=react.useState(e),l=r,c=a;s!==e&&(o(e),e==="error"&&(l=false,n(false)),e==="done"&&(c=true,i(true)));let d=react.useCallback(()=>n(true),[]);return {visible:xr({status:e,showTips:t,dismissed:l,completedOnce:c}),dismiss:d}}var se={accent:{name:"--xco-accent",fallback:"#22c55e"},warning:{name:"--xco-warning",fallback:"#eab308"},danger:{name:"--xco-danger",fallback:"#ef4444"},background:{name:"--xco-background",fallback:"#000"},mask:{name:"--xco-mask",fallback:"rgba(0,0,0,0.4)"},hintBg:{name:"--xco-hint-bg",fallback:"rgba(0,0,0,0.7)"},hintFg:{name:"--xco-hint-fg",fallback:"white"},buttonBg:{name:"--xco-button-bg",fallback:"rgba(255,255,255,0.92)"},buttonFg:{name:"--xco-button-fg",fallback:"#111"},shutter:{name:"--xco-shutter",fallback:"rgba(255,255,255,0.9)"},overlayBg:{name:"--xco-overlay-bg",fallback:"rgba(0,0,0,0.78)"},overlayFg:{name:"--xco-overlay-fg",fallback:"white"},fontFamily:{name:"--xco-font-family",fallback:"system-ui, -apple-system, sans-serif"},radius:{name:"--xco-radius",fallback:"12px"}};function g(e){let{name:t,fallback:r}=se[e];return `var(${t}, ${r})`}function ut(e){let t={};if(!e)return t;for(let r of Object.keys(e)){if(!Object.prototype.hasOwnProperty.call(se,r))continue;let n=e[r];typeof n!="string"||n===""||(t[se[r].name]=n);}return t}function dt(e,t){return e==="done"?g("accent"):e==="error"?g("danger"):e==="uploading"||e==="capturing"?g("accent"):t?.detected?t.confidence>=1?g("accent"):g("warning"):g("danger")}var Ee={width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",focusable:"false",style:{flex:"0 0 auto"}},Sr=()=>jsxRuntime.jsxs("svg",{...Ee,children:[jsxRuntime.jsx("circle",{cx:"12",cy:"12",r:"4"}),jsxRuntime.jsx("path",{d:"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"})]}),Cr=()=>jsxRuntime.jsxs("svg",{...Ee,children:[jsxRuntime.jsx("rect",{x:"3",y:"5",width:"18",height:"14",rx:"2"}),jsxRuntime.jsx("path",{d:"M14 5l-6 14M18 5l-6 14"})]}),wr=()=>jsxRuntime.jsxs("svg",{...Ee,children:[jsxRuntime.jsx("path",{d:"M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3"}),jsxRuntime.jsx("circle",{cx:"12",cy:"12",r:"2"})]});function pt({texts:e,onDismiss:t}){let r=[{Icon:Sr,text:e.tip_light},{Icon:Cr,text:e.tip_glare},{Icon:wr,text:e.tip_focus}];return jsxRuntime.jsxs("div",{className:"xco-tips",style:Rr,children:[jsxRuntime.jsx("ul",{style:Tr,children:r.map(({Icon:n,text:a})=>jsxRuntime.jsxs("li",{style:vr,children:[jsxRuntime.jsx(n,{}),jsxRuntime.jsx("span",{children:a})]},a))}),jsxRuntime.jsx("button",{type:"button",className:"xco-tips-dismiss",onClick:t,style:kr,children:e.tips_dismiss})]})}var Rr={position:"absolute",top:"calc(16px + env(safe-area-inset-top, 0px))",left:"50%",transform:"translateX(-50%)",boxSizing:"border-box",width:"calc(100% - 32px)",maxWidth:360,padding:16,background:g("overlayBg"),color:g("overlayFg"),borderRadius:g("radius"),fontFamily:g("fontFamily"),fontSize:15,lineHeight:1.35,display:"flex",flexDirection:"column",gap:12,pointerEvents:"auto",zIndex:5},Tr={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:10},vr={display:"flex",alignItems:"center",gap:12},kr={alignSelf:"flex-end",minHeight:44,padding:"10px 20px",borderRadius:g("radius"),background:g("buttonBg"),color:g("buttonFg"),border:"none",fontFamily:g("fontFamily"),fontSize:15,fontWeight:600,cursor:"pointer",WebkitTapHighlightColor:"transparent"};var Fr=typeof window>"u"?react.useEffect:react.useLayoutEffect;function ht({videoRef:e,status:t,frame:r,texts:n,documentTypes:a,theme:i,className:s,style:o,hideStatusOverlay:l,hideShutter:c,tipsVisible:d,onCapture:p,onRetake:m,onStart:u,onDismissTips:x}){let S=t==="streaming"||t==="loading_detector",y=t==="done"||t==="error",b=t==="idle"||t==="stopped",h=t==="capturing"||t==="uploading",f=react.useRef(null),[T,R]=react.useState({width:0,height:0}),k=r?.documentType,v=k==="PASSPORT_MX"?H:k==="INE"?I:a[0]==="PASSPORT_MX"?H:I;Fr(()=>{let _=f.current;if(!_)return;let O=()=>{let le=_.clientWidth,X=_.clientHeight,B=le*.85,W=X*.7,U=B,D=U/v;D>W&&(D=W,U=D*v),R({width:U,height:D});};O();let N=new ResizeObserver(O);return N.observe(_),()=>N.disconnect()},[v]);let w=dt(t,r),E=ae(t,r?.hint,n),F=s?`xid-camera-ocr ${s}`:"xid-camera-ocr";return jsxRuntime.jsxs("div",{ref:f,className:F,"data-status":t,"data-hint":r?.hint??"",style:{...Pr,...ut(i),...o},children:[jsxRuntime.jsx("style",{children:"@keyframes xco-spin { to { transform: rotate(360deg); } }"}),jsxRuntime.jsx("video",{ref:e,playsInline:true,muted:true,autoPlay:true,style:Or}),!l&&jsxRuntime.jsx("div",{style:Ir,children:jsxRuntime.jsx("div",{className:"xco-guide",style:{...Dr,width:T.width,height:T.height,borderColor:w}})}),!l&&(b?jsxRuntime.jsx("button",{type:"button",className:"xco-hint",onClick:u,style:Xr,children:E}):jsxRuntime.jsx("p",{className:"xco-hint",style:ft,role:"status","aria-live":"polite",children:E})),!l&&!c&&S&&jsxRuntime.jsx("button",{type:"button",className:"xco-shutter",onClick:p,"aria-label":n.capture,style:Qr,children:jsxRuntime.jsx("span",{style:qr})}),!l&&y&&jsxRuntime.jsx("button",{type:"button",className:"xco-retake",onClick:m,"aria-label":n.retake,style:Nr,children:n.retake}),!l&&h&&jsxRuntime.jsxs("div",{className:"xco-loader",style:Hr,"aria-busy":"true","aria-live":"polite",children:[jsxRuntime.jsx("div",{style:Lr}),jsxRuntime.jsx("p",{style:Ur,children:ae(t,void 0,n)})]}),d&&!l&&jsxRuntime.jsx(pt,{texts:n,onDismiss:x})]})}var yt="calc(104px + env(safe-area-inset-bottom, 0px))",Pr={position:"relative",width:"100%",height:"100%",overflow:"hidden",background:g("background"),fontFamily:g("fontFamily"),touchAction:"none"},Or={position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover",display:"block"},Ir={position:"absolute",inset:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",pointerEvents:"none"},Dr={borderRadius:g("radius"),borderWidth:4,borderStyle:"solid",boxShadow:`0 0 0 9999px ${g("mask")}`,transition:"border-color 200ms ease-in-out, width 200ms ease-in-out, height 200ms ease-in-out"},Hr={position:"absolute",inset:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:20,background:g("overlayBg"),backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",pointerEvents:"auto",zIndex:10},Lr={width:56,height:56,borderRadius:"50%",border:"5px solid rgba(255, 255, 255, 0.18)",borderTopColor:g("accent"),animation:"xco-spin 0.85s linear infinite"},Ur={color:g("overlayFg"),fontFamily:g("fontFamily"),fontSize:15,fontWeight:500,margin:0,textAlign:"center",padding:"0 24px"},Qr={position:"absolute",bottom:yt,left:"50%",transform:"translateX(-50%)",width:68,height:68,borderRadius:"50%",background:"rgba(255,255,255,0.18)",border:`4px solid ${g("shutter")}`,cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",pointerEvents:"auto",WebkitTapHighlightColor:"transparent",padding:0},qr={width:52,height:52,borderRadius:"50%",background:"white",display:"block"},Nr={position:"absolute",bottom:yt,left:"50%",transform:"translateX(-50%)",minHeight:44,padding:"10px 20px",borderRadius:22,background:g("buttonBg"),color:g("buttonFg"),border:"none",fontFamily:g("fontFamily"),fontSize:15,fontWeight:600,cursor:"pointer",pointerEvents:"auto",WebkitTapHighlightColor:"transparent"},ft={position:"absolute",bottom:"calc(16px + env(safe-area-inset-bottom, 0px))",left:"50%",transform:"translateX(-50%)",boxSizing:"border-box",width:"max-content",maxWidth:"calc(100% - 32px)",background:g("hintBg"),color:g("hintFg"),padding:"10px 16px",borderRadius:g("radius"),fontFamily:g("fontFamily"),fontSize:16,lineHeight:1.35,margin:0,whiteSpace:"normal",textAlign:"center"},Xr={...ft,border:"none",minHeight:44,cursor:"pointer",pointerEvents:"auto",WebkitTapHighlightColor:"transparent"};var Gr=["INE","PASSPORT_MX"],Me=()=>{},bt=react.forwardRef(function({className:t,style:r,hideStatusOverlay:n,texts:a,theme:i,showTips:s,hideShutter:o,onStatusChange:l,...c},d){let{videoRef:p,status:m,frame:u,start:x,captureNow:S,retake:y}=ve(c),b=ct(m,s??true),h=react.useMemo(()=>we(a),[a]),f=m==="streaming"||m==="loading_detector",T=m==="done"||m==="error",R=async()=>{f&&await S().catch(Me);},k=()=>{T&&y().catch(Me);},v=async()=>{await x().catch(Me);};react.useImperativeHandle(d,()=>({capture:R,retake:k,start:v}));let w=react.useRef(l);return w.current=l,react.useEffect(()=>{w.current?.(m);},[m]),jsxRuntime.jsx(ht,{videoRef:p,status:m,frame:u,texts:h,documentTypes:c.documentTypes??Gr,theme:i,className:t,style:r,hideStatusOverlay:n,hideShutter:o,tipsVisible:b.visible,onCapture:()=>{R();},onRetake:k,onStart:()=>{v();},onDismissTips:b.dismiss})});bt.displayName="XidCameraOcr";exports.CVWorkerClient=j;exports.DEFAULT_TEXTS_EN=Ot;exports.DEFAULT_TEXTS_ES=Ce;exports.ID1_RATIO=I;exports.QUALITY_THRESHOLDS=M;exports.StabilityTracker=K;exports.TD3_RATIO=H;exports.XCO_VARS=se;exports.XidCameraOcr=bt;exports.XidCameraOcrSession=$;exports.attachCamera=ce;exports.copyFor=ae;exports.decideHint=Se;exports.defaultOpencvUrl=ye;exports.detachCamera=ue;exports.detectDocumentCV=or;exports.isCapturableAspect=ge;exports.measureQuality=xe;exports.orderCorners=be;exports.rectifiedAspect=z;exports.resolveTexts=we;exports.sampleFrame=pe;exports.setTorch=he;exports.snapshotToBlob=de;exports.supportsTorch=me;exports.typeHintFromAspect=ne;exports.useXidCameraOcr=ve;exports.warpDocument=Ne;exports.warpDocumentImageData=qe;exports.warpedSnapshotToBlob=_t;//# sourceMappingURL=index.cjs.map
|
|
1
|
+
'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var Tt=()=>typeof document>"u"?new URL(`file:${__filename}`).href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?document.currentScript.src:new URL("main.js",document.baseURI).href,w=Tt();function Mt(e=typeof window>"u"?void 0:window){return e?typeof e.matchMedia=="function"?e.matchMedia("(orientation: portrait)").matches?"portrait":"landscape":e.innerHeight>e.innerWidth?"portrait":"landscape":"landscape"}function vt(e){return {audio:false,video:{facingMode:{ideal:"environment"},...e==="portrait"?{width:{ideal:1080},height:{ideal:1920},aspectRatio:{ideal:9/16}}:{width:{ideal:1920,min:640},height:{ideal:1080,min:480},aspectRatio:{ideal:16/9}}}}}async function ie(e,t,r=Mt()){let n=vt(r);if(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia)throw new Error("Camera unavailable: getUserMedia requires a secure context (HTTPS or localhost) and a browser with MediaDevices support.");let o;try{o=await navigator.mediaDevices.getUserMedia(t??n);}catch(s){if(!t)o=await navigator.mediaDevices.getUserMedia({audio:false,video:true});else throw s}e.srcObject=o,e.muted=true,e.playsInline=true,e.autoplay=true;let a=o.getVideoTracks()[0],i=a.getCapabilities?.();return i&&"focusMode"in i&&i.focusMode?.includes("continuous")&&a.applyConstraints({advanced:[{focusMode:"continuous"}]}).catch(()=>{}),await e.play().catch(()=>{}),o}function se(e,t){e.srcObject===t&&(e.srcObject=null),t?.getTracks().forEach(r=>r.stop());}async function ue(e,t=.92){let r=e.videoWidth,n=e.videoHeight;if(!r||!n)throw new Error("Video stream not ready (videoWidth=0)");let o=document.createElement("canvas");o.width=r,o.height=n;let a=o.getContext("2d");if(!a)throw new Error("2D canvas context unavailable");return a.drawImage(e,0,0,r,n),new Promise((i,s)=>{o.toBlob(u=>u?i(u):s(new Error("Canvas toBlob returned null")),"image/jpeg",t);})}function Pe(e,t,r,n=.92){let o=document.createElement("canvas");o.width=r.width,o.height=r.height;let a=o.getContext("2d");return a?(a.imageSmoothingEnabled=true,a.imageSmoothingQuality="high",a.drawImage(e,t.x,t.y,t.width,t.height,0,0,r.width,r.height),new Promise((i,s)=>o.toBlob(u=>u?i(u):s(new Error("Canvas toBlob returned null")),"image/jpeg",n))):Promise.reject(new Error("2D canvas context unavailable"))}var L=null;function ce(e,t=640){let r=e.videoWidth,n=e.videoHeight;if(!r||!n)return null;let o=Math.round(t),a=Math.round(n*t/r);L??=document.createElement("canvas"),(L.width!==o||L.height!==a)&&(L.width=o,L.height=a);let i=L.getContext("2d",{willReadFrequently:true});return i?(i.drawImage(e,0,0,o,a),i.getImageData(0,0,o,a)):null}function le(e){let t=e.getCapabilities;return typeof t!="function"?false:t.call(e).torch===true}async function de(e,t){try{return await e.applyConstraints({advanced:[{torch:t}]}),!0}catch{return false}}var Et="@andresxid/xid_camera_ocr_sdk_react";function Fe(e,t){return e.pathname.endsWith(`/${Et}/${t}`)}function Oe(e){return new URL(e,w)}function Ie(){return new URL("@andresxid/xid_camera_ocr_sdk_react/opencv.js",w)}function pe(){let e=Ie();return Fe(e,"opencv.js")?Oe("./opencv.js").href:e.href}function De(e){return e!==void 0?new Worker(e,{type:"module"}):Fe(Ie(),"opencv.js")?new Worker(Oe("./cv-worker.js"),{type:"module"}):new Worker(new URL("@andresxid/xid_camera_ocr_sdk_react/cv-worker.js",w),{type:"module"})}function He(e){if(!e||typeof e!="object")return null;let t=e;switch(t.type){case "ready":return {type:"ready"};case "init-error":return typeof t.error=="string"?{type:"init-error",error:t.error}:null;case "error":return typeof t.id=="number"&&typeof t.error=="string"?{type:"error",id:t.id,error:t.error}:null;case "analyze-result":return typeof t.id=="number"&&t.result&&typeof t.result=="object"?{type:"analyze-result",id:t.id,result:t.result}:null;case "warp-result":return typeof t.id=="number"&&typeof t.width=="number"&&typeof t.height=="number"&&t.buffer instanceof ArrayBuffer?{type:"warp-result",id:t.id,width:t.width,height:t.height,buffer:t.buffer}:null;default:return null}}function Le(e){let t=new Uint8ClampedArray(e.data.byteLength);return t.set(e.data),t.buffer}var _t=e=>e instanceof Error?e:new Error(String(e)),Q=class{initTimeoutMs;worker=null;readyPromise=null;rejectInit=null;initTimer=null;ready=false;pending=new Map;nextId=1;constructor(t={}){this.initTimeoutMs=t.initTimeoutMs??3e4;}init(t=pe(),r){if(this.readyPromise)return this.readyPromise;let n;try{n=De(r);}catch(a){return Promise.reject(_t(a))}this.worker=n;let o=new Promise((a,i)=>{this.rejectInit=i,this.initTimer=setTimeout(()=>this.fail(new Error(`OpenCV worker not ready within ${this.initTimeoutMs} ms`)),this.initTimeoutMs),n.addEventListener("message",s=>{if(this.worker!==n)return;let u=He(s.data);if(!u)return;if(u.type==="ready"){this.clearInitTimer(),this.rejectInit=null,this.ready=true,a();return}if(u.type==="init-error"){this.fail(new Error(u.error));return}let c=this.pending.get(u.id);c&&(this.pending.delete(u.id),u.type==="error"?c.reject(new Error(u.error)):c.resolve(u));}),n.addEventListener("error",s=>{this.worker===n&&this.fail(new Error(s.message||"Worker error"));}),n.addEventListener("messageerror",s=>{if(this.worker!==n)return;let u=new Error("Worker message could not be deserialized"),c=s.data?.id,d=typeof c=="number"?this.pending.get(c):void 0;if(typeof c=="number"&&d){this.pending.delete(c),d.reject(u);return}this.fail(u);});});return this.readyPromise=o,n.postMessage({type:"init",opencvUrl:t}),o}isReady(){return this.ready&&this.worker!==null}async analyze(t,r={}){let n=Le(t),o=r.prior,a=await this.request(i=>({type:"analyze",id:i,width:t.width,height:t.height,buffer:n,...o?{prior:o}:{}}),[n]);if(a.type!=="analyze-result")throw new Error(`Unexpected worker response: ${a.type}`);return a.result}async warp(t,r,n,o=1024){let a=Le(t),i=await this.request(s=>({type:"warp",id:s,width:t.width,height:t.height,buffer:a,corners:r,docType:n,longSide:o}),[a]);if(i.type!=="warp-result")throw new Error(`Unexpected worker response: ${i.type}`);return new ImageData(new Uint8ClampedArray(i.buffer),i.width,i.height)}terminate(){this.rejectInit=null,this.shutdown(new Error("Worker terminated"));}fail(t){let r=this.rejectInit;this.rejectInit=null,this.shutdown(t),r?.(t);}clearInitTimer(){this.initTimer!==null&&clearTimeout(this.initTimer),this.initTimer=null;}shutdown(t){this.clearInitTimer(),this.worker?.terminate(),this.worker=null,this.readyPromise=null,this.ready=false;let r=[...this.pending.values()];this.pending.clear();for(let n of r)n.reject(t);}request(t,r){let n=this.worker;if(!n||!this.ready)return Promise.reject(new Error("CVWorkerClient not ready"));let o=t(this.nextId++),a=new Promise((i,s)=>this.pending.set(o.id,{resolve:i,reject:s}));return n.postMessage(o,r),a}};var I=1.585772508336421,U=125/88,At=1.503,Pt=.05,K=1.25,me=1.75,Ft=.25;function he(e){if(e.length!==4)throw new Error(`orderCorners expects 4 points, got ${e.length}`);let t=(e[0].x+e[1].x+e[2].x+e[3].x)/4,r=(e[0].y+e[1].y+e[2].y+e[3].y)/4,n=[...e].sort((a,i)=>Math.atan2(a.y-r,a.x-t)-Math.atan2(i.y-r,i.x-t)),o=0;for(let a=1;a<4;a++){let i=n[a],s=n[o];(i.x+i.y<s.x+s.y||i.x+i.y===s.x+s.y&&i.y<s.y)&&(o=a);}return [0,1,2,3].map(a=>n[(o+a)%4])}function D(e){let t=0;for(let r=0;r<e.length;r++){let n=e[r],o=e[(r+1)%e.length];t+=n.x*o.y-o.x*n.y;}return Math.abs(t)/2}function $(e){let[t,r,n,o]=e;return {top:Math.hypot(r.x-t.x,r.y-t.y),right:Math.hypot(n.x-r.x,n.y-r.y),bottom:Math.hypot(n.x-o.x,n.y-o.y),left:Math.hypot(o.x-t.x,o.y-t.y)}}function Ot(e){let t=[0,0,0,0];for(let r=0;r<4;r++){let n=e[(r+3)%4],o=e[r],a=e[(r+1)%4],i=n.x-o.x,s=n.y-o.y,u=a.x-o.x,c=a.y-o.y,d=Math.hypot(i,s),p=Math.hypot(u,c);if(d===0||p===0){t[r]=0;continue}let m=Math.min(1,Math.max(-1,(i*u+s*c)/(d*p)));t[r]=Math.acos(m)*180/Math.PI;}return t}function Z(e,t=70,r=110){let n=$(e);return n.top<=0||n.right<=0||n.bottom<=0||n.left<=0||!It(e)||D(e)<=0?false:Ot(e).every(o=>o>=t&&o<=r)}function It(e){let t=0;for(let r=0;r<4;r++){let n=e[r],o=e[(r+1)%4],a=e[(r+2)%4],i=(o.x-n.x)*(a.y-o.y)-(o.y-n.y)*(a.x-o.x);if(i===0)return false;let s=Math.sign(i);if(t===0)t=s;else if(s!==t)return false}return true}function X(e){let t=$(e),r=(t.top+t.bottom)/2,n=(t.left+t.right)/2,o=Math.min(r,n);return o<=0?0:Math.max(r,n)/o}function Dt(e){if(!Number.isFinite(e))return 0;let t=Math.min(Math.abs(e-I),Math.abs(e-U));return Math.max(0,1-t/Ft)}function Ue(e){return .5*e.areaRatio+.3*e.rectangularity+.2*Dt(e.aspect)}function J(e){let t=e>=At?I:U;return Math.abs(e-t)/t<=Pt?t===I?"INE":"PASSPORT_MX":"OTHER"}function ye(e){return e>=K&&e<=me}function Be(e,t){return e==="INE"?I:e==="PASSPORT_MX"?U:t}var N=1024,Qe=1600;function Xe(e,t,r,n,o=N){let a=Lt(Be(n,X(r))),i=Math.max(1,Math.round(o/a)),s=$(r),u=s.top+s.bottom<s.left+s.right,c=n==="INE"||n==="PASSPORT_MX",d=u&&c?[r[3],r[0],r[1],r[2]]:r,p=!u||c,m=p?o:i,l=p?i:o,x=null,S=null,y=null,b=null,h=null,f=null;try{return x=e.matFromImageData(t),S=new e.Mat,y=e.matFromArray(4,1,e.CV_32FC2,[d[0].x,d[0].y,d[1].x,d[1].y,d[2].x,d[2].y,d[3].x,d[3].y]),b=e.matFromArray(4,1,e.CV_32FC2,[0,0,m,0,m,l,0,l]),h=e.getPerspectiveTransform(y,b),e.warpPerspective(x,S,h,new e.Size(m,l),e.INTER_LINEAR,e.BORDER_CONSTANT,new e.Scalar(0,0,0,255)),S.channels()!==4&&(f=new e.Mat,e.cvtColor(S,f,e.COLOR_BGR2RGBA)),Ut(new Uint8ClampedArray((f??S).data),m,l)}finally{for(let T of [f,h,b,y,S,x])T?.delete();}}function Ne(e,t,r,n,o=N){let a=t.getContext("2d",{willReadFrequently:true});if(!a)throw new Error("2D canvas context unavailable");let i=a.getImageData(0,0,t.width,t.height),s=Xe(e,i,r,n,o),u=document.createElement("canvas");u.width=s.width,u.height=s.height;let c=u.getContext("2d");if(!c)throw new Error("2D canvas context unavailable (output)");return c.putImageData(s,0,0),u}async function Ht(e,t,r,n,o=.92,a=N){let i=r?Ne(e,t,r,n,a):t;return new Promise((s,u)=>{i.toBlob(c=>c?s(c):u(new Error("Canvas toBlob returned null")),"image/jpeg",o);})}function Lt(e){return Number.isFinite(e)?Math.min(me,Math.max(K,e)):K}function Ut(e,t,r){return typeof globalThis.ImageData=="function"?new ImageData(e,t,r):{data:e,width:t,height:r,colorSpace:"srgb"}}var Bt=.85,Qt=.7;function H(e){return e?.[0]==="PASSPORT_MX"?U:I}function q(e,t){let r=e.width*Bt,n=e.height*Qt,o=r,a=o/t;return a>n&&(a=n,o=a*t),{width:o,height:a}}var qe=e=>Number.isFinite(e)&&e>0,fe=e=>qe(e.width)&&qe(e.height);function We(e,t,r){if(!fe(e)||!fe(t)||!fe(r))return null;let n=Math.max(t.width/e.width,t.height/e.height),o=(t.width-e.width*n)/2,a=(t.height-e.height*n)/2,i=Math.round(((t.width-r.width)/2-o)/n),s=Math.round(((t.height-r.height)/2-a)/n),u=i+Math.round(r.width/n),c=s+Math.round(r.height/n),d=Math.max(0,i),p=Math.max(0,s),m=Math.min(e.width,u),l=Math.min(e.height,c);return m<=d||l<=p?null:{x:d,y:p,width:m-d,height:l-p}}function je(e,t){let r=Math.max(e.width,e.height);if(r<=t)return {width:e.width,height:e.height};let n=t/r;return {width:Math.max(1,Math.round(e.width*n)),height:Math.max(1,Math.round(e.height*n))}}var k={brightnessMin:.18,brightnessMax:.9,blurMin:60,glareMax:.04,fillMin:.25,glarePixel:248};function W(e,t,r){let n=new Int32Array(r*2);for(let o=0;o<r;o++){let a=o+.5,i=1/0,s=-1/0;for(let u=0;u<4;u++){let c=e[u],d=e[(u+1)%4],p=Math.min(c.y,d.y),m=Math.max(c.y,d.y);if(a<p||a>m||m===p)continue;let l=c.x+(a-c.y)*(d.x-c.x)/(d.y-c.y);l<i&&(i=l),l>s&&(s=l);}if(i===1/0){n[o*2]=0,n[o*2+1]=0;continue}n[o*2]=Math.max(0,Math.ceil(i-.5)),n[o*2+1]=Math.min(t,Math.floor(s+.5));}return n}function be(e,t){let{data:r,width:n,height:o}=e,a=W(t,n,o),i=k.glarePixel,s=0,u=0,c=0,d=0,p=0,m=0;for(let f=0;f<o;f++){let T=a[f*2],R=a[f*2+1];if(T>=R)continue;let M=0,v=0;f>0&&f<o-1&&(M=Math.max(T,a[(f-1)*2],a[(f+1)*2])+1,v=Math.min(R,a[(f-1)*2+1],a[(f+1)*2+1])-1);let C=f*n;for(let E=T;E<R;E++){let _=C+E,P=r[_];if(s+=P,P>=i&&u++,c++,E>=M&&E<v){let O=4*P-r[_-n]-r[_+n]-r[_-1]-r[_+1];d+=O,p+=O*O,m++;}}}let l=c>0?s/c/255:0,x=c>0?u/c:0,S=m>0?Math.max(0,p/m-(d/m)**2):0,y=n*o,b=y>0?D(t)/y:0,h=l>=k.brightnessMin&&l<=k.brightnessMax&&S>=k.blurMin&&x<=k.glareMax&&b>=k.fillMin;return {brightness:l,blurScore:S,glareRatio:x,docFillRatio:b,ok:h}}function ze(e){let{data:t}=e;if(t.length===0)return 0;let r=0;for(let n=0;n<t.length;n++)r+=t[n];return r/t.length/255}function ge(e){if(!e.detected||!e.quality)return "no_document";let t=e.quality;return !Number.isFinite(t.docFillRatio)||t.docFillRatio<k.fillMin?"too_small":!Number.isFinite(t.brightness)||t.brightness<k.brightnessMin?"too_dark":!Number.isFinite(t.glareRatio)||t.glareRatio>k.glareMax||t.brightness>k.brightnessMax?"glare":!Number.isFinite(t.blurScore)||t.blurScore<k.blurMin?"blurry":e.stableFrames<e.stableFramesRequired?"hold_still":"ready"}var Xt=k.brightnessMin;function Ve(e){return !e.torchSupported||e.torchOn?false:(e.quality?e.quality.brightness:e.frameBrightness)<Xt}var xe=Object.freeze({idle:"Toca para activar la c\xE1mara",starting:"Abriendo la c\xE1mara\u2026",loading_detector:"Preparando la c\xE1mara\u2026",no_document:"Coloca tu identificaci\xF3n dentro del recuadro",too_small:"Acerca m\xE1s tu identificaci\xF3n",too_dark:"Hay poca luz, busca un lugar m\xE1s iluminado",glare:"Hay un reflejo, inclina un poco tu identificaci\xF3n",blurry:"Espera a que la c\xE1mara enfoque",hold_still:"Mant\xE9n la c\xE1mara quieta",ready:"\xA1Perfecto! Toma la foto",capturing:"Tomando la foto\u2026",uploading:"Revisando tu identificaci\xF3n\u2026",done:"\xA1Listo!",error:"Algo sali\xF3 mal, int\xE9ntalo de nuevo",detector_unavailable:"Toma la foto con el bot\xF3n",retake:"Tomar otra vez",capture:"Tomar foto",tip_light:"Busca un lugar con buena luz",tip_glare:"Evita reflejos: no apuntes una l\xE1mpara directo a tu identificaci\xF3n",tip_focus:"Espera a que la c\xE1mara enfoque y luego toca el bot\xF3n",tips_dismiss:"Entendido"}),Nt=Object.freeze({idle:"Ready to start",starting:"Starting camera\u2026",loading_detector:"Loading detector\u2026",no_document:"Place the document inside the frame",too_small:"Move closer to the document",too_dark:"Find more light",glare:"Tilt the document to avoid glare",blurry:"Hold still to focus",hold_still:"Hold the camera steady",ready:"Perfect! Take the photo",capturing:"Capturing\u2026",uploading:"Processing document\u2026",done:"Done",error:"Something went wrong \u2014 try again",detector_unavailable:"Detector unavailable \u2014 capture manually",retake:"Capture again",capture:"Capture now",tip_light:"Find a well-lit place",tip_glare:"Avoid glare: don't point a lamp straight at your ID",tip_focus:"Wait for the camera to focus, then tap the button",tips_dismiss:"Got it"}),qt=["loading_detector","capturing","uploading","done","error"];function Wt(e){return qt.includes(e)}function j(e,t){return t?.[e]??xe[e]}function Se(e){let t={...xe};for(let r of Object.keys(t))t[r]=j(r,e);return t}function ee(e,t,r){return e==="idle"||e==="stopped"?j("idle",r):e==="starting"?j("starting",r):Wt(e)?j(e,r):j(t??"no_document",r)}var jt=.05;function zt(e,t,r){let n=r.width/t.width,o=r.height/t.height;return e.map(a=>({x:a.x*n,y:a.y*o}))}function Vt(e,t){return e.map(r=>({x:Math.min(t.width,Math.max(0,r.x)),y:Math.min(t.height,Math.max(0,r.y))}))}function Ge(e,t,r){let n=Vt(zt(e,t,r),r);return !Z(n)||D(n)<jt*r.width*r.height?null:n}var Ye={stableFrames:0,isStable:false,quad:null,typeHint:null},z=class{required;maxRatio;switchFrames;current=Ye;candidate=null;constructor(t={}){this.required=t.stableFramesRequired??10,this.maxRatio=t.maxDisplacementRatio??.015,this.switchFrames=Math.max(1,t.hintSwitchFrames??3);}get snapshot(){return this.current}reset(){this.current=Ye,this.candidate=null;}update(t,r){if(!t)return this.reset(),this.current;let n=Math.hypot(r.width,r.height)*this.maxRatio,o=this.current,a=o.quad!==null&&o.quad.every((u,c)=>Math.hypot(t.quad[c].x-u.x,t.quad[c].y-u.y)<=n),i=o.typeHint??t.typeHint,s=a?o.stableFrames+1:1;if(t.typeHint===i)this.candidate=null;else {let u=this.candidate?.typeHint===t.typeHint?this.candidate.frames+1:1;this.candidate={typeHint:t.typeHint,frames:u},u>=this.switchFrames&&(i=t.typeHint,s=1,this.candidate=null);}return this.current={stableFrames:s,isStable:s>=this.required,quad:t.quad,typeHint:i},this.current}};var Yt=640,Kt=10,Ke=1500,$t=35e3,Zt=5,Jt={createWorkerClient:e=>new Q(e),attachCamera:e=>ie(e),detachCamera:se,sampleFrame:ce,cropFrame:Pe,snapshotToBlob:ue,supportsTorch:le,setTorch:de,now:()=>performance.now()},$e=e=>e.provenance==="reduced"||e.provenance==="lowres",Ze=e=>e instanceof Error?e:new Error(String(e)),Je=e=>e.map(t=>({x:t.x,y:t.y})),V=class{opts;deps;stream=null;track=null;cv=null;generation=0;timer=null;frameCallback=null;warmupUntil=0;capturing=false;autoShutterSpent=false;torchSupported=false;torchOn=false;torchRejected=false;weakRun=false;prior=null;stability;state={status:"idle",lastFrame:null,lastResult:null,lastError:null};constructor(t,r={}){this.opts={videoElement:t.videoElement,documentTypes:t.documentTypes??["INE","PASSPORT_MX"],cropToGuide:t.cropToGuide??false,guideRatio:t.guideRatio,autoCapture:t.autoCapture??false,stableFramesRequired:t.stableFramesRequired??10,captureQuality:t.captureQuality??.92,captureResolution:t.captureResolution??"standard",opencvUrl:t.opencvUrl,workerUrl:t.workerUrl,onUpload:t.onUpload,onFrameAnalyzed:t.onFrameAnalyzed,onCaptured:t.onCaptured,onResult:t.onResult,onError:t.onError},this.deps={...Jt,...r},this.stability=new z({stableFramesRequired:this.opts.stableFramesRequired});}async start(){if(!["idle","stopped","error"].includes(this.state.status))throw new Error(`Cannot start session from status=${this.state.status}`);this.release();let t=++this.generation;this.setStatus("starting"),this.state.lastError=null;try{let r=await this.deps.attachCamera(this.opts.videoElement);if(t!==this.generation){this.deps.detachCamera(this.opts.videoElement,r);return}this.stream=r,this.track=r.getVideoTracks()[0]??null,this.torchSupported=this.track?this.deps.supportsTorch(this.track):!1,this.warmupUntil=0,this.setStatus("loading_detector");let n=this.deps.createWorkerClient({initTimeoutMs:$t});this.cv=n,n.init(this.opts.opencvUrl,this.opts.workerUrl).then(()=>{t!==this.generation||this.cv!==n||(this.state.status==="loading_detector"&&this.setStatus("streaming"),this.scheduleNext(t,0));},o=>this.detectorUnavailable(t,n,o));}catch(r){throw t===this.generation&&this.emitError(r),r}}stop(){this.generation++,this.release(),this.setStatus("stopped");}async captureNow(){if(this.capturing)return;let t=this.state.status;if(t!=="streaming"&&t!=="loading_detector"&&!(t==="done"&&this.stream))throw new Error(`Cannot capture from status=${this.state.status}`);let r=this.stability.snapshot,n=this.state.lastFrame?.frameSize??null,o=r.quad&&n?this.fullFrameQuad(r.quad,n):null;await this.doCapture(this.generation,o?r.typeHint:null);}getState(){return {...this.state}}scheduleNext(t,r){this.timer=setTimeout(()=>{if(this.timer=null,t!==this.generation)return;let n=this.opts.videoElement;typeof n.requestVideoFrameCallback=="function"?this.frameCallback=n.requestVideoFrameCallback(()=>{this.frameCallback=null,this.tick(t);}):this.tick(t);},r);}async tick(t){if(t!==this.generation)return;let r=this.deps.now();try{await this.analyzeFrame(t);}catch(n){t===this.generation&&console.warn("[xid-camera-ocr] analyzeFrame error:",n);}t!==this.generation||!this.cv||this.scheduleNext(t,Math.max(0,1e3/Kt-(this.deps.now()-r)));}async analyzeFrame(t){let r=this.cv;if(this.capturing||!r?.isReady())return;let n=this.deps.sampleFrame(this.opts.videoElement,Yt);if(!n)return;let o={width:n.width,height:n.height};this.warmupUntil===0&&(this.warmupUntil=this.deps.now()+Ke);let a=this.prior&&this.prior.width===n.width&&this.prior.height===n.height?this.prior.quad:null,i;try{i=await r.analyze(n,a?{prior:a}:{});}catch(m){if(t===this.generation&&this.cv===r&&!r.isReady()){this.detectorUnavailable(t,r,m);return}throw m}if(t!==this.generation||this.capturing)return;this.prior=i.detected&&i.corners?{quad:Je(i.corners),...o}:null;let u=!(this.deps.now()<this.warmupUntil)&&i.detected&&i.corners&&i.documentType?{quad:i.corners,typeHint:i.documentType}:null,c=this.stability.update(u,o);this.weakRun=c.stableFrames<=1?u!==null&&$e(i):this.weakRun||$e(i);let d=this.opts.stableFramesRequired+(this.weakRun?Zt:0);if(await this.updateTorch(t,i),t!==this.generation)return;let p={detected:i.detected,documentType:i.documentType,confidence:Math.min(c.stableFrames/d,1),aspectRatio:i.aspectRatio,quality:i.quality,corners:i.corners?Je(i.corners):null,frameSize:o,hint:ge({detected:i.detected,quality:i.quality,stableFrames:c.stableFrames,stableFramesRequired:d}),...this.torchSupported?{torch:this.torchOn}:{}};if(this.state.lastFrame=p,this.opts.onFrameAnalyzed?.(p),t===this.generation&&this.opts.autoCapture&&!this.autoShutterSpent&&c.quad&&c.stableFrames>=d&&i.quality?.ok){if(!this.fullFrameQuad(c.quad,o)){this.stability.reset(),this.weakRun=false;return}await this.doCapture(t,c.typeHint);}}detectorUnavailable(t,r,n){if(t!==this.generation||this.cv!==r)return;r.terminate(),this.cv=null,this.prior=null,this.stability.reset(),this.weakRun=false,this.autoShutterSpent=true,this.state.status==="loading_detector"&&this.setStatus("streaming");let o=Ze(n);this.state.lastError=o;let a={detected:false,documentType:null,confidence:0,aspectRatio:null,quality:null,corners:null,frameSize:null,hint:"detector_unavailable",...this.torchSupported?{torch:this.torchOn}:{}};this.state.lastFrame=a,this.opts.onFrameAnalyzed?.(a),t===this.generation&&this.opts.onError?.(o);}async updateTorch(t,r){if(!this.track||this.torchRejected||!Ve({torchSupported:this.torchSupported,torchOn:this.torchOn,quality:r.quality,frameBrightness:r.frameBrightness}))return;let n=await this.deps.setTorch(this.track,true);t===this.generation&&(this.torchOn=n,n||(this.torchRejected=true));}fullFrameQuad(t,r){let n=this.opts.videoElement;return !n.videoWidth||!n.videoHeight?null:Ge(t,r,{width:n.videoWidth,height:n.videoHeight})}async doCapture(t,r){if(!this.capturing){this.capturing=true,this.autoShutterSpent=true,this.prior=null,this.stability.reset(),this.weakRun=false,this.setStatus("capturing");try{let{blob:n,type:o}=await this.captureBlob(t,r);if(t!==this.generation)return;this.opts.onCaptured?.(n,o),this.setStatus("uploading");let a=await this.opts.onUpload(n,o);if(t!==this.generation)return;this.state.lastResult=a,this.setStatus("done"),this.opts.onResult?.(a);}catch(n){t===this.generation&&this.emitError(n);}finally{t===this.generation&&(this.stability.reset(),this.weakRun=false,this.warmupUntil=this.deps.now()+Ke,this.capturing=false);}}}async captureBlob(t,r){let n=this.opts.videoElement,o=async()=>({blob:await this.deps.snapshotToBlob(n,this.opts.captureQuality),type:null});if(!this.opts.cropToGuide)return {blob:await this.deps.snapshotToBlob(n,this.opts.captureQuality),type:r};let a={width:n.clientWidth,height:n.clientHeight},i=this.opts.guideRatio?.()??H(this.opts.documentTypes),s=We({width:n.videoWidth,height:n.videoHeight},a,q(a,i));if(!s)return o();try{let u=this.opts.captureResolution==="high"?Qe:N;return {blob:await this.deps.cropFrame(n,s,je(s,u),this.opts.captureQuality),type:r}}catch(u){if(t!==this.generation)throw u;return console.warn("[xid-camera-ocr] guide crop failed, using plain snapshot:",u),o()}}release(){this.prior=null,this.timer!==null&&clearTimeout(this.timer),this.timer=null,this.frameCallback!==null&&(this.opts.videoElement.cancelVideoFrameCallback?.(this.frameCallback),this.frameCallback=null),this.track&&this.torchOn&&this.deps.setTorch(this.track,false),this.torchOn=false,this.torchRejected=false,this.stream&&this.deps.detachCamera(this.opts.videoElement,this.stream),this.stream=null,this.track=null,this.cv?.terminate(),this.cv=null,this.stability.reset(),this.weakRun=false,this.capturing=false,this.autoShutterSpent=false;}setStatus(t){this.state.status=t;}emitError(t){let r=Ze(t);this.state.lastError=r,this.setStatus("error"),this.opts.onError?.(r);}};var et=.08,er=.95,tr=.02,B=8,rr=2,tt=8,rt=.33,te=2,nr=2,or=1,ar=nr+te,ir=2,sr=2*(te+1),ur=10,nt=1.1,st=te+1,cr=6,ut=.92,lr=1/ut,dr=.85,pr=2,mr=.05,ot=2,at=64;function ct(e){return {detected:false,documentType:null,aspectRatio:null,corners:null,boundingBox:null,quality:null,frameBrightness:e,score:null}}function hr(e,t,r,n={}){let o={brightness:0};try{return yr(e,t,o,n.prior??null)}catch(a){return console.warn("[xid-camera-ocr] detectDocumentCV failed:",a),ct(o.brightness)}}function yr(e,t,r,n){let{width:o,height:a}=t,i=[],s=u=>(i.push(u),u);try{let u=s(e.matFromImageData(t)),c=s(new e.Mat),d=s(new e.Mat),p={equalized:s(new e.Mat),filtered:s(new e.Mat),padded:s(new e.Mat),edges:s(new e.Mat),dilated:s(new e.Mat),kernel:s(e.Mat.ones(3,3,e.CV_8U)),contours:s(new e.MatVector),hierarchy:s(new e.Mat),hull:s(new e.Mat),approx:s(new e.Mat),clahe:s(new e.CLAHE(rr,new e.Size(tt,tt)))};e.cvtColor(u,c,e.COLOR_RGBA2GRAY),r.brightness=ze(we(c,o,a));let m=[],l=it(e,c,p,o,a,m,n),x=!1,S=Math.round(o/ot),y=Math.round(a/ot);if(!l&&S>=at&&y>=at&&(e.resize(c,d,new e.Size(S,y),0,0,e.INTER_AREA),l=it(e,d,p,o,a,m,n),x=l!==null),l&&wr(l,m,we(c,o,a))&&(l=null),!l)return ct(r.brightness);let b=x?"lowres":l.reduced?"reduced":"direct",h=l.quad.map(C=>C.x),f=l.quad.map(C=>C.y),T=Math.min(...h),R=Math.max(...h),M=Math.min(...f),v=Math.max(...f);return {detected:!0,documentType:J(l.aspect),aspectRatio:l.aspect,corners:l.quad,boundingBox:{x:T/o,y:M/a,width:(R-T)/o,height:(v-M)/a},quality:be(we(c,o,a),pt(l.quad,st*l.scale)),frameBrightness:r.brightness,score:b==="direct"?l.score:l.score*dr,provenance:b}}finally{for(let u of i)u.delete();}}function it(e,t,r,n,o,a,i){let s=t.cols,u=t.rows,c=n/s,d=o/u,p=s*u,m=h=>({x:h.x*c,y:h.y*d}),{hull:l,approx:x,contours:S}=r;r.clahe.apply(t,r.equalized),e.bilateralFilter(r.equalized,r.filtered,9,75,75,e.BORDER_DEFAULT),e.copyMakeBorder(r.filtered,r.padded,B,B,B,B,e.BORDER_CONSTANT,new e.Scalar(0));let y=Mr(r.padded.data);e.Canny(r.padded,r.edges,Math.max(10,(1-rt)*y),Math.min(255,(1+rt)*y)),e.dilate(r.edges,r.dilated,r.kernel,new e.Point(-1,-1),te),e.findContours(r.dilated,S,r.hierarchy,e.RETR_LIST,e.CHAIN_APPROX_SIMPLE);let b=null;for(let h=0;h<S.size();h++){let f=S.get(h);try{let T=e.boundingRect(f);if(T.width*T.height<p*et)continue;e.convexHull(f,l,!1,!0);let R=e.contourArea(l);if(R<p*et||R>p*er||(e.approxPolyDP(l,x,tr*e.arcLength(l,!0),!0),x.rows<4||x.rows>cr||!e.isContourConvex(x)))continue;let M=x.rows!==4,v;if(!M)v=br(x,s,u);else {let _e=gr(lt(x));if(!_e)continue;v=_e.map(Rt=>dt(Rt,s,u));let Ae=R/D(v);if(!(Ae>=ut&&Ae<=lr))continue}let C=R*c*d,E=v.map(m),_=ar*c;if(Tr(E,n,o,_)>or){Sr(E,n,o,_)||a.push({pts:E,area:C});continue}let P=he(E);if(!Z(P))continue;let O=e.minAreaRect(M?l:x),Ee=O.size.width*O.size.height,Ct=Ee>0?Math.min(1,R/Ee):0,oe=X(P);if(!ye(oe))continue;let ae=Ue({areaRatio:R/p,rectangularity:Ct,aspect:oe}),ke=i?ae-fr(P,i,n,o):ae;(!b||ke>b.rank)&&(b={quad:P,score:ae,rank:ke,aspect:oe,area:C,scale:c,reduced:M});}finally{f.delete();}}return b}function fr(e,t,r,n){let o=1/0;for(let a=0;a<4;a++){let i=0;for(let s=0;s<4;s++){let u=e[(s+a)%4];i=Math.max(i,Math.hypot(u.x-t[s].x,u.y-t[s].y));}o=Math.min(o,i);}return pr*Math.min(o/Math.hypot(r,n),mr)}function we(e,t,r){return {data:e.data,width:t,height:r}}function lt(e){let t=e.data32S,r=[];for(let n=0;n<e.rows;n++)r.push({x:t[n*2]-B,y:t[n*2+1]-B});return r}function dt(e,t,r){return {x:Math.min(t,Math.max(0,e.x)),y:Math.min(r,Math.max(0,e.y))}}function br(e,t,r){return lt(e).map(n=>dt(n,t,r))}function gr(e){let t=e;for(;t.length>4;){let r=t.length,n=-1,o=1/0,a=null;for(let s=0;s<r;s++){let u=t[(s+r-1)%r],c=t[s],d=t[(s+1)%r],p=t[(s+2)%r],m=xr(u,c,p,d);if(!m)continue;let l=Math.abs((m.x-c.x)*(d.y-c.y)-(m.y-c.y)*(d.x-c.x))/2;l<o&&(o=l,n=s,a=m);}if(n<0||!a)return null;let i=[];for(let s=0;s<r;s++)s===n?i.push(a):s!==(n+1)%r&&i.push(t[s]);t=i;}return t}function xr(e,t,r,n){let o=t.x-e.x,a=t.y-e.y,i=n.x-r.x,s=n.y-r.y,u=o*s-a*i;if(Math.abs(u)<1e-9)return null;let c=((r.x-e.x)*s-(r.y-e.y)*i)/u,d=((r.x-e.x)*a-(r.y-e.y)*o)/u;return c<1||d<1?null:{x:e.x+c*o,y:e.y+c*a}}function Sr(e,t,r,n){let o=e.map(i=>i.x),a=e.map(i=>i.y);return Math.min(...o)<=n&&Math.max(...o)>=t-n||Math.min(...a)<=n&&Math.max(...a)>=r-n}function wr(e,t,r){let n=sr*e.scale;if(!t.some(s=>s.area>=ir*e.area&&e.quad.every(u=>Rr(s.pts,u,n))))return false;if(J(e.aspect)==="OTHER")return true;let{inner:a,ring:i}=Cr(r,pt(e.quad,st*e.scale),e.quad);return a<i-ur}function Cr(e,t,r){let{data:n,width:o,height:a}=e,i=(r[0].x+r[1].x+r[2].x+r[3].x)/4,s=(r[0].y+r[1].y+r[2].y+r[3].y)/4,u=r.map(y=>({x:i+(y.x-i)*nt,y:s+(y.y-s)*nt})),c=W(u,o,a),d=W(r,o,a),p=W(t,o,a),m=0,l=0,x=0,S=0;for(let y=0;y<a;y++){let b=c[y*2],h=c[y*2+1];if(b>=h)continue;let f=d[y*2],T=d[y*2+1],R=p[y*2],M=p[y*2+1],v=y*o;for(let C=b;C<h;C++){let E=n[v+C];C>=R&&C<M?(m+=E,l++):(C<f||C>=T)&&(x+=E,S++);}}return {inner:l>0?m/l:0,ring:S>0?x/S:0}}function Rr(e,t,r){let n=0;for(let a=0;a<e.length;a++){let i=e[a],s=e[(a+1)%e.length];n+=i.x*s.y-s.x*i.y;}let o=Math.sign(n);if(o===0)return false;for(let a=0;a<e.length;a++){let i=e[a],s=e[(a+1)%e.length],u=Math.hypot(s.x-i.x,s.y-i.y);if(u===0)continue;if(o*((s.x-i.x)*(t.y-i.y)-(s.y-i.y)*(t.x-i.x))/u<r)return false}return true}function pt(e,t){return e.map((r,n)=>{let o=e[(n+3)%4],a=e[(n+1)%4],i=o.x-r.x,s=o.y-r.y,u=a.x-r.x,c=a.y-r.y,d=Math.hypot(i,s),p=Math.hypot(u,c),m=i/d+u/p,l=s/d+c/p,x=Math.hypot(m,l),S=Math.sin(Math.acos(Math.min(1,Math.max(-1,(i*u+s*c)/(d*p))))/2),y=t/S/x;return {x:r.x+m*y,y:r.y+l*y}})}function Tr(e,t,r,n){let o=0;for(let a of e)(a.x<=n||a.x>=t-n||a.y<=n||a.y>=r-n)&&o++;return o}function Mr(e){let t=new Uint32Array(256);for(let n=0;n<e.length;n++)t[e[n]]++;let r=0;for(let n=0;n<256;n++)if(r+=t[n],r>=e.length/2)return n;return 128}function Re(e){let t=react.useRef(null),r=react.useRef(null),[n,o]=react.useState("idle"),[a,i]=react.useState(null),[s,u]=react.useState(null),[c,d]=react.useState(null),p=react.useRef(e);p.current=e;let m=react.useCallback(()=>{if(r.current)return r.current;if(!t.current)throw new Error("videoRef not attached yet");let b=new V({videoElement:t.current,documentTypes:p.current.documentTypes,cropToGuide:p.current.cropToGuide,guideRatio:()=>H(p.current.documentTypes),autoCapture:p.current.autoCapture,stableFramesRequired:p.current.stableFramesRequired,captureQuality:p.current.captureQuality,captureResolution:p.current.captureResolution,opencvUrl:p.current.opencvUrl,workerUrl:p.current.workerUrl,onUpload:(h,f)=>p.current.onUpload(h,f),onFrameAnalyzed:h=>{i(h),r.current===b&&o(b.getState().status),p.current.onFrameAnalyzed?.(h);},onCaptured:(h,f)=>{o("capturing"),p.current.onCaptured?.(h,f);},onResult:h=>{u(h),o("done"),p.current.onResult?.(h);},onError:h=>{d(h),r.current===b&&o(b.getState().status),p.current.onError?.(h);}});return r.current=b,b},[]),l=react.useCallback(async()=>{let b=m();await b.start(),r.current===b&&o(b.getState().status);},[m]),x=react.useCallback(()=>{r.current?.stop(),o("stopped");},[]),S=react.useCallback(async()=>{await r.current?.captureNow();},[]),y=react.useCallback(async()=>{x(),u(null),d(null),i(null),await l();},[x,l]);return react.useEffect(()=>(e.autoStart!==false&&t.current&&l().catch(()=>{}),()=>{r.current?.stop(),r.current=null;}),[]),{videoRef:t,status:n,frame:a,result:s,error:c,start:l,stop:x,captureNow:S,retake:y}}function kr({status:e,showTips:t,dismissed:r,completedOnce:n}){return t&&!r&&(e==="error"||!n&&(e==="starting"||e==="loading_detector"))}function mt(e,t){let[r,n]=react.useState(false),[o,a]=react.useState(e==="done"),[i,s]=react.useState(e),u=r,c=o;i!==e&&(s(e),e==="error"&&(u=false,n(false)),e==="done"&&(c=true,a(true)));let d=react.useCallback(()=>n(true),[]);return {visible:kr({status:e,showTips:t,dismissed:u,completedOnce:c}),dismiss:d}}var ne={accent:{name:"--xco-accent",fallback:"#22c55e"},warning:{name:"--xco-warning",fallback:"#eab308"},danger:{name:"--xco-danger",fallback:"#ef4444"},background:{name:"--xco-background",fallback:"#000"},mask:{name:"--xco-mask",fallback:"rgba(0,0,0,0.4)"},hintBg:{name:"--xco-hint-bg",fallback:"rgba(0,0,0,0.7)"},hintFg:{name:"--xco-hint-fg",fallback:"white"},buttonBg:{name:"--xco-button-bg",fallback:"rgba(255,255,255,0.92)"},buttonFg:{name:"--xco-button-fg",fallback:"#111"},shutter:{name:"--xco-shutter",fallback:"rgba(255,255,255,0.9)"},overlayBg:{name:"--xco-overlay-bg",fallback:"rgba(0,0,0,0.78)"},overlayFg:{name:"--xco-overlay-fg",fallback:"white"},fontFamily:{name:"--xco-font-family",fallback:"system-ui, -apple-system, sans-serif"},radius:{name:"--xco-radius",fallback:"12px"}};function g(e){let{name:t,fallback:r}=ne[e];return `var(${t}, ${r})`}function ht(e){let t={};if(!e)return t;for(let r of Object.keys(e)){if(!Object.prototype.hasOwnProperty.call(ne,r))continue;let n=e[r];typeof n!="string"||n===""||(t[ne[r].name]=n);}return t}function yt(e,t){return e==="done"?g("accent"):e==="error"?g("danger"):e==="uploading"||e==="capturing"?g("accent"):t?.detected?t.confidence>=1?g("accent"):g("warning"):g("danger")}var Me={width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",focusable:"false",style:{flex:"0 0 auto"}},_r=()=>jsxRuntime.jsxs("svg",{...Me,children:[jsxRuntime.jsx("circle",{cx:"12",cy:"12",r:"4"}),jsxRuntime.jsx("path",{d:"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"})]}),Ar=()=>jsxRuntime.jsxs("svg",{...Me,children:[jsxRuntime.jsx("rect",{x:"3",y:"5",width:"18",height:"14",rx:"2"}),jsxRuntime.jsx("path",{d:"M14 5l-6 14M18 5l-6 14"})]}),Pr=()=>jsxRuntime.jsxs("svg",{...Me,children:[jsxRuntime.jsx("path",{d:"M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3"}),jsxRuntime.jsx("circle",{cx:"12",cy:"12",r:"2"})]});function ft({texts:e,onDismiss:t}){let r=[{Icon:_r,text:e.tip_light},{Icon:Ar,text:e.tip_glare},{Icon:Pr,text:e.tip_focus}];return jsxRuntime.jsxs("div",{className:"xco-tips",style:Fr,children:[jsxRuntime.jsx("ul",{style:Or,children:r.map(({Icon:n,text:o})=>jsxRuntime.jsxs("li",{style:Ir,children:[jsxRuntime.jsx(n,{}),jsxRuntime.jsx("span",{children:o})]},o))}),jsxRuntime.jsx("button",{type:"button",className:"xco-tips-dismiss",onClick:t,style:Dr,children:e.tips_dismiss})]})}var Fr={position:"absolute",top:"calc(16px + env(safe-area-inset-top, 0px))",left:"50%",transform:"translateX(-50%)",boxSizing:"border-box",width:"calc(100% - 32px)",maxWidth:360,padding:16,background:g("overlayBg"),color:g("overlayFg"),borderRadius:g("radius"),fontFamily:g("fontFamily"),fontSize:15,lineHeight:1.35,display:"flex",flexDirection:"column",gap:12,pointerEvents:"auto",zIndex:5},Or={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:10},Ir={display:"flex",alignItems:"center",gap:12},Dr={alignSelf:"flex-end",minHeight:44,padding:"10px 20px",borderRadius:g("radius"),background:g("buttonBg"),color:g("buttonFg"),border:"none",fontFamily:g("fontFamily"),fontSize:15,fontWeight:600,cursor:"pointer",WebkitTapHighlightColor:"transparent"};var Qr=typeof window>"u"?react.useEffect:react.useLayoutEffect;function gt({videoRef:e,status:t,frame:r,texts:n,documentTypes:o,theme:a,className:i,style:s,hideStatusOverlay:u,hideShutter:c,tipsVisible:d,onCapture:p,onRetake:m,onStart:l,onDismissTips:x}){let S=t==="streaming"||t==="loading_detector",y=t==="done"||t==="error",b=t==="idle"||t==="stopped",h=t==="capturing"||t==="uploading",f=react.useRef(null),[T,R]=react.useState({width:0,height:0}),M=H(o);Qr(()=>{let _=f.current;if(!_)return;let P=()=>R(q({width:_.clientWidth,height:_.clientHeight},M));P();let O=new ResizeObserver(P);return O.observe(_),()=>O.disconnect()},[M]);let v=yt(t,r),C=ee(t,r?.hint,n),E=i?`xid-camera-ocr ${i}`:"xid-camera-ocr";return jsxRuntime.jsxs("div",{ref:f,className:E,"data-status":t,"data-hint":r?.hint??"",style:{...Xr,...ht(a),...s},children:[jsxRuntime.jsx("style",{children:"@keyframes xco-spin { to { transform: rotate(360deg); } }"}),jsxRuntime.jsx("video",{ref:e,playsInline:true,muted:true,autoPlay:true,style:Nr}),!u&&jsxRuntime.jsx("div",{style:qr,children:jsxRuntime.jsx("div",{className:"xco-guide",style:{...Wr,width:T.width,height:T.height,borderColor:v}})}),!u&&(b?jsxRuntime.jsx("button",{type:"button",className:"xco-hint",onClick:l,style:$r,children:C}):jsxRuntime.jsx("p",{className:"xco-hint",style:St,role:"status","aria-live":"polite",children:C})),!u&&!c&&S&&jsxRuntime.jsx("button",{type:"button",className:"xco-shutter",onClick:p,"aria-label":n.capture,style:Gr,children:jsxRuntime.jsx("span",{style:Yr})}),!u&&y&&jsxRuntime.jsx("button",{type:"button",className:"xco-retake",onClick:m,"aria-label":n.retake,style:Kr,children:n.retake}),!u&&h&&jsxRuntime.jsxs("div",{className:"xco-loader",style:jr,"aria-busy":"true","aria-live":"polite",children:[jsxRuntime.jsx("div",{style:zr}),jsxRuntime.jsx("p",{style:Vr,children:ee(t,void 0,n)})]}),d&&!u&&jsxRuntime.jsx(ft,{texts:n,onDismiss:x})]})}var xt="calc(104px + env(safe-area-inset-bottom, 0px))",Xr={position:"relative",width:"100%",height:"100%",overflow:"hidden",background:g("background"),fontFamily:g("fontFamily"),touchAction:"none"},Nr={position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover",display:"block"},qr={position:"absolute",inset:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",pointerEvents:"none"},Wr={borderRadius:g("radius"),boxSizing:"content-box",borderWidth:4,borderStyle:"solid",boxShadow:`0 0 0 9999px ${g("mask")}`,transition:"border-color 200ms ease-in-out"},jr={position:"absolute",inset:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:20,background:g("overlayBg"),backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",pointerEvents:"auto",zIndex:10},zr={width:56,height:56,borderRadius:"50%",border:"5px solid rgba(255, 255, 255, 0.18)",borderTopColor:g("accent"),animation:"xco-spin 0.85s linear infinite"},Vr={color:g("overlayFg"),fontFamily:g("fontFamily"),fontSize:15,fontWeight:500,margin:0,textAlign:"center",padding:"0 24px"},Gr={position:"absolute",bottom:xt,left:"50%",transform:"translateX(-50%)",width:68,height:68,borderRadius:"50%",background:"rgba(255,255,255,0.18)",border:`4px solid ${g("shutter")}`,cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",pointerEvents:"auto",WebkitTapHighlightColor:"transparent",padding:0},Yr={width:52,height:52,borderRadius:"50%",background:"white",display:"block"},Kr={position:"absolute",bottom:xt,left:"50%",transform:"translateX(-50%)",minHeight:44,padding:"10px 20px",borderRadius:22,background:g("buttonBg"),color:g("buttonFg"),border:"none",fontFamily:g("fontFamily"),fontSize:15,fontWeight:600,cursor:"pointer",pointerEvents:"auto",WebkitTapHighlightColor:"transparent"},St={position:"absolute",bottom:"calc(16px + env(safe-area-inset-bottom, 0px))",left:"50%",transform:"translateX(-50%)",boxSizing:"border-box",width:"max-content",maxWidth:"calc(100% - 32px)",background:g("hintBg"),color:g("hintFg"),padding:"10px 16px",borderRadius:g("radius"),fontFamily:g("fontFamily"),fontSize:16,lineHeight:1.35,margin:0,whiteSpace:"normal",textAlign:"center"},$r={...St,border:"none",minHeight:44,cursor:"pointer",pointerEvents:"auto",WebkitTapHighlightColor:"transparent"};var nn=["INE","PASSPORT_MX"],ve=()=>{},wt=react.forwardRef(function({className:t,style:r,hideStatusOverlay:n,texts:o,theme:a,showTips:i,hideShutter:s,onStatusChange:u,...c},d){let{videoRef:p,status:m,frame:l,start:x,captureNow:S,retake:y}=Re({...c,cropToGuide:!n}),b=mt(m,i??true),h=react.useMemo(()=>Se(o),[o]),f=m==="streaming"||m==="loading_detector",T=m==="done"||m==="error",R=async()=>{f&&await S().catch(ve);},M=()=>{T&&y().catch(ve);},v=async()=>{await x().catch(ve);};react.useImperativeHandle(d,()=>({capture:R,retake:M,start:v}));let C=react.useRef(u);return C.current=u,react.useEffect(()=>{C.current?.(m);},[m]),jsxRuntime.jsx(gt,{videoRef:p,status:m,frame:l,texts:h,documentTypes:c.documentTypes??nn,theme:a,className:t,style:r,hideStatusOverlay:n,hideShutter:s,tipsVisible:b.visible,onCapture:()=>{R();},onRetake:M,onStart:()=>{v();},onDismissTips:b.dismiss})});wt.displayName="XidCameraOcr";exports.CVWorkerClient=Q;exports.DEFAULT_TEXTS_EN=Nt;exports.DEFAULT_TEXTS_ES=xe;exports.ID1_RATIO=I;exports.QUALITY_THRESHOLDS=k;exports.StabilityTracker=z;exports.TD3_RATIO=U;exports.XCO_VARS=ne;exports.XidCameraOcr=wt;exports.XidCameraOcrSession=V;exports.attachCamera=ie;exports.copyFor=ee;exports.decideHint=ge;exports.defaultOpencvUrl=pe;exports.detachCamera=se;exports.detectDocumentCV=hr;exports.guideBox=q;exports.guideRatioFor=H;exports.isCapturableAspect=ye;exports.measureQuality=be;exports.orderCorners=he;exports.rectifiedAspect=X;exports.resolveTexts=Se;exports.sampleFrame=ce;exports.setTorch=de;exports.snapshotToBlob=ue;exports.supportsTorch=le;exports.typeHintFromAspect=J;exports.useXidCameraOcr=Re;exports.warpDocument=Ne;exports.warpDocumentImageData=Xe;exports.warpedSnapshotToBlob=Ht;//# sourceMappingURL=index.cjs.map
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|