@exige/web-scanner-sdk 0.2.27 → 0.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -173,7 +173,7 @@ Auto-capture modes are exactly `defaultOn`, `defaultOff`, `alwaysOn`, `alwaysOff
173
173
 
174
174
  Document finding always uses DocAligner LCNet100. `detectDocument`, `scanDocument`, and `startFinder(...).detection` load Exige's immutable, CORS-enabled CDN model by default. Omit `modelUrl`. Inference or geometry failure stays visible as `modelStatus: "failed"`. Worker startup throws `ScannerError` with code `worker`. A failed DocAligner HTTP response throws `ScannerError` with code `model-http`. The finder catches both and still reports `modelStatus: "failed"` so the camera stays up. A bad image is `no-document`, `low-confidence`, or `low-quality` and does not throw.
175
175
 
176
- The worker lazy-loads `onnxruntime-web` (kept external; it is not concatenated into `scanner-sdk.js`), runs DocAligner on WASM, reuses sessions, and caches model bytes in IndexedDB plus the browser cache. The JSEP wasm and its glue (`ort-wasm-simd-threaded.jsep.wasm` and `ort-wasm-simd-threaded.jsep.mjs`) ship in the package. Document finding loads them with literal `new URL` calls when the host bundler emits them. Next 15 `next dev --turbo` does not copy those siblings; the worker then uses pinned `onnxruntime-web@1.30.0` on the Exige demo CDN or jsDelivr. A host app does not copy `/ort/` or set `ortWasmPaths` for the default path. Omit `ortWasmPaths`. Pass it only to replace those files: a string is a directory that contains both, or an object `{ wasm, mjs }` sets each URL. A 404 override still falls back to the pin. The DocAligner weights stay on the CDN, with neighboring Apache-2.0 attribution, and are copied only into the demo build. See the [VitePress ML detector guide](site/guide/ml-document-detector.md).
176
+ The worker lazy-loads `onnxruntime-web` (kept external; it is not concatenated into `scanner-sdk.js`), runs DocAligner on WebGPU on WebKit when `navigator.gpu` is exposed (WASM if the session fails or heatmaps are empty), and stays WASM-only on Android, reuses sessions, and caches model bytes in IndexedDB plus the browser cache. The JSEP wasm and its glue (`ort-wasm-simd-threaded.jsep.wasm` and `ort-wasm-simd-threaded.jsep.mjs`) ship in the package. Document finding loads them with literal `new URL` calls when the host bundler emits them. Next 15 `next dev --turbo` does not copy those siblings; the worker then uses pinned `onnxruntime-web@1.30.0` on the Exige demo CDN or jsDelivr. A host app does not copy `/ort/` or set `ortWasmPaths` for the default path. Omit `ortWasmPaths`. Pass it only to replace those files: a string is a directory that contains both, or an object `{ wasm, mjs }` sets each URL. A 404 override still falls back to the pin. The DocAligner weights stay on the CDN, with neighboring Apache-2.0 attribution, and are copied only into the demo build. See the [VitePress ML detector guide](site/guide/ml-document-detector.md).
177
177
 
178
178
  ### Full-screen React camera
179
179
 
@@ -215,7 +215,7 @@ await setLicenseKey(licenseKey)
215
215
  </ScannerProvider>
216
216
  ```
217
217
 
218
- `useScanner()` reads the session inside the provider. `useScannerRef()` gives the parent an imperative handle, passed as `apiRef`, the same idea as MUI's `useGridApiRef`. The camera covers the viewport, respects safe areas, shows finder/permission/error states, and keeps the existing auto-capture policies. On Android, `captureMode="auto"` skips the live preview and opens the system Camera app as soon as `open` becomes true from a user gesture; DocAligner then finds the most prominent document on that JPEG. A miss stays on screen with **Try again**. iOS keeps the live overlay and auto-capture. The live path requests an ideal 4K rear-camera stream (no 1080×1920 Android cap), shows that stream in the preview, and continuously mirrors the last non-black frame. Outside WebKit, live capture uses `ImageCapture.takePhoto()` at max photo size and keeps the still even when its aspect differs from the preview; a still longer than 4096px is scaled with high-quality smoothing. WebKit `takePhoto()` returns black stills, so iOS copies the live preview or the mirrored frame. A genuinely blank capture fails loudly instead of exporting black pixels. Finder analysis stays on a 520–1600 working copy. Render `ScannerDiagnostics` for a live diagnostics panel with engine/browser, video and track state, luminance, and the chosen capture path; **Copy report** pastes a shutter-time dump for support. Pass `theme` to `ScannerFrame` to recolor the camera. `primaryColorHex` replaces the yellowish-green accents (`#c8ff45`). `seekingColorHex` and `detectingColorHex` are the waiting box colors (both `#ef6a5b` unless set apart), `unstableColorHex` is `#ffbe40`, `readyColorHex` is `#8af25a`, and `capturedColorHex` is `#ffffff`. Omit `theme` or any field to keep that default. The [React camera guide](site/guide/react-camera-overlay.md#theme) lists what each field paints. The demo source screen chooses Default, Ocean, Violet, or Signal before the camera starts and passes that palette as `theme` on `ScannerFrame`. Capture results include the displayed quad and its bounded-analysis dimensions; use `mapDocumentCornersToFrame()` plus `cropDocument()` so the final crop matches what was on screen. The framework-free imperative APIs remain available from the root package.
218
+ `useScanner()` reads the session inside the provider. `useScannerRef()` gives the parent an imperative handle, passed as `apiRef`, the same idea as MUI's `useGridApiRef`. The camera covers the viewport, respects safe areas, shows finder/permission/error states, and keeps the existing auto-capture policies. On Android, `captureMode="auto"` skips the live preview and opens the system Camera app as soon as `open` becomes true from a user gesture; DocAligner then finds the most prominent document on that JPEG. A miss stays on screen with **Try again**. iOS keeps the live overlay and auto-capture. `useCase="drivers-license"` captures the front, then the back, and returns AAMVA fields from the PDF417 barcode (`onDriverLicense`). It does not call `onCapture`. See [Driver’s license](site/guide/drivers-license.md). The live path requests an ideal 4K rear-camera stream (no 1080×1920 Android cap), shows that stream in the preview, and continuously mirrors the last non-black frame. Outside WebKit, live capture uses `ImageCapture.takePhoto()` at max photo size and keeps the still even when its aspect differs from the preview; a still longer than 4096px is scaled with high-quality smoothing. WebKit `takePhoto()` returns black stills, so iOS copies the live preview or the mirrored frame. A genuinely blank capture fails loudly instead of exporting black pixels. Finder analysis stays on a 520–1600 working copy. Render `ScannerDiagnostics` for a live diagnostics panel with engine/browser, video and track state, luminance, and the chosen capture path; **Copy report** pastes a shutter-time dump for support. Pass `theme` to `ScannerFrame` to recolor the camera. `primaryColorHex` replaces the yellowish-green accents (`#c8ff45`). `seekingColorHex` and `detectingColorHex` are the waiting box colors (both `#ef6a5b` unless set apart), `unstableColorHex` is `#ffbe40`, `readyColorHex` is `#8af25a`, and `capturedColorHex` is `#ffffff`. Omit `theme` or any field to keep that default. The [React camera guide](site/guide/react-camera-overlay.md#theme) lists what each field paints. The demo source screen chooses Default, Ocean, Violet, or Signal before the camera starts and passes that palette as `theme` on `ScannerFrame`. Capture results include the displayed quad and its bounded-analysis dimensions; use `mapDocumentCornersToFrame()` plus `cropDocument()` so the final crop matches what was on screen. The framework-free imperative APIs remain available from the root package.
219
219
 
220
220
  ## Run the demo
221
221
 
@@ -224,7 +224,7 @@ npm install
224
224
  npm run dev
225
225
  ```
226
226
 
227
- Open [http://localhost:43127](http://localhost:43127). Upload a PNG/JPEG/WebP or use a mobile camera, then detect and crop, run OCR, inspect detection suggestions, apply developer redaction rules, and export the result. There is no draw-your-own box tool.
227
+ Open [http://localhost:43127](http://localhost:43127). Upload a PNG/JPEG/WebP or use a mobile camera, then detect and crop, run OCR, inspect detection suggestions, apply developer redaction rules, and export the result. There is no draw-your-own box tool. **Driver’s license** runs the two-sided AAMVA flow and prints jurisdiction, expiration, and the rest of the fields.
228
228
 
229
229
  ```bash
230
230
  npm test
@@ -238,7 +238,7 @@ The repository produces two independent bundles from the same source. The demo i
238
238
 
239
239
  | Command | Config | Output | Purpose |
240
240
  |---|---|---|---|
241
- | `npm run build:lib` | `vite.config.ts` | `dist/` | Vite library mode: ESM bundle `dist/scanner-sdk.js` plus `.d.ts` declarations. `tesseract.js` and `onnxruntime-web` stay external. The package copies the Tesseract worker, the three LSTM cores, English traineddata, and the ONNX Runtime JSEP wasm into `dist/assets/`. Those bytes are not inside `scanner-sdk.js`. ONNX model weights are not copied into `dist/`. This is what `files`, `exports`, and npm publish use. |
241
+ | `npm run build:lib` | `vite.config.ts` | `dist/` | Vite library mode: ESM bundle `dist/scanner-sdk.js` plus `.d.ts` declarations. `tesseract.js`, `onnxruntime-web`, and `zxing-wasm` stay external. The package copies the Tesseract worker, the three LSTM cores, English traineddata, the ONNX Runtime JSEP wasm, and `zxing_reader.wasm` into `dist/assets/`. Those bytes are not inside `scanner-sdk.js`. ONNX model weights are not copied into `dist/`. This is what `files`, `exports`, and npm publish use. |
242
242
  | `npm run build:demo` | `vite.config.app.ts` | `dist-demo/` | Standard Vite application build: real `index.html`, hashed JS/CSS assets, and `public/` files. This is what static hosting serves. |
243
243
  | `npm run build` | both | `dist/` and `dist-demo/` | Convenience wrapper that runs the library build then the demo build. |
244
244
 
@@ -1 +1 @@
1
- function e(e,t,n){return(t.x-e.x)*(n.y-e.y)-(t.y-e.y)*(n.x-e.x)}function t(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n],i=e[(n+1)%e.length];t+=r.x*i.y-i.x*r.y}return Math.abs(t)/2}function n(e){if(e.length<4)return;let t=e.reduce((e,t)=>t.x+t.y<e.x+e.y?t:e),n=e.reduce((e,t)=>t.x+t.y>e.x+e.y?t:e),r=e.reduce((e,t)=>t.x-t.y>e.x-e.y?t:e),i=e.reduce((e,t)=>t.x-t.y<e.x-e.y?t:e);if((/* @__PURE__ */ new Set([t,r,n,i])).size===4)return{topLeft:t,topRight:r,bottomRight:n,bottomLeft:i}}function r(e){return Math.max(0,Math.min(1,e))}function i(e,t){return Math.hypot(e.x-t.x,e.y-t.y)}function a(e,t,n){let r=e.x-t.x,i=e.y-t.y,a=n.x-t.x,o=n.y-t.y,s=Math.max(1e-6,Math.hypot(r,i)*Math.hypot(a,o));return Math.acos(Math.max(-1,Math.min(1,(r*a+i*o)/s)))*180/Math.PI}function o(e,t,n,r){let i=Math.atan2(t.y-e.y,t.x-e.x),a=Math.atan2(r.y-n.y,r.x-n.x),o=Math.abs(i-a)%Math.PI;return o>Math.PI/2&&(o=Math.PI-o),o}function s(e){let{topLeft:t,topRight:n,bottomRight:r,bottomLeft:i}=e,a=n.x-r.x,o=i.x-r.x,s=n.y-r.y,c=i.y-r.y,l=t.x-n.x+r.x-i.x,u=t.y-n.y+r.y-i.y,d=a*c-o*s,f=Math.abs(d)<1e-6?0:(l*c-o*u)/d,p=Math.abs(d)<1e-6?0:(a*u-l*s)/d;return{a:n.x-t.x+f*n.x,b:i.x-t.x+p*i.x,c:t.x,d:n.y-t.y+f*n.y,e:i.y-t.y+p*i.y,f:t.y,g:f,h:p}}function c(e,t){let{a:n,b:r,c:i,d:a,e:o,f:s,g:c,h:l}=t,u=n-e.x*c,d=r-e.x*l,f=a-e.y*c,p=o-e.y*l,m=e.x-i,h=e.y-s,g=u*p-d*f;if(!(Math.abs(g)<1e-8))return{x:(m*p-d*h)/g,y:(u*h-m*f)/g}}function l(e,t){if(!t.length)return 0;let n=s(e),r=0,i=0,a=Math.max(1,Math.floor(t.length/80));for(let e=0;e<t.length;e+=a){let a=c(t[e],n);if(!a)return 0;let o=Math.min(Math.abs(a.x),Math.abs(1-a.x),Math.abs(a.y),Math.abs(1-a.y))+Math.max(0,-a.x,a.x-1,-a.y,a.y-1)*2;r+=o*o,i+=1}let o=Math.sqrt(r/Math.max(1,i));return Math.exp(-o/.08)}function u(n,s,c,u=[n.topLeft,n.topRight,n.bottomRight,n.bottomLeft]){let d=[n.topLeft,n.topRight,n.bottomRight,n.bottomLeft],f=Math.max(1,Math.min(s,c)),p=Math.max(0,...d.map(e=>Math.max(-e.x,-e.y,e.x-s,e.y-c)/f)),m=d.map((t,n)=>e(t,d[(n+1)%4],d[(n+2)%4])),h=m.every(e=>e>0)||m.every(e=>e<0),g=t(d)/Math.max(1,s*c),_=h?g<.04?`quad area ${g.toFixed(3)} is under 0.04`:g>.995?`quad area ${g.toFixed(3)} covers the frame`:p>.12?`corners sit outside the frame (${p.toFixed(2)})`:void 0:`corners are not convex`;if(_)return{total:0,areaRatio:g,angleScore:0,oppositeAngleScore:0,parallelScore:0,homographyScore:0,aspectBoost:0,reject:_};let v=d.map((e,t)=>a(d[(t+3)%4],e,d[(t+1)%4])),y=d.map((e,t)=>i(e,d[(t+1)%4])),b=Math.max(y[0],y[2])/Math.max(1,Math.min(y[0],y[2])),x=Math.max(y[1],y[3])/Math.max(1,Math.min(y[1],y[3]));if(v.some(e=>e<18||e>162)||b>4||x>4)return{total:0,areaRatio:g,angleScore:0,oppositeAngleScore:0,parallelScore:0,homographyScore:0,aspectBoost:0};let S=v.reduce((e,t)=>e+Math.exp(-Math.abs(t-90)/48),0)/4,C=(Math.exp(-Math.abs(v[0]+v[2]-180)/55)+Math.exp(-Math.abs(v[1]+v[3]-180)/55))/2,w=(Math.exp(-o(d[0],d[1],d[3],d[2])/.62)+Math.exp(-o(d[0],d[3],d[1],d[2])/.62))/2,T=l(n,u),E=r((g-.04)/.5)*(g>.94?r((.995-g)/.055):1),D=Math.exp(-Math.max(0,Math.max(b,x)-1.6)/1.5),O=p?Math.exp(-p/.07):1,k=Math.max(i(d[0],d[1]),i(d[3],d[2])),A=Math.max(i(d[0],d[3]),i(d[1],d[2])),j=Math.max(k,A)/Math.max(1,Math.min(k,A)),M=Math.exp(-Math.abs(j-1.58)/.18),N=Math.exp(-Math.abs(j-1.29)/.18),P=j>=2?r((j-2)/2):0,F=Math.max(M,N,P)*.08;return{total:r(E*.18+S*.2+C*.14+w*.18+T*.3+F)*(.72+D*.28)*O,areaRatio:g,angleScore:S,oppositeAngleScore:C,parallelScore:w,homographyScore:T,aspectBoost:F}}function d(e,t,n=256){let r=Math.max(1,e),i=Math.max(1,t),a=Math.min(n/r,n/i),o=r*a,s=i*a;return{inputSize:n,contentWidth:o,contentHeight:s,offsetX:(n-o)/2,offsetY:(n-s)/2,sourceWidth:r,sourceHeight:i,cropX:0,cropY:0,imageWidth:e,imageHeight:t}}function f(e,t,n,r){return{x:e.x/t*r.inputSize,y:e.y/n*r.inputSize}}function p(e,t,n,r,i=1.5){if(!r||r.contentWidth<=0||r.contentHeight<=0||!t||!n)return!0;let a=f(e,t,n,r);return a.x>=r.offsetX-i&&a.x<=r.offsetX+r.contentWidth+i&&a.y>=r.offsetY-i&&a.y<=r.offsetY+r.contentHeight+i}function m(e,t,n,r,i,a){if(!a||a.contentWidth<=0||a.contentHeight<=0||!t||!n)return{x:Math.max(0,Math.min(r-1,e.x/t*r)),y:Math.max(0,Math.min(i-1,e.y/n*i))};let o=f(e,t,n,a),s=a.cropX+(o.x-a.offsetX)/a.contentWidth*a.sourceWidth,c=a.cropY+(o.y-a.offsetY)/a.contentHeight*a.sourceHeight;return{x:Math.max(0,Math.min(r-1,s)),y:Math.max(0,Math.min(i-1,c))}}function h(e,t,n,r,i,a){let o=Math.min(t-1,Math.max(0,Math.floor(r))),s=Math.min(n-1,Math.max(0,Math.floor(i))),c=Math.min(t-1,o+1),l=Math.min(n-1,s+1),u=r-o,d=i-s,f=(s*t+o)*4+a,p=(s*t+c)*4+a,m=(l*t+o)*4+a,h=(l*t+c)*4+a;return(e[f]*(1-u)*(1-d)+e[p]*u*(1-d)+e[m]*(1-u)*d+e[h]*u*d)/255}function g(e,t,n,r=256){let i=d(t,n,r),a=r*r,o=new Float32Array(a*3),s=i.sourceWidth>i.contentWidth||i.sourceHeight>i.contentHeight;for(let c=0;c<r;c+=1){let l=(c+.5-i.offsetY)/i.contentHeight;if(!(l<0||l>1))for(let u=0;u<r;u+=1){let d=(u+.5-i.offsetX)/i.contentWidth;if(d<0||d>1)continue;let f=c*r+u;if(s){let r=i.cropX+d*i.sourceWidth-.5,s=i.cropY+l*i.sourceHeight-.5;o[f]=h(e,t,n,r,s,0),o[a+f]=h(e,t,n,r,s,1),o[a*2+f]=h(e,t,n,r,s,2);continue}let p=Math.min(t-1,Math.max(0,Math.round(i.cropX+d*i.sourceWidth-.5))),m=(Math.min(n-1,Math.max(0,Math.round(i.cropY+l*i.sourceHeight-.5)))*t+p)*4;o[f]=e[m]/255,o[a+f]=e[m+1]/255,o[a*2+f]=e[m+2]/255}}return{data:o,dimensions:[1,3,r,r],letterbox:i}}function _(e,t,n){let r=-1/0;for(let i=0;i<n;i+=1){let n=e[t+i];n>r&&(r=n)}return Number.isFinite(r)?r:0}function v(e,t,n,r,i){let a=new Uint8Array(n*r),o=new Int32Array(n*r),s=0,c,l=0;for(let u=0;u<n*r;u+=1){if(a[u]||e[t+u]<i)continue;let d=0,f=0,p=0,m=0,h=0,g=0;for(o[f++]=u,a[u]=1;d<f;){let s=o[d++],c=s%n,l=Math.floor(s/n),u=Math.max(0,e[t+s]);p+=(c+.5)*u,m+=(l+.5)*u,h+=u,g=Math.max(g,u);let _=[s-1,s+1,s-n,s+n];for(let s of _){if(s<0||s>=n*r||a[s]||e[t+s]<i)continue;let l=s%n;Math.abs(l-c)>1||(a[s]=1,o[f++]=s)}}f>s&&h>0&&(s=f,l=g,c={x:p/h,y:m/h})}return c?{point:c,peak:l}:void 0}function y(e,t,r,i,a=.3,o){if(t.length!==4||Number(t[0])!==1||Number(t[1])!==4)throw Error(`Expected document heatmaps shaped [1,4,H,W], got [${t.join(`,`)}].`);let s=Number(t[2]),c=Number(t[3]),l=c*s;if(e.length<l*4||!c||!s)throw Error(`Document heatmap tensor is incomplete.`);let d=[],f=[];for(let t=0;t<4;t+=1){let n=v(e,t*l,c,s,a);if(!n){let n=_(e,t*l,l);return{cornerConfidences:f,rejectReason:`no heatmap peak for corner ${t+1}/4 above ${a} (max ${n.toFixed(3)}) on ${r}×${i}`}}if(f.push(n.peak),!p(n.point,c,s,o))return{cornerConfidences:f,rejectReason:o?`corner ${t+1}/4 peak is in the letterbox pad on ${r}×${i} (content ${Math.round(o.contentWidth)}×${Math.round(o.contentHeight)} inside ${o.inputSize}²)`:`corner ${t+1}/4 peak is outside the frame on ${r}×${i}`};d.push(m(n.point,c,s,r,i,o))}let h=n(d);if(!h)return{cornerConfidences:f,rejectReason:`four peaks could not be ordered into corners on ${r}×${i}`};let g=u(h,r,i);if(g.total<.35)return{cornerConfidences:f,rejectReason:g.reject??`geometry score ${g.total.toFixed(2)} is under 0.35 (area ${g.areaRatio.toFixed(3)}) on ${r}×${i}`};let y=f.reduce((e,t)=>e+t,0)/4;return{candidate:{corners:h,areaRatio:g.areaRatio,confidence:Math.min(.99,y*.55+g.total*.45)},cornerConfidences:f}}var b=class extends Error{code;constructor(e,t){super(t),this.name=`ScannerError`,this.code=e}};const x=Symbol.for(`exige.web-scanner-sdk.model-downloads`);function S(){let e=globalThis;return e[x]||(e[x]={active:/* @__PURE__ */ new Map,listeners:/* @__PURE__ */ new Set,cache:/* @__PURE__ */ new Map,inflight:/* @__PURE__ */ new Map}),e[x]}S().active,S().listeners,S().cache,S().inflight;function C(e){let t=Number(e.headers.get(`content-length`));return Number.isFinite(t)&&t>0?t:null}async function w(e,t){let n=C(e);if(!e.body){let r=await e.arrayBuffer();return t?.({loaded:r.byteLength,total:n??r.byteLength}),r}let r=e.body.getReader(),i=[],a=0;for(;;){let{done:e,value:o}=await r.read();if(e)break;o?.byteLength&&(i.push(o),a+=o.byteLength,t?.({loaded:a,total:n}))}let o=new Uint8Array(a),s=0;for(let e of i)o.set(e,s),s+=e.byteLength;return a===0&&t?.({loaded:0,total:n}),o.buffer}const T=`1.30.0`,E=`https://exige-scanner-sdk.vercel.app/ort/${T}/`,D=`https://cdn.jsdelivr.net/npm/onnxruntime-web@${T}/dist/`,O=`ort.min.mjs`,k=`ort-wasm-simd-threaded.jsep.wasm`,A=`ort-wasm-simd-threaded.jsep.mjs`;function j(e,t){return`${e.endsWith(`/`)?e:`${e}/`}${t}`}function M(){return[j(E,O),j(D,O)]}function N(e=E){return{wasm:j(e,k),mjs:j(e,A)}}function P(e){return typeof e==`string`?{wasm:j(e,k),mjs:j(e,A)}:{wasm:e.wasm,mjs:e.mjs}}function F(e){return typeof e==`string`?e.trim().length>0:!!(e?.wasm.trim()&&e.mjs.trim())}function I(e){return!e.ok&&e.status!==206?!1:!(e.headers.get(`content-type`)??``).toLowerCase().includes(`text/html`)}async function L(e,t=fetch){if(!e)return!1;try{let n=await t(e,{method:`HEAD`});if(I(n))return!0;if(n.status===404||n.status===410)return!1}catch{}try{return I(await t(e,{method:`GET`,headers:{Range:`bytes=0-63`}}))}catch{return!1}}async function R(e,t=fetch){let{wasm:n,mjs:r}=P(e),[i,a]=await Promise.all([L(n,t),L(r,t)]);return i&&a}async function z(e,t=fetch){if(F(e)&&await R(e,t))return typeof e==`string`?e.trim():{wasm:e.wasm.trim(),mjs:e.mjs.trim()};let n=N(E);return await R(n,t)?n:N(D)}async function B(e){return import(e)}async function V(e=B){let t=[];for(let n of M())try{return await e(n)}catch(e){t.push(e)}throw Error(`Failed to load onnxruntime-web@${T} from the pinned CDN.`,{cause:t.at(-1)})}const H=globalThis,U=/* @__PURE__ */ new Map,W=/* @__PURE__ */ new Map,G=`model-bytes`;function K(){return new Promise((e,t)=>{let n=indexedDB.open(`exige-scanner-sdk-models`,1);n.onupgradeneeded=()=>{n.result.objectStoreNames.contains(G)||n.result.createObjectStore(G)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}async function q(e){if(typeof indexedDB>`u`)return;let t=await K();try{return await new Promise((n,r)=>{let i=t.transaction(G).objectStore(G).get(e);i.onsuccess=()=>n(i.result instanceof ArrayBuffer?i.result:void 0),i.onerror=()=>r(i.error)})}finally{t.close()}}async function J(e,t){if(typeof indexedDB>`u`)return;let n=await K();try{await new Promise((r,i)=>{let a=n.transaction(G,`readwrite`);a.objectStore(G).put(t,e),a.oncomplete=()=>r(),a.onerror=()=>i(a.error)})}finally{n.close()}}async function Y(e,t,n){let r=U.get(e);return r||(r=(async()=>{if(t)try{let t=await q(e);if(t)return t}catch{}n?.({loaded:0,total:null});let r;try{let i=await fetch(e,{cache:t?`force-cache`:`no-store`});if(!i.ok)throw new b(`model-http`,`Document model request failed with HTTP ${i.status}.`);r=await w(i,n)}catch(e){throw n?.({loaded:0,total:null,done:!0}),e}if(n?.({loaded:r.byteLength,total:r.byteLength,done:!0}),t)try{await J(e,r)}catch{}return r})(),U.set(e,r)),r}function X(e){return e?typeof e==`string`?e:`${e.wasm}|${e.mjs}`:``}async function Z(){try{return await import("./ort.min.js")}catch{return V()}}async function Q(e,t,n,r){let i=`${e}|${X(n)}`,a=W.get(i);return a||(a=(async()=>{let[i,a]=await Promise.all([Z(),Y(e,t,r)]);return i.env.wasm.wasmPaths=await z(n),{session:await i.InferenceSession.create(a,{executionProviders:[`wasm`],graphOptimizationLevel:`all`}),ort:i,provider:`wasm`}})(),W.set(i,a)),a}async function ee(e){if(!e.modelUrl)throw Error(`A DocAligner modelUrl is required.`);let t=g(new Uint8ClampedArray(e.pixels),e.width,e.height),{session:n,ort:r,provider:i}=await Q(e.modelUrl,e.cacheModel!==!1,e.ortWasmPaths,t=>{H.postMessage({id:e.id,download:{url:e.modelUrl??``,...t}})}),a=performance.now(),o=(await n.run({[n.inputNames[0]]:new r.Tensor(`float32`,t.data,t.dimensions)}))[n.outputNames[0]];if(!o)throw Error(`Document model returned no output tensor.`);let s=y(o.data,o.dims.map(Number),e.width,e.height,e.heatmapThreshold??.3,t.letterbox);return{sharpness:20,candidate:s.candidate,detector:`ml`,modelStatus:`ready`,modelProvider:i,modelError:s.candidate?void 0:s.rejectReason,inferenceMs:performance.now()-a}}async function te(e){try{let t=await ee(e);return t.candidate?t:{...t,modelStatus:`failed`,modelError:t.modelError??`ML detector returned no geometrically valid corners.`}}catch(e){if(e instanceof b&&e.code===`model-http`)throw e;return{sharpness:20,detector:`ml`,modelStatus:`failed`,modelError:e instanceof Error?e.message:`Document model failed.`}}}let $=Promise.resolve();H.onmessage=e=>{let t=e.data;$=$.then(async()=>{try{H.postMessage({id:t.id,result:await te(t)})}catch(e){H.postMessage({id:t.id,error:e instanceof Error?e.message:`Document analysis failed.`,errorCode:e instanceof b?e.code:void 0})}})};
1
+ function e(e,t,n){return(t.x-e.x)*(n.y-e.y)-(t.y-e.y)*(n.x-e.x)}function t(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n],i=e[(n+1)%e.length];t+=r.x*i.y-i.x*r.y}return Math.abs(t)/2}function n(e){if(e.length<4)return;let t=e.reduce((e,t)=>t.x+t.y<e.x+e.y?t:e),n=e.reduce((e,t)=>t.x+t.y>e.x+e.y?t:e),r=e.reduce((e,t)=>t.x-t.y>e.x-e.y?t:e),i=e.reduce((e,t)=>t.x-t.y<e.x-e.y?t:e);if((/* @__PURE__ */ new Set([t,r,n,i])).size===4)return{topLeft:t,topRight:r,bottomRight:n,bottomLeft:i}}function r(e){return Math.max(0,Math.min(1,e))}function i(e,t){return Math.hypot(e.x-t.x,e.y-t.y)}function a(e,t,n){let r=e.x-t.x,i=e.y-t.y,a=n.x-t.x,o=n.y-t.y,s=Math.max(1e-6,Math.hypot(r,i)*Math.hypot(a,o));return Math.acos(Math.max(-1,Math.min(1,(r*a+i*o)/s)))*180/Math.PI}function o(e,t,n,r){let i=Math.atan2(t.y-e.y,t.x-e.x),a=Math.atan2(r.y-n.y,r.x-n.x),o=Math.abs(i-a)%Math.PI;return o>Math.PI/2&&(o=Math.PI-o),o}function s(e){let{topLeft:t,topRight:n,bottomRight:r,bottomLeft:i}=e,a=n.x-r.x,o=i.x-r.x,s=n.y-r.y,c=i.y-r.y,l=t.x-n.x+r.x-i.x,u=t.y-n.y+r.y-i.y,d=a*c-o*s,f=Math.abs(d)<1e-6?0:(l*c-o*u)/d,p=Math.abs(d)<1e-6?0:(a*u-l*s)/d;return{a:n.x-t.x+f*n.x,b:i.x-t.x+p*i.x,c:t.x,d:n.y-t.y+f*n.y,e:i.y-t.y+p*i.y,f:t.y,g:f,h:p}}function c(e,t){let{a:n,b:r,c:i,d:a,e:o,f:s,g:c,h:l}=t,u=n-e.x*c,d=r-e.x*l,f=a-e.y*c,p=o-e.y*l,m=e.x-i,h=e.y-s,g=u*p-d*f;if(!(Math.abs(g)<1e-8))return{x:(m*p-d*h)/g,y:(u*h-m*f)/g}}function l(e,t){if(!t.length)return 0;let n=s(e),r=0,i=0,a=Math.max(1,Math.floor(t.length/80));for(let e=0;e<t.length;e+=a){let a=c(t[e],n);if(!a)return 0;let o=Math.min(Math.abs(a.x),Math.abs(1-a.x),Math.abs(a.y),Math.abs(1-a.y))+Math.max(0,-a.x,a.x-1,-a.y,a.y-1)*2;r+=o*o,i+=1}let o=Math.sqrt(r/Math.max(1,i));return Math.exp(-o/.08)}function u(n,s,c,u=[n.topLeft,n.topRight,n.bottomRight,n.bottomLeft]){let d=[n.topLeft,n.topRight,n.bottomRight,n.bottomLeft],f=Math.max(1,Math.min(s,c)),p=Math.max(0,...d.map(e=>Math.max(-e.x,-e.y,e.x-s,e.y-c)/f)),m=d.map((t,n)=>e(t,d[(n+1)%4],d[(n+2)%4])),h=m.every(e=>e>0)||m.every(e=>e<0),g=t(d)/Math.max(1,s*c),_=h?g<.04?`quad area ${g.toFixed(3)} is under 0.04`:g>.995?`quad area ${g.toFixed(3)} covers the frame`:p>.12?`corners sit outside the frame (${p.toFixed(2)})`:void 0:`corners are not convex`;if(_)return{total:0,areaRatio:g,angleScore:0,oppositeAngleScore:0,parallelScore:0,homographyScore:0,aspectBoost:0,reject:_};let v=d.map((e,t)=>a(d[(t+3)%4],e,d[(t+1)%4])),y=d.map((e,t)=>i(e,d[(t+1)%4])),b=Math.max(y[0],y[2])/Math.max(1,Math.min(y[0],y[2])),x=Math.max(y[1],y[3])/Math.max(1,Math.min(y[1],y[3]));if(v.some(e=>e<18||e>162)||b>4||x>4)return{total:0,areaRatio:g,angleScore:0,oppositeAngleScore:0,parallelScore:0,homographyScore:0,aspectBoost:0};let S=v.reduce((e,t)=>e+Math.exp(-Math.abs(t-90)/48),0)/4,C=(Math.exp(-Math.abs(v[0]+v[2]-180)/55)+Math.exp(-Math.abs(v[1]+v[3]-180)/55))/2,w=(Math.exp(-o(d[0],d[1],d[3],d[2])/.62)+Math.exp(-o(d[0],d[3],d[1],d[2])/.62))/2,T=l(n,u),E=r((g-.04)/.5)*(g>.94?r((.995-g)/.055):1),D=Math.exp(-Math.max(0,Math.max(b,x)-1.6)/1.5),O=p?Math.exp(-p/.07):1,k=Math.max(i(d[0],d[1]),i(d[3],d[2])),A=Math.max(i(d[0],d[3]),i(d[1],d[2])),j=Math.max(k,A)/Math.max(1,Math.min(k,A)),M=Math.exp(-Math.abs(j-1.58)/.18),N=Math.exp(-Math.abs(j-1.29)/.18),P=j>=2?r((j-2)/2):0,F=Math.max(M,N,P)*.08;return{total:r(E*.18+S*.2+C*.14+w*.18+T*.3+F)*(.72+D*.28)*O,areaRatio:g,angleScore:S,oppositeAngleScore:C,parallelScore:w,homographyScore:T,aspectBoost:F}}function d(e,t=!1){return e===`webgpu`&&!t}function f(e,t,n=256){let r=Math.max(1,e),i=Math.max(1,t),a=Math.min(n/r,n/i),o=r*a,s=i*a;return{inputSize:n,contentWidth:o,contentHeight:s,offsetX:(n-o)/2,offsetY:(n-s)/2,sourceWidth:r,sourceHeight:i,cropX:0,cropY:0,imageWidth:e,imageHeight:t}}function p(e,t,n,r){return{x:e.x/t*r.inputSize,y:e.y/n*r.inputSize}}function m(e,t,n,r,i=1.5){if(!r||r.contentWidth<=0||r.contentHeight<=0||!t||!n)return!0;let a=p(e,t,n,r);return a.x>=r.offsetX-i&&a.x<=r.offsetX+r.contentWidth+i&&a.y>=r.offsetY-i&&a.y<=r.offsetY+r.contentHeight+i}function h(e,t,n,r,i,a){if(!a||a.contentWidth<=0||a.contentHeight<=0||!t||!n)return{x:Math.max(0,Math.min(r-1,e.x/t*r)),y:Math.max(0,Math.min(i-1,e.y/n*i))};let o=p(e,t,n,a),s=a.cropX+(o.x-a.offsetX)/a.contentWidth*a.sourceWidth,c=a.cropY+(o.y-a.offsetY)/a.contentHeight*a.sourceHeight;return{x:Math.max(0,Math.min(r-1,s)),y:Math.max(0,Math.min(i-1,c))}}function g(e,t,n,r,i,a){let o=Math.min(t-1,Math.max(0,Math.floor(r))),s=Math.min(n-1,Math.max(0,Math.floor(i))),c=Math.min(t-1,o+1),l=Math.min(n-1,s+1),u=r-o,d=i-s,f=(s*t+o)*4+a,p=(s*t+c)*4+a,m=(l*t+o)*4+a,h=(l*t+c)*4+a;return(e[f]*(1-u)*(1-d)+e[p]*u*(1-d)+e[m]*(1-u)*d+e[h]*u*d)/255}function _(e,t,n,r=256){let i=f(t,n,r),a=r*r,o=new Float32Array(a*3),s=i.sourceWidth>i.contentWidth||i.sourceHeight>i.contentHeight;for(let c=0;c<r;c+=1){let l=(c+.5-i.offsetY)/i.contentHeight;if(!(l<0||l>1))for(let u=0;u<r;u+=1){let d=(u+.5-i.offsetX)/i.contentWidth;if(d<0||d>1)continue;let f=c*r+u;if(s){let r=i.cropX+d*i.sourceWidth-.5,s=i.cropY+l*i.sourceHeight-.5;o[f]=g(e,t,n,r,s,0),o[a+f]=g(e,t,n,r,s,1),o[a*2+f]=g(e,t,n,r,s,2);continue}let p=Math.min(t-1,Math.max(0,Math.round(i.cropX+d*i.sourceWidth-.5))),m=(Math.min(n-1,Math.max(0,Math.round(i.cropY+l*i.sourceHeight-.5)))*t+p)*4;o[f]=e[m]/255,o[a+f]=e[m+1]/255,o[a*2+f]=e[m+2]/255}}return{data:o,dimensions:[1,3,r,r],letterbox:i}}function v(e,t,n){let r=-1/0;for(let i=0;i<n;i+=1){let n=e[t+i];n>r&&(r=n)}return Number.isFinite(r)?r:0}function y(e,t,n,r,i){let a=new Uint8Array(n*r),o=new Int32Array(n*r),s=0,c,l=0;for(let u=0;u<n*r;u+=1){if(a[u]||e[t+u]<i)continue;let d=0,f=0,p=0,m=0,h=0,g=0;for(o[f++]=u,a[u]=1;d<f;){let s=o[d++],c=s%n,l=Math.floor(s/n),u=Math.max(0,e[t+s]);p+=(c+.5)*u,m+=(l+.5)*u,h+=u,g=Math.max(g,u);let _=[s-1,s+1,s-n,s+n];for(let s of _){if(s<0||s>=n*r||a[s]||e[t+s]<i)continue;let l=s%n;Math.abs(l-c)>1||(a[s]=1,o[f++]=s)}}f>s&&h>0&&(s=f,l=g,c={x:p/h,y:m/h})}return c?{point:c,peak:l}:void 0}function b(e,t,r,i,a=.3,o){if(t.length!==4||Number(t[0])!==1||Number(t[1])!==4)throw Error(`Expected document heatmaps shaped [1,4,H,W], got [${t.join(`,`)}].`);let s=Number(t[2]),c=Number(t[3]),l=c*s;if(e.length<l*4||!c||!s)throw Error(`Document heatmap tensor is incomplete.`);let d=[],f=[];for(let t=0;t<4;t+=1){let n=y(e,t*l,c,s,a);if(!n){let n=v(e,t*l,l);return{cornerConfidences:f,rejectReason:`no heatmap peak for corner ${t+1}/4 above ${a} (max ${n.toFixed(3)}) on ${r}×${i}`}}if(f.push(n.peak),!m(n.point,c,s,o))return{cornerConfidences:f,rejectReason:o?`corner ${t+1}/4 peak is in the letterbox pad on ${r}×${i} (content ${Math.round(o.contentWidth)}×${Math.round(o.contentHeight)} inside ${o.inputSize}²)`:`corner ${t+1}/4 peak is outside the frame on ${r}×${i}`};d.push(h(n.point,c,s,r,i,o))}let p=n(d);if(!p)return{cornerConfidences:f,rejectReason:`four peaks could not be ordered into corners on ${r}×${i}`};let g=u(p,r,i);if(g.total<.35)return{cornerConfidences:f,rejectReason:g.reject??`geometry score ${g.total.toFixed(2)} is under 0.35 (area ${g.areaRatio.toFixed(3)}) on ${r}×${i}`};let _=f.reduce((e,t)=>e+t,0)/4;return{candidate:{corners:p,areaRatio:g.areaRatio,confidence:Math.min(.99,_*.55+g.total*.45)},cornerConfidences:f}}var x=class extends Error{code;constructor(e,t){super(t),this.name=`ScannerError`,this.code=e}};const S=Symbol.for(`exige.web-scanner-sdk.model-downloads`);function C(){let e=globalThis;return e[S]||(e[S]={active:/* @__PURE__ */ new Map,listeners:/* @__PURE__ */ new Set,cache:/* @__PURE__ */ new Map,inflight:/* @__PURE__ */ new Map}),e[S]}C().active,C().listeners,C().cache,C().inflight;function w(e){let t=Number(e.headers.get(`content-length`));return Number.isFinite(t)&&t>0?t:null}async function T(e,t){let n=w(e);if(!e.body){let r=await e.arrayBuffer();return t?.({loaded:r.byteLength,total:n??r.byteLength}),r}let r=e.body.getReader(),i=[],a=0;for(;;){let{done:e,value:o}=await r.read();if(e)break;o?.byteLength&&(i.push(o),a+=o.byteLength,t?.({loaded:a,total:n}))}let o=new Uint8Array(a),s=0;for(let e of i)o.set(e,s),s+=e.byteLength;return a===0&&t?.({loaded:0,total:n}),o.buffer}const E=`1.30.0`,D=`https://exige-scanner-sdk.vercel.app/ort/${E}/`,O=`https://cdn.jsdelivr.net/npm/onnxruntime-web@${E}/dist/`,k=`ort.min.mjs`,A=`ort-wasm-simd-threaded.jsep.wasm`,j=`ort-wasm-simd-threaded.jsep.mjs`;function M(e,t){return`${e.endsWith(`/`)?e:`${e}/`}${t}`}function N(){return[M(D,k),M(O,k)]}function P(e=D){return{wasm:M(e,A),mjs:M(e,j)}}function F(e){return typeof e==`string`?{wasm:M(e,A),mjs:M(e,j)}:{wasm:e.wasm,mjs:e.mjs}}function I(e){return typeof e==`string`?e.trim().length>0:!!(e?.wasm.trim()&&e.mjs.trim())}function L(e){return!e.ok&&e.status!==206?!1:!(e.headers.get(`content-type`)??``).toLowerCase().includes(`text/html`)}async function R(e,t=fetch){if(!e)return!1;try{let n=await t(e,{method:`HEAD`});if(L(n))return!0;if(n.status===404||n.status===410)return!1}catch{}try{return L(await t(e,{method:`GET`,headers:{Range:`bytes=0-63`}}))}catch{return!1}}async function z(e,t=fetch){let{wasm:n,mjs:r}=F(e),[i,a]=await Promise.all([R(n,t),R(r,t)]);return i&&a}async function B(e,t=fetch){if(I(e)&&await z(e,t))return typeof e==`string`?e.trim():{wasm:e.wasm.trim(),mjs:e.mjs.trim()};let n=P(D);return await z(n,t)?n:P(O)}async function V(e){return import(e)}async function H(e=V){let t=[];for(let n of N())try{return await e(n)}catch(e){t.push(e)}throw Error(`Failed to load onnxruntime-web@${E} from the pinned CDN.`,{cause:t.at(-1)})}const U=globalThis,W=/* @__PURE__ */ new Map,G=/* @__PURE__ */ new Map,K=/* @__PURE__ */ new Set,q=`model-bytes`;function J(){return new Promise((e,t)=>{let n=indexedDB.open(`exige-scanner-sdk-models`,1);n.onupgradeneeded=()=>{n.result.objectStoreNames.contains(q)||n.result.createObjectStore(q)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}async function Y(e){if(typeof indexedDB>`u`)return;let t=await J();try{return await new Promise((n,r)=>{let i=t.transaction(q).objectStore(q).get(e);i.onsuccess=()=>n(i.result instanceof ArrayBuffer?i.result:void 0),i.onerror=()=>r(i.error)})}finally{t.close()}}async function X(e,t){if(typeof indexedDB>`u`)return;let n=await J();try{await new Promise((r,i)=>{let a=n.transaction(q,`readwrite`);a.objectStore(q).put(t,e),a.oncomplete=()=>r(),a.onerror=()=>i(a.error)})}finally{n.close()}}async function ee(e,t,n){let r=W.get(e);return r||(r=(async()=>{if(t)try{let t=await Y(e);if(t)return t}catch{}n?.({loaded:0,total:null});let r;try{let i=await fetch(e,{cache:t?`force-cache`:`no-store`});if(!i.ok)throw new x(`model-http`,`Document model request failed with HTTP ${i.status}.`);r=await T(i,n)}catch(e){throw n?.({loaded:0,total:null,done:!0}),e}if(n?.({loaded:r.byteLength,total:r.byteLength,done:!0}),t)try{await X(e,r)}catch{}return r})(),W.set(e,r)),r}function te(e){return e?typeof e==`string`?e:`${e.wasm}|${e.mjs}`:``}async function ne(){try{return await import("./ort.min.js")}catch{return H()}}function Z(e,t){return`${e}|${te(t)}`}async function re(e){let t=G.get(e);if(G.delete(e),t)try{await(await t).session.release?.()}catch{}}async function ie(e,t,n,r,i=!1,a=!1){let o=Z(e,n),s=i||K.has(o)||!a,c=s?`${o}|wasm`:o,l=G.get(c);return l||(l=(async()=>{let[i,o]=await Promise.all([ne(),ee(e,t,r)]);i.env.wasm.wasmPaths=await B(n);let c=typeof navigator<`u`&&`gpu`in navigator,l;if(!s&&c)try{return{session:await i.InferenceSession.create(o,{executionProviders:[`webgpu`],graphOptimizationLevel:`all`}),ort:i,provider:`webgpu`}}catch(e){l=`WebGPU session failed (${e instanceof Error?e.message:`WebGPU session failed`}); using WASM`}else a&&!c&&(l=`WebGPU is not exposed; using WASM`);return{session:await i.InferenceSession.create(o,{executionProviders:[`wasm`],graphOptimizationLevel:`all`}),ort:i,provider:`wasm`,providerNote:l}})(),G.set(c,l)),l}async function Q(e,t=!1){if(!e.modelUrl)throw Error(`A DocAligner modelUrl is required.`);let n=_(new Uint8ClampedArray(e.pixels),e.width,e.height),{session:r,ort:i,provider:a,providerNote:o}=await ie(e.modelUrl,e.cacheModel!==!1,e.ortWasmPaths,t=>{U.postMessage({id:e.id,download:{url:e.modelUrl??``,...t}})},t,e.preferWebGpu===!0),s=performance.now(),c=(await r.run({[r.inputNames[0]]:new i.Tensor(`float32`,n.data,n.dimensions)}))[r.outputNames[0]];if(!c)throw Error(`Document model returned no output tensor.`);let l=b(c.data,c.dims.map(Number),e.width,e.height,e.heatmapThreshold??.3,n.letterbox);return{sharpness:20,candidate:l.candidate,detector:`ml`,modelStatus:`ready`,modelProvider:a,modelError:l.candidate?void 0:l.rejectReason,modelNote:o,inferenceMs:performance.now()-s}}async function ae(e){try{let t=await Q(e);if(e.modelUrl&&d(t.modelProvider,!!t.candidate)){let n=Z(e.modelUrl,e.ortWasmPaths);K.add(n),await re(n);let r=await Q(e,!0),i=`WebGPU ran but ${t.modelError??`returned no corners`}; using WASM`;return r.candidate?{...r,modelNote:i}:{...r,modelStatus:`failed`,modelError:r.modelError??`ML detector returned no geometrically valid corners.`,modelNote:i}}return t.candidate?t:{...t,modelStatus:`failed`,modelError:t.modelError??`ML detector returned no geometrically valid corners.`}}catch(e){if(e instanceof x&&e.code===`model-http`)throw e;return{sharpness:20,detector:`ml`,modelStatus:`failed`,modelError:e instanceof Error?e.message:`Document model failed.`}}}let $=Promise.resolve();U.onmessage=e=>{let t=e.data;$=$.then(async()=>{try{U.postMessage({id:t.id,result:await ae(t)})}catch(e){U.postMessage({id:t.id,error:e instanceof Error?e.message:`Document analysis failed.`,errorCode:e instanceof x?e.code:void 0})}})};
@@ -1 +1 @@
1
- {"version":3,"file":"document-worker.js","names":[],"sources":["../../src/sdk/document-geometry.ts","../../src/sdk/document-ml.ts","../../src/sdk/errors.ts","../../src/sdk/model-download.ts","../../src/sdk/ort-cdn.ts","../../src/sdk/document-worker.ts"],"sourcesContent":["import type { DocumentCorners, Point } from './types'\n\nexport interface QuadCandidate {\n corners: DocumentCorners\n confidence: number\n areaRatio: number\n}\n\nexport interface PerspectiveQuadScore {\n total: number\n areaRatio: number\n angleScore: number\n oppositeAngleScore: number\n parallelScore: number\n homographyScore: number\n aspectBoost: number\n /** Set when the quad is rejected before a perspective score is computed. */\n reject?: string\n}\n\nfunction cross(origin: Point, a: Point, b: Point) {\n return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x)\n}\n\nexport function polygonArea(points: Point[]) {\n let sum = 0\n for (let index = 0; index < points.length; index += 1) {\n const current = points[index]\n const next = points[(index + 1) % points.length]\n sum += current.x * next.y - next.x * current.y\n }\n return Math.abs(sum) / 2\n}\n\nexport function orderCorners(points: Point[]): DocumentCorners | undefined {\n if (points.length < 4) return undefined\n const topLeft = points.reduce((best, point) => point.x + point.y < best.x + best.y ? point : best)\n const bottomRight = points.reduce((best, point) => point.x + point.y > best.x + best.y ? point : best)\n const topRight = points.reduce((best, point) => point.x - point.y > best.x - best.y ? point : best)\n const bottomLeft = points.reduce((best, point) => point.x - point.y < best.x - best.y ? point : best)\n const unique = new Set([topLeft, topRight, bottomRight, bottomLeft])\n if (unique.size !== 4) return undefined\n return { topLeft, topRight, bottomRight, bottomLeft }\n}\n\nfunction clamp01(value: number) {\n return Math.max(0, Math.min(1, value))\n}\n\nfunction distance(a: Point, b: Point) {\n return Math.hypot(a.x - b.x, a.y - b.y)\n}\n\nfunction interiorAngle(previous: Point, vertex: Point, next: Point) {\n const ax = previous.x - vertex.x\n const ay = previous.y - vertex.y\n const bx = next.x - vertex.x\n const by = next.y - vertex.y\n const denominator = Math.max(1e-6, Math.hypot(ax, ay) * Math.hypot(bx, by))\n return Math.acos(Math.max(-1, Math.min(1, (ax * bx + ay * by) / denominator))) * 180 / Math.PI\n}\n\nfunction directionDifference(a: Point, b: Point, c: Point, d: Point) {\n const first = Math.atan2(b.y - a.y, b.x - a.x)\n const second = Math.atan2(d.y - c.y, d.x - c.x)\n let difference = Math.abs(first - second) % Math.PI\n if (difference > Math.PI / 2) difference = Math.PI - difference\n return difference\n}\n\nfunction unitToQuadCoefficients(corners: DocumentCorners) {\n const { topLeft: tl, topRight: tr, bottomRight: br, bottomLeft: bl } = corners\n const dx1 = tr.x - br.x\n const dx2 = bl.x - br.x\n const dy1 = tr.y - br.y\n const dy2 = bl.y - br.y\n const sx = tl.x - tr.x + br.x - bl.x\n const sy = tl.y - tr.y + br.y - bl.y\n const denominator = dx1 * dy2 - dx2 * dy1\n const g = Math.abs(denominator) < 1e-6 ? 0 : (sx * dy2 - dx2 * sy) / denominator\n const h = Math.abs(denominator) < 1e-6 ? 0 : (dx1 * sy - sx * dy1) / denominator\n return {\n a: tr.x - tl.x + g * tr.x,\n b: bl.x - tl.x + h * bl.x,\n c: tl.x,\n d: tr.y - tl.y + g * tr.y,\n e: bl.y - tl.y + h * bl.y,\n f: tl.y,\n g,\n h,\n }\n}\n\nfunction quadToUnit(point: Point, coefficients: ReturnType<typeof unitToQuadCoefficients>) {\n const { a, b, c, d, e, f, g, h } = coefficients\n const aa = a - point.x * g\n const bb = b - point.x * h\n const dd = d - point.y * g\n const ee = e - point.y * h\n const xx = point.x - c\n const yy = point.y - f\n const determinant = aa * ee - bb * dd\n if (Math.abs(determinant) < 1e-8) return undefined\n return {\n x: (xx * ee - bb * yy) / determinant,\n y: (aa * yy - xx * dd) / determinant,\n }\n}\n\nfunction homographyBoundaryScore(corners: DocumentCorners, hull: Point[]) {\n if (!hull.length) return 0\n const coefficients = unitToQuadCoefficients(corners)\n let squaredResidual = 0\n let samples = 0\n const stride = Math.max(1, Math.floor(hull.length / 80))\n for (let index = 0; index < hull.length; index += stride) {\n const unit = quadToUnit(hull[index], coefficients)\n if (!unit) return 0\n const boundaryDistance = Math.min(\n Math.abs(unit.x),\n Math.abs(1 - unit.x),\n Math.abs(unit.y),\n Math.abs(1 - unit.y),\n )\n const outside = Math.max(0, -unit.x, unit.x - 1, -unit.y, unit.y - 1)\n const residual = boundaryDistance + outside * 2\n squaredResidual += residual * residual\n samples += 1\n }\n const rms = Math.sqrt(squaredResidual / Math.max(1, samples))\n return Math.exp(-rms / 0.08)\n}\n\nexport function scorePerspectiveQuad(\n corners: DocumentCorners,\n frameWidth: number,\n frameHeight: number,\n hull: Point[] = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft],\n): PerspectiveQuadScore {\n const ordered = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft]\n const frameScale = Math.max(1, Math.min(frameWidth, frameHeight))\n const outsideRatio = Math.max(\n 0,\n ...ordered.map((point) => Math.max(\n -point.x,\n -point.y,\n point.x - frameWidth,\n point.y - frameHeight,\n ) / frameScale),\n )\n const signedCrosses = ordered.map((point, index) =>\n cross(point, ordered[(index + 1) % 4], ordered[(index + 2) % 4]),\n )\n const convex = signedCrosses.every((value) => value > 0) ||\n signedCrosses.every((value) => value < 0)\n const areaRatio = polygonArea(ordered) / Math.max(1, frameWidth * frameHeight)\n const reject = !convex\n ? 'corners are not convex'\n : areaRatio < 0.04\n ? `quad area ${areaRatio.toFixed(3)} is under 0.04`\n : areaRatio > 0.995\n ? `quad area ${areaRatio.toFixed(3)} covers the frame`\n : outsideRatio > 0.12\n ? `corners sit outside the frame (${outsideRatio.toFixed(2)})`\n : undefined\n if (reject) {\n return {\n total: 0,\n areaRatio,\n angleScore: 0,\n oppositeAngleScore: 0,\n parallelScore: 0,\n homographyScore: 0,\n aspectBoost: 0,\n reject,\n }\n }\n\n const angles = ordered.map((point, index) =>\n interiorAngle(ordered[(index + 3) % 4], point, ordered[(index + 1) % 4]),\n )\n const edgeLengths = ordered.map((point, index) => distance(point, ordered[(index + 1) % 4]))\n const horizontalScaleRatio = Math.max(edgeLengths[0], edgeLengths[2]) /\n Math.max(1, Math.min(edgeLengths[0], edgeLengths[2]))\n const verticalScaleRatio = Math.max(edgeLengths[1], edgeLengths[3]) /\n Math.max(1, Math.min(edgeLengths[1], edgeLengths[3]))\n if (\n angles.some((angle) => angle < 18 || angle > 162) ||\n horizontalScaleRatio > 4 ||\n verticalScaleRatio > 4\n ) {\n return {\n total: 0,\n areaRatio,\n angleScore: 0,\n oppositeAngleScore: 0,\n parallelScore: 0,\n homographyScore: 0,\n aspectBoost: 0,\n }\n }\n const angleScore = angles.reduce(\n (sum, angle) => sum + Math.exp(-Math.abs(angle - 90) / 48),\n 0,\n ) / 4\n const oppositeAngleScore = (\n Math.exp(-Math.abs(angles[0] + angles[2] - 180) / 55) +\n Math.exp(-Math.abs(angles[1] + angles[3] - 180) / 55)\n ) / 2\n const parallelScore = (\n Math.exp(-directionDifference(ordered[0], ordered[1], ordered[3], ordered[2]) / 0.62) +\n Math.exp(-directionDifference(ordered[0], ordered[3], ordered[1], ordered[2]) / 0.62)\n ) / 2\n const homographyScore = homographyBoundaryScore(corners, hull)\n const areaScore = clamp01((areaRatio - 0.04) / 0.5) *\n (areaRatio > 0.94 ? clamp01((0.995 - areaRatio) / 0.055) : 1)\n const perspectiveScaleScore = Math.exp(\n -Math.max(0, Math.max(horizontalScaleRatio, verticalScaleRatio) - 1.6) / 1.5,\n )\n const frameVisibilityScore = outsideRatio ? Math.exp(-outsideRatio / 0.07) : 1\n const width = Math.max(distance(ordered[0], ordered[1]), distance(ordered[3], ordered[2]))\n const height = Math.max(distance(ordered[0], ordered[3]), distance(ordered[1], ordered[2]))\n const aspect = Math.max(width, height) / Math.max(1, Math.min(width, height))\n const idBoost = Math.exp(-Math.abs(aspect - 1.58) / 0.18)\n const letterBoost = Math.exp(-Math.abs(aspect - 1.29) / 0.18)\n const receiptBoost = aspect >= 2 ? clamp01((aspect - 2) / 2) : 0\n const aspectBoost = Math.max(idBoost, letterBoost, receiptBoost) * 0.08\n const geometricScore = clamp01(\n areaScore * 0.18 +\n angleScore * 0.2 +\n oppositeAngleScore * 0.14 +\n parallelScore * 0.18 +\n homographyScore * 0.3 +\n aspectBoost\n )\n const total = geometricScore * (0.72 + perspectiveScaleScore * 0.28) * frameVisibilityScore\n return {\n total,\n areaRatio,\n angleScore,\n oppositeAngleScore,\n parallelScore,\n homographyScore,\n aspectBoost,\n }\n}\n","import { orderCorners, scorePerspectiveQuad, type QuadCandidate } from './document-geometry'\nimport type { Point } from './types'\n\nexport interface DocumentLetterbox {\n inputSize: number\n contentWidth: number\n contentHeight: number\n offsetX: number\n offsetY: number\n sourceWidth: number\n sourceHeight: number\n /** Always 0: the full analysis bitmap is letterboxed, never center-cropped. */\n cropX: number\n /** Always 0: the full analysis bitmap is letterboxed, never center-cropped. */\n cropY: number\n imageWidth: number\n imageHeight: number\n}\n\nexport interface DocumentModelInput {\n data: Float32Array\n dimensions: [1, 3, number, number]\n letterbox: DocumentLetterbox\n}\n\nexport interface DecodedDocumentHeatmaps {\n candidate?: QuadCandidate\n cornerConfidences: number[]\n /** Why four corners were not returned. Absent when `candidate` is set. */\n rejectReason?: string\n}\n\nexport function documentModelLetterbox(\n width: number,\n height: number,\n inputSize = 256,\n): DocumentLetterbox {\n const sourceWidth = Math.max(1, width)\n const sourceHeight = Math.max(1, height)\n const scale = Math.min(inputSize / sourceWidth, inputSize / sourceHeight)\n const contentWidth = sourceWidth * scale\n const contentHeight = sourceHeight * scale\n return {\n inputSize,\n contentWidth,\n contentHeight,\n offsetX: (inputSize - contentWidth) / 2,\n offsetY: (inputSize - contentHeight) / 2,\n sourceWidth,\n sourceHeight,\n cropX: 0,\n cropY: 0,\n imageWidth: width,\n imageHeight: height,\n }\n}\n\nfunction letterboxInputPoint(\n point: Point,\n heatmapWidth: number,\n heatmapHeight: number,\n letterbox: DocumentLetterbox,\n) {\n return {\n x: point.x / heatmapWidth * letterbox.inputSize,\n y: point.y / heatmapHeight * letterbox.inputSize,\n }\n}\n\nexport function heatmapPointInLetterbox(\n point: Point,\n heatmapWidth: number,\n heatmapHeight: number,\n letterbox?: DocumentLetterbox,\n margin = 1.5,\n) {\n if (\n !letterbox ||\n letterbox.contentWidth <= 0 ||\n letterbox.contentHeight <= 0 ||\n !heatmapWidth ||\n !heatmapHeight\n ) {\n return true\n }\n const input = letterboxInputPoint(point, heatmapWidth, heatmapHeight, letterbox)\n return input.x >= letterbox.offsetX - margin &&\n input.x <= letterbox.offsetX + letterbox.contentWidth + margin &&\n input.y >= letterbox.offsetY - margin &&\n input.y <= letterbox.offsetY + letterbox.contentHeight + margin\n}\n\nexport function mapHeatmapPointToImage(\n point: Point,\n heatmapWidth: number,\n heatmapHeight: number,\n imageWidth: number,\n imageHeight: number,\n letterbox?: DocumentLetterbox,\n): Point {\n if (\n !letterbox ||\n letterbox.contentWidth <= 0 ||\n letterbox.contentHeight <= 0 ||\n !heatmapWidth ||\n !heatmapHeight\n ) {\n return {\n x: Math.max(0, Math.min(imageWidth - 1, point.x / heatmapWidth * imageWidth)),\n y: Math.max(0, Math.min(imageHeight - 1, point.y / heatmapHeight * imageHeight)),\n }\n }\n const input = letterboxInputPoint(point, heatmapWidth, heatmapHeight, letterbox)\n const mappedX = letterbox.cropX +\n (input.x - letterbox.offsetX) / letterbox.contentWidth * letterbox.sourceWidth\n const mappedY = letterbox.cropY +\n (input.y - letterbox.offsetY) / letterbox.contentHeight * letterbox.sourceHeight\n return {\n x: Math.max(0, Math.min(imageWidth - 1, mappedX)),\n y: Math.max(0, Math.min(imageHeight - 1, mappedY)),\n }\n}\n\nfunction sampleBilinearChannel(\n pixels: Uint8ClampedArray,\n width: number,\n height: number,\n sourceX: number,\n sourceY: number,\n channel: number,\n) {\n const x0 = Math.min(width - 1, Math.max(0, Math.floor(sourceX)))\n const y0 = Math.min(height - 1, Math.max(0, Math.floor(sourceY)))\n const x1 = Math.min(width - 1, x0 + 1)\n const y1 = Math.min(height - 1, y0 + 1)\n const tx = sourceX - x0\n const ty = sourceY - y0\n const i00 = (y0 * width + x0) * 4 + channel\n const i10 = (y0 * width + x1) * 4 + channel\n const i01 = (y1 * width + x0) * 4 + channel\n const i11 = (y1 * width + x1) * 4 + channel\n return (\n pixels[i00] * (1 - tx) * (1 - ty) +\n pixels[i10] * tx * (1 - ty) +\n pixels[i01] * (1 - tx) * ty +\n pixels[i11] * tx * ty\n ) / 255\n}\n\nexport function preprocessDocumentModel(\n pixels: Uint8ClampedArray,\n width: number,\n height: number,\n inputSize = 256,\n): DocumentModelInput {\n const letterbox = documentModelLetterbox(width, height, inputSize)\n const planeSize = inputSize * inputSize\n const data = new Float32Array(planeSize * 3)\n const downsample = letterbox.sourceWidth > letterbox.contentWidth ||\n letterbox.sourceHeight > letterbox.contentHeight\n for (let y = 0; y < inputSize; y += 1) {\n const normalizedY = (y + 0.5 - letterbox.offsetY) / letterbox.contentHeight\n if (normalizedY < 0 || normalizedY > 1) continue\n for (let x = 0; x < inputSize; x += 1) {\n const normalizedX = (x + 0.5 - letterbox.offsetX) / letterbox.contentWidth\n if (normalizedX < 0 || normalizedX > 1) continue\n const targetOffset = y * inputSize + x\n if (downsample) {\n const sourceX = letterbox.cropX + normalizedX * letterbox.sourceWidth - 0.5\n const sourceY = letterbox.cropY + normalizedY * letterbox.sourceHeight - 0.5\n data[targetOffset] = sampleBilinearChannel(pixels, width, height, sourceX, sourceY, 0)\n data[planeSize + targetOffset] = sampleBilinearChannel(pixels, width, height, sourceX, sourceY, 1)\n data[planeSize * 2 + targetOffset] = sampleBilinearChannel(pixels, width, height, sourceX, sourceY, 2)\n continue\n }\n const sourceX = Math.min(width - 1, Math.max(0, Math.round(\n letterbox.cropX + normalizedX * letterbox.sourceWidth - 0.5,\n )))\n const sourceY = Math.min(height - 1, Math.max(0, Math.round(\n letterbox.cropY + normalizedY * letterbox.sourceHeight - 0.5,\n )))\n const sourceOffset = (sourceY * width + sourceX) * 4\n data[targetOffset] = pixels[sourceOffset] / 255\n data[planeSize + targetOffset] = pixels[sourceOffset + 1] / 255\n data[planeSize * 2 + targetOffset] = pixels[sourceOffset + 2] / 255\n }\n }\n return { data, dimensions: [1, 3, inputSize, inputSize], letterbox }\n}\n\nfunction heatmapChannelMax(values: Float32Array, base: number, count: number) {\n let max = Number.NEGATIVE_INFINITY\n for (let index = 0; index < count; index += 1) {\n const value = values[base + index]\n if (value > max) max = value\n }\n return Number.isFinite(max) ? max : 0\n}\n\nfunction largestHeatmapComponent(\n values: Float32Array,\n base: number,\n width: number,\n height: number,\n threshold: number,\n) {\n const visited = new Uint8Array(width * height)\n const queue = new Int32Array(width * height)\n let bestCount = 0\n let bestPoint: Point | undefined\n let bestPeak = 0\n for (let seed = 0; seed < width * height; seed += 1) {\n if (visited[seed] || values[base + seed] < threshold) continue\n let read = 0\n let write = 0\n let weightedX = 0\n let weightedY = 0\n let totalWeight = 0\n let peak = 0\n queue[write++] = seed\n visited[seed] = 1\n while (read < write) {\n const index = queue[read++]\n const x = index % width\n const y = Math.floor(index / width)\n const weight = Math.max(0, values[base + index])\n weightedX += (x + 0.5) * weight\n weightedY += (y + 0.5) * weight\n totalWeight += weight\n peak = Math.max(peak, weight)\n const neighbors = [index - 1, index + 1, index - width, index + width]\n for (const neighbor of neighbors) {\n if (\n neighbor < 0 ||\n neighbor >= width * height ||\n visited[neighbor] ||\n values[base + neighbor] < threshold\n ) continue\n const neighborX = neighbor % width\n if (Math.abs(neighborX - x) > 1) continue\n visited[neighbor] = 1\n queue[write++] = neighbor\n }\n }\n if (write > bestCount && totalWeight > 0) {\n bestCount = write\n bestPeak = peak\n bestPoint = {\n x: weightedX / totalWeight,\n y: weightedY / totalWeight,\n }\n }\n }\n return bestPoint ? { point: bestPoint, peak: bestPeak } : undefined\n}\n\nexport function decodeDocumentHeatmaps(\n heatmaps: Float32Array,\n dimensions: readonly number[],\n imageWidth: number,\n imageHeight: number,\n threshold = 0.3,\n letterbox?: DocumentLetterbox,\n): DecodedDocumentHeatmaps {\n if (\n dimensions.length !== 4 ||\n Number(dimensions[0]) !== 1 ||\n Number(dimensions[1]) !== 4\n ) {\n throw new Error(`Expected document heatmaps shaped [1,4,H,W], got [${dimensions.join(',')}].`)\n }\n const heatmapHeight = Number(dimensions[2])\n const heatmapWidth = Number(dimensions[3])\n const planeSize = heatmapWidth * heatmapHeight\n if (heatmaps.length < planeSize * 4 || !heatmapWidth || !heatmapHeight) {\n throw new Error('Document heatmap tensor is incomplete.')\n }\n const points: Point[] = []\n const cornerConfidences: number[] = []\n for (let channel = 0; channel < 4; channel += 1) {\n const component = largestHeatmapComponent(\n heatmaps,\n channel * planeSize,\n heatmapWidth,\n heatmapHeight,\n threshold,\n )\n if (!component) {\n const max = heatmapChannelMax(heatmaps, channel * planeSize, planeSize)\n return {\n cornerConfidences,\n rejectReason: `no heatmap peak for corner ${channel + 1}/4 above ${threshold} (max ${max.toFixed(3)}) on ${imageWidth}×${imageHeight}`,\n }\n }\n cornerConfidences.push(component.peak)\n if (!heatmapPointInLetterbox(component.point, heatmapWidth, heatmapHeight, letterbox)) {\n return {\n cornerConfidences,\n rejectReason: letterbox\n ? `corner ${channel + 1}/4 peak is in the letterbox pad on ${imageWidth}×${imageHeight} (content ${Math.round(letterbox.contentWidth)}×${Math.round(letterbox.contentHeight)} inside ${letterbox.inputSize}²)`\n : `corner ${channel + 1}/4 peak is outside the frame on ${imageWidth}×${imageHeight}`,\n }\n }\n points.push(mapHeatmapPointToImage(\n component.point,\n heatmapWidth,\n heatmapHeight,\n imageWidth,\n imageHeight,\n letterbox,\n ))\n }\n const corners = orderCorners(points)\n if (!corners) {\n return {\n cornerConfidences,\n rejectReason: `four peaks could not be ordered into corners on ${imageWidth}×${imageHeight}`,\n }\n }\n const geometry = scorePerspectiveQuad(corners, imageWidth, imageHeight)\n if (geometry.total < 0.35) {\n return {\n cornerConfidences,\n rejectReason: geometry.reject ??\n `geometry score ${geometry.total.toFixed(2)} is under 0.35 (area ${geometry.areaRatio.toFixed(3)}) on ${imageWidth}×${imageHeight}`,\n }\n }\n const heatmapConfidence = cornerConfidences.reduce((sum, value) => sum + value, 0) / 4\n return {\n candidate: {\n corners,\n areaRatio: geometry.areaRatio,\n confidence: Math.min(0.99, heatmapConfidence * 0.55 + geometry.total * 0.45),\n },\n cornerConfidences,\n }\n}\n","/**\n * Operational failure from the scanner runtime.\n *\n * Scan quality stays a returned status (`success`, `no-document`,\n * `low-confidence`, or `low-quality`). Redaction match quality stays\n * `success`, `partial`, or `unmatched`. Neither of those outcomes uses this error.\n * A bad image does not throw.\n */\nexport type ScannerErrorCode =\n | 'secure-context'\n | 'worker'\n | 'model-http'\n | 'language'\n | 'canvas'\n\nexport class ScannerError extends Error {\n readonly code: ScannerErrorCode\n\n constructor(code: ScannerErrorCode, message: string) {\n super(message)\n this.name = 'ScannerError'\n this.code = code\n }\n}\n\nexport function canvasUnavailable(): ScannerError {\n return new ScannerError('canvas', 'Canvas 2D is unavailable in this browser.')\n}\n","import { ScannerError } from './errors'\n\nexport interface ModelDownloadProgress {\n loaded: number\n /** Byte length from Content-Length. Null when that header is absent. */\n total: number | null\n done?: boolean\n}\n\nexport interface ModelDownloadSnapshot {\n downloading: boolean\n loaded: number\n /** Sum of Content-Length values when every active transfer has one. */\n total: number | null\n}\n\nconst STORE = Symbol.for('exige.web-scanner-sdk.model-downloads')\n\ninterface ModelDownloadStore {\n active: Map<string, { loaded: number; total: number | null }>\n listeners: Set<(snapshot: ModelDownloadSnapshot) => void>\n cache: Map<string, ArrayBuffer>\n inflight: Map<string, Promise<ArrayBuffer>>\n}\n\n/** One store across the package entry and the React entry. */\nfunction store(): ModelDownloadStore {\n const scope = globalThis as typeof globalThis & { [STORE]?: ModelDownloadStore }\n if (!scope[STORE]) {\n scope[STORE] = {\n active: new Map(),\n listeners: new Set(),\n cache: new Map(),\n inflight: new Map(),\n }\n }\n return scope[STORE]\n}\n\nconst active = store().active\nconst listeners = store().listeners\nconst cache = store().cache\nconst inflight = store().inflight\n\nfunction contentLength(response: Response) {\n const parsed = Number(response.headers.get('content-length'))\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null\n}\n\nexport function modelDownloadSnapshot(): ModelDownloadSnapshot {\n if (active.size === 0) return { downloading: false, loaded: 0, total: null }\n let loaded = 0\n let total = 0\n let known = true\n for (const item of active.values()) {\n loaded += item.loaded\n if (item.total == null) known = false\n else total += item.total\n }\n return { downloading: true, loaded, total: known ? total : null }\n}\n\nfunction emit() {\n const snapshot = modelDownloadSnapshot()\n for (const listener of listeners) listener(snapshot)\n}\n\n/** Subscribing does not fetch. The document worker and `downloadModelBytes` report into the same snapshot. */\nexport function subscribeModelDownloads(listener: (snapshot: ModelDownloadSnapshot) => void) {\n listener(modelDownloadSnapshot())\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\n/** Progress reported from another realm, such as the document worker. */\nexport function reportModelDownload(url: string, progress: ModelDownloadProgress) {\n if (progress.done) active.delete(url)\n else active.set(url, { loaded: progress.loaded, total: progress.total })\n emit()\n}\n\nexport async function readResponseBytes(\n response: Response,\n onProgress?: (progress: ModelDownloadProgress) => void,\n) {\n const total = contentLength(response)\n if (!response.body) {\n const bytes = await response.arrayBuffer()\n onProgress?.({ loaded: bytes.byteLength, total: total ?? bytes.byteLength })\n return bytes\n }\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let loaded = 0\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!value?.byteLength) continue\n chunks.push(value)\n loaded += value.byteLength\n onProgress?.({ loaded, total })\n }\n const bytes = new Uint8Array(loaded)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n if (loaded === 0) onProgress?.({ loaded: 0, total })\n return bytes.buffer\n}\n\n/**\n * Fetch a model file and report loaded/total while the body transfers.\n * A second call for the same URL reuses the cached bytes and does not fetch.\n */\nexport async function downloadModelBytes(url: string, init?: { cache?: RequestCache }) {\n const cached = cache.get(url)\n if (cached) return cached.slice(0)\n const existing = inflight.get(url)\n if (existing) return existing.then((bytes) => bytes.slice(0))\n const request = (async () => {\n reportModelDownload(url, { loaded: 0, total: null })\n const response = await fetch(url, { cache: init?.cache })\n if (!response.ok) {\n throw new ScannerError('model-http', `Model request failed with HTTP ${response.status}.`)\n }\n return readResponseBytes(response, (progress) => reportModelDownload(url, progress))\n })()\n inflight.set(url, request)\n try {\n const bytes = await request\n cache.set(url, bytes)\n return bytes.slice(0)\n } finally {\n inflight.delete(url)\n if (active.has(url)) {\n active.delete(url)\n emit()\n }\n }\n}\n","export interface PackagedOrtWasmPaths {\n wasm: string\n mjs: string\n}\n\n/**\n * Override for the packaged ONNX Runtime files.\n * A string is a directory that contains `ort-wasm-simd-threaded.jsep.wasm`\n * and `ort-wasm-simd-threaded.jsep.mjs`. An object sets each file URL.\n * Omit the option to use the copies shipped with the SDK.\n */\nexport type OrtWasmPaths = string | { wasm: string; mjs: string }\n\n/** Pinned onnxruntime-web version. Do not float `latest`. */\nexport const PINNED_ONNXRUNTIME_WEB_VERSION = '1.30.0' as const\n\nexport const PREFERRED_ORT_CDN_BASE =\n `https://exige-scanner-sdk.vercel.app/ort/${PINNED_ONNXRUNTIME_WEB_VERSION}/`\n\nexport const LAST_RESORT_ORT_CDN_BASE =\n `https://cdn.jsdelivr.net/npm/onnxruntime-web@${PINNED_ONNXRUNTIME_WEB_VERSION}/dist/`\n\nexport const SIBLING_ORT_MODULE = 'onnxruntime-web'\nexport const ORT_JS_FILENAME = 'ort.min.mjs'\nexport const ORT_WASM_FILENAME = 'ort-wasm-simd-threaded.jsep.wasm'\nexport const ORT_WASM_MJS_FILENAME = 'ort-wasm-simd-threaded.jsep.mjs'\n\nexport type OrtModule = typeof import('onnxruntime-web')\nexport type OrtLoadSource = 'sibling' | 'preferred-cdn' | 'last-resort-cdn'\nexport type OrtImporter = (specifier: string) => Promise<OrtModule>\n\nexport function joinOrtCdnUrl(base: string, fileName: string): string {\n const root = base.endsWith('/') ? base : `${base}/`\n return `${root}${fileName}`\n}\n\nexport function siblingOrtUrl(workerHref: string): string {\n return new URL('./ort.min.js', workerHref).href\n}\n\nexport function pinnedOrtJsUrls(): readonly [string, string] {\n return [\n joinOrtCdnUrl(PREFERRED_ORT_CDN_BASE, ORT_JS_FILENAME),\n joinOrtCdnUrl(LAST_RESORT_ORT_CDN_BASE, ORT_JS_FILENAME),\n ]\n}\n\nexport function pinnedOrtWasmPaths(base: string = PREFERRED_ORT_CDN_BASE): PackagedOrtWasmPaths {\n return {\n wasm: joinOrtCdnUrl(base, ORT_WASM_FILENAME),\n mjs: joinOrtCdnUrl(base, ORT_WASM_MJS_FILENAME),\n }\n}\n\nexport function expandOrtWasmPaths(value: OrtWasmPaths): PackagedOrtWasmPaths {\n if (typeof value === 'string') {\n return {\n wasm: joinOrtCdnUrl(value, ORT_WASM_FILENAME),\n mjs: joinOrtCdnUrl(value, ORT_WASM_MJS_FILENAME),\n }\n }\n return { wasm: value.wasm, mjs: value.mjs }\n}\n\nfunction hasOrtWasmValue(value?: OrtWasmPaths): value is OrtWasmPaths {\n if (typeof value === 'string') return value.trim().length > 0\n return Boolean(value?.wasm.trim() && value.mjs.trim())\n}\n\nfunction isSuccessfulAssetResponse(response: Response): boolean {\n if (!response.ok && response.status !== 206) return false\n const type = response.headers.get('content-type') ?? ''\n return !type.toLowerCase().includes('text/html')\n}\n\n/** HEAD, then GET. Treat HTML (SPA fallback) and 404 as missing. */\nexport async function probeHttpUrl(\n url: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<boolean> {\n if (!url) return false\n try {\n const head = await fetchImpl(url, { method: 'HEAD' })\n if (isSuccessfulAssetResponse(head)) return true\n if (head.status === 404 || head.status === 410) return false\n } catch {\n // Some hosts reject HEAD; try GET.\n }\n try {\n const get = await fetchImpl(url, {\n method: 'GET',\n headers: { Range: 'bytes=0-63' },\n })\n return isSuccessfulAssetResponse(get)\n } catch {\n return false\n }\n}\n\nexport async function probeOrtWasmPaths(\n value: OrtWasmPaths,\n fetchImpl: typeof fetch = fetch,\n): Promise<boolean> {\n const { wasm, mjs } = expandOrtWasmPaths(value)\n const [wasmOk, mjsOk] = await Promise.all([\n probeHttpUrl(wasm, fetchImpl),\n probeHttpUrl(mjs, fetchImpl),\n ])\n return wasmOk && mjsOk\n}\n\n/**\n * Prefer same-origin / caller wasm when those URLs exist.\n * A 404 override still falls back to the pinned CDN pair.\n */\nexport async function resolveUsableOrtWasmPaths(\n value?: OrtWasmPaths,\n fetchImpl: typeof fetch = fetch,\n): Promise<OrtWasmPaths> {\n if (hasOrtWasmValue(value) && await probeOrtWasmPaths(value, fetchImpl)) {\n if (typeof value === 'string') return value.trim()\n return { wasm: value.wasm.trim(), mjs: value.mjs.trim() }\n }\n const preferred = pinnedOrtWasmPaths(PREFERRED_ORT_CDN_BASE)\n if (await probeOrtWasmPaths(preferred, fetchImpl)) return preferred\n return pinnedOrtWasmPaths(LAST_RESORT_ORT_CDN_BASE)\n}\n\nexport async function importOrtSpecifier(specifier: string): Promise<OrtModule> {\n return import(specifier) as Promise<OrtModule>\n}\n\nexport async function loadOrtJsFromPinnedCdn(\n importModule: OrtImporter = importOrtSpecifier,\n): Promise<OrtModule> {\n const errors: unknown[] = []\n for (const url of pinnedOrtJsUrls()) {\n try {\n return await importModule(url)\n } catch (error) {\n errors.push(error)\n }\n }\n throw new Error(\n `Failed to load onnxruntime-web@${PINNED_ONNXRUNTIME_WEB_VERSION} from the pinned CDN.`,\n { cause: errors.at(-1) },\n )\n}\n\n/**\n * Sibling `onnxruntime-web` first (webpack copies `./ort.min.js`).\n * Next 15 turbo leaves that sibling missing, so use the pinned CDN.\n */\nexport async function loadOrtRuntime(\n importModule: OrtImporter = importOrtSpecifier,\n): Promise<{ ort: OrtModule; source: OrtLoadSource }> {\n try {\n return { ort: await importModule(SIBLING_ORT_MODULE), source: 'sibling' }\n } catch {\n // Next turbo emits the worker without a sibling ort.min.js.\n }\n const [preferred, lastResort] = pinnedOrtJsUrls()\n try {\n return { ort: await importModule(preferred), source: 'preferred-cdn' }\n } catch {\n return { ort: await importModule(lastResort), source: 'last-resort-cdn' }\n }\n}\n","import {\n decodeDocumentHeatmaps,\n preprocessDocumentModel,\n} from './document-ml'\nimport { ScannerError, type ScannerErrorCode } from './errors'\nimport { readResponseBytes, type ModelDownloadProgress } from './model-download'\nimport {\n loadOrtJsFromPinnedCdn,\n resolveUsableOrtWasmPaths,\n} from './ort-cdn'\nimport type { DocumentModelStatus } from './types'\nimport type { QuadCandidate } from './document-geometry'\n\ninterface PixelAnalysis {\n sharpness: number\n candidate?: QuadCandidate\n detector?: 'ml'\n modelStatus?: DocumentModelStatus\n modelProvider?: 'webgpu' | 'wasm'\n modelError?: string\n /** WebGPU attempt that fell back to WASM, or that WebGPU is missing. */\n modelNote?: string\n inferenceMs?: number\n}\n\ninterface AnalysisRequest {\n id: number\n pixels: ArrayBuffer\n width: number\n height: number\n modelUrl?: string\n heatmapThreshold?: number\n cacheModel?: boolean\n ortWasmPaths?: string | { wasm: string; mjs: string }\n}\n\ninterface AnalysisResponse {\n id: number\n result?: PixelAnalysis\n error?: string\n errorCode?: ScannerErrorCode\n download?: {\n url: string\n loaded: number\n total: number | null\n done?: boolean\n }\n}\n\ninterface WorkerScope {\n onmessage: ((event: MessageEvent<AnalysisRequest>) => void) | null\n postMessage: (message: AnalysisResponse) => void\n}\n\nconst workerScope = globalThis as unknown as WorkerScope\nconst modelBytes = new Map<string, Promise<ArrayBuffer>>()\ntype DocumentSession = {\n session: import('onnxruntime-web').InferenceSession\n ort: typeof import('onnxruntime-web')\n provider: 'wasm'\n}\n\nconst sessions = new Map<string, Promise<DocumentSession>>()\n\nconst MODEL_DATABASE = 'exige-scanner-sdk-models'\nconst MODEL_STORE = 'model-bytes'\n\nfunction openModelDatabase() {\n return new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(MODEL_DATABASE, 1)\n request.onupgradeneeded = () => {\n if (!request.result.objectStoreNames.contains(MODEL_STORE)) {\n request.result.createObjectStore(MODEL_STORE)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n}\n\nasync function readCachedModel(modelUrl: string) {\n if (typeof indexedDB === 'undefined') return undefined\n const database = await openModelDatabase()\n try {\n return await new Promise<ArrayBuffer | undefined>((resolve, reject) => {\n const request = database.transaction(MODEL_STORE).objectStore(MODEL_STORE).get(modelUrl)\n request.onsuccess = () => resolve(\n request.result instanceof ArrayBuffer ? request.result : undefined,\n )\n request.onerror = () => reject(request.error)\n })\n } finally {\n database.close()\n }\n}\n\nasync function writeCachedModel(modelUrl: string, bytes: ArrayBuffer) {\n if (typeof indexedDB === 'undefined') return\n const database = await openModelDatabase()\n try {\n await new Promise<void>((resolve, reject) => {\n const transaction = database.transaction(MODEL_STORE, 'readwrite')\n transaction.objectStore(MODEL_STORE).put(bytes, modelUrl)\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => reject(transaction.error)\n })\n } finally {\n database.close()\n }\n}\n\nasync function fetchModel(\n modelUrl: string,\n cacheModel: boolean,\n onProgress?: (progress: ModelDownloadProgress) => void,\n) {\n let request = modelBytes.get(modelUrl)\n if (!request) {\n request = (async () => {\n if (cacheModel) {\n try {\n const cached = await readCachedModel(modelUrl)\n if (cached) return cached\n } catch {\n // IndexedDB may be unavailable in private/embedded contexts.\n }\n }\n onProgress?.({ loaded: 0, total: null })\n let bytes: ArrayBuffer\n try {\n const response = await fetch(modelUrl, {\n cache: cacheModel ? 'force-cache' : 'no-store',\n })\n if (!response.ok) {\n throw new ScannerError(\n 'model-http',\n `Document model request failed with HTTP ${response.status}.`,\n )\n }\n bytes = await readResponseBytes(response, onProgress)\n } catch (error) {\n onProgress?.({ loaded: 0, total: null, done: true })\n throw error\n }\n onProgress?.({ loaded: bytes.byteLength, total: bytes.byteLength, done: true })\n if (cacheModel) {\n try {\n await writeCachedModel(modelUrl, bytes)\n } catch {\n // HTTP caching and the in-worker promise cache still apply.\n }\n }\n return bytes\n })()\n modelBytes.set(modelUrl, request)\n }\n return request\n}\n\nfunction ortWasmKey(ortWasmPaths?: string | { wasm: string; mjs: string }) {\n if (!ortWasmPaths) return ''\n if (typeof ortWasmPaths === 'string') return ortWasmPaths\n return `${ortWasmPaths.wasm}|${ortWasmPaths.mjs}`\n}\n\nasync function loadOrtModule() {\n try {\n return await import('onnxruntime-web')\n } catch {\n return loadOrtJsFromPinnedCdn()\n }\n}\n\nasync function loadSession(\n modelUrl: string,\n cacheModel: boolean,\n ortWasmPaths?: string | { wasm: string; mjs: string },\n onProgress?: (progress: ModelDownloadProgress) => void,\n) {\n const sessionKey = `${modelUrl}|${ortWasmKey(ortWasmPaths)}`\n let request = sessions.get(sessionKey)\n if (!request) {\n request = (async () => {\n const [ort, bytes] = await Promise.all([\n loadOrtModule(),\n fetchModel(modelUrl, cacheModel, onProgress),\n ])\n ort.env.wasm.wasmPaths = await resolveUsableOrtWasmPaths(ortWasmPaths)\n // WASM only. Android Chrome can create a WebGPU session whose corner\n // heatmaps never peak, so a WebGPU-first attempt finds no document.\n const session = await ort.InferenceSession.create(bytes, {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n })\n return { session, ort, provider: 'wasm' as const }\n })()\n sessions.set(sessionKey, request)\n }\n return request\n}\n\nasync function analyzeWithModel(request: AnalysisRequest): Promise<PixelAnalysis> {\n if (!request.modelUrl) {\n throw new Error('A DocAligner modelUrl is required.')\n }\n const pixels = new Uint8ClampedArray(request.pixels)\n const input = preprocessDocumentModel(pixels, request.width, request.height)\n const { session, ort, provider } = await loadSession(\n request.modelUrl,\n request.cacheModel !== false,\n request.ortWasmPaths,\n (progress) => {\n workerScope.postMessage({\n id: request.id,\n download: { url: request.modelUrl ?? '', ...progress },\n })\n },\n )\n const startedAt = performance.now()\n const outputs = await session.run({\n [session.inputNames[0]]: new ort.Tensor('float32', input.data, input.dimensions),\n })\n const tensor = outputs[session.outputNames[0]]\n if (!tensor) throw new Error('Document model returned no output tensor.')\n const decoded = decodeDocumentHeatmaps(\n tensor.data as Float32Array,\n tensor.dims.map(Number),\n request.width,\n request.height,\n request.heatmapThreshold ?? 0.3,\n input.letterbox,\n )\n return {\n sharpness: 20,\n candidate: decoded.candidate,\n detector: 'ml',\n modelStatus: 'ready',\n modelProvider: provider,\n modelError: decoded.candidate ? undefined : decoded.rejectReason,\n inferenceMs: performance.now() - startedAt,\n }\n}\n\nasync function analyze(request: AnalysisRequest): Promise<PixelAnalysis> {\n try {\n const ml = await analyzeWithModel(request)\n if (!ml.candidate) {\n return {\n ...ml,\n modelStatus: 'failed',\n modelError: ml.modelError ?? 'ML detector returned no geometrically valid corners.',\n }\n }\n return ml\n } catch (error) {\n if (error instanceof ScannerError && error.code === 'model-http') throw error\n const message = error instanceof Error ? error.message : 'Document model failed.'\n return {\n sharpness: 20,\n detector: 'ml',\n modelStatus: 'failed',\n modelError: message,\n }\n }\n}\n\nlet analysisQueue = Promise.resolve()\nworkerScope.onmessage = (event) => {\n const request = event.data\n analysisQueue = analysisQueue.then(async () => {\n try {\n workerScope.postMessage({ id: request.id, result: await analyze(request) })\n } catch (error) {\n workerScope.postMessage({\n id: request.id,\n error: error instanceof Error ? error.message : 'Document analysis failed.',\n errorCode: error instanceof ScannerError ? error.code : undefined,\n })\n }\n })\n}\n"],"mappings":"AAoBA,SAAS,EAAM,EAAe,EAAU,EAAU,CAChD,OAAQ,EAAE,EAAI,EAAO,IAAM,EAAE,EAAI,EAAO,IAAM,EAAE,EAAI,EAAO,IAAM,EAAE,EAAI,EAAO,EAChF,CAEA,SAAgB,EAAY,EAAiB,CAC3C,IAAI,EAAM,EACV,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAO,OAAQ,GAAS,EAAG,CACrD,IAAM,EAAU,EAAO,GACjB,EAAO,GAAQ,EAAQ,GAAK,EAAO,QACzC,GAAO,EAAQ,EAAI,EAAK,EAAI,EAAK,EAAI,EAAQ,CAC/C,CACA,OAAO,KAAK,IAAI,CAAG,EAAI,CACzB,CAEA,SAAgB,EAAa,EAA8C,CACzE,GAAI,EAAO,OAAS,EAAG,OACvB,IAAM,EAAU,EAAO,QAAQ,EAAM,IAAU,EAAM,EAAI,EAAM,EAAI,EAAK,EAAI,EAAK,EAAI,EAAQ,CAAI,EAC3F,EAAc,EAAO,QAAQ,EAAM,IAAU,EAAM,EAAI,EAAM,EAAI,EAAK,EAAI,EAAK,EAAI,EAAQ,CAAI,EAC/F,EAAW,EAAO,QAAQ,EAAM,IAAU,EAAM,EAAI,EAAM,EAAI,EAAK,EAAI,EAAK,EAAI,EAAQ,CAAI,EAC5F,EAAa,EAAO,QAAQ,EAAM,IAAU,EAAM,EAAI,EAAM,EAAI,EAAK,EAAI,EAAK,EAAI,EAAQ,CAAI,EAEpG,oBAAI,IADe,IAAI,CAAC,EAAS,EAAU,EAAa,CAAU,CAC9D,EAAA,CAAO,OAAS,EACpB,MAAO,CAAE,UAAS,WAAU,cAAa,YAAW,CACtD,CAEA,SAAS,EAAQ,EAAe,CAC9B,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,CAAK,CAAC,CACvC,CAEA,SAAS,EAAS,EAAU,EAAU,CACpC,OAAO,KAAK,MAAM,EAAE,EAAI,EAAE,EAAG,EAAE,EAAI,EAAE,CAAC,CACxC,CAEA,SAAS,EAAc,EAAiB,EAAe,EAAa,CAClE,IAAM,EAAK,EAAS,EAAI,EAAO,EACzB,EAAK,EAAS,EAAI,EAAO,EACzB,EAAK,EAAK,EAAI,EAAO,EACrB,EAAK,EAAK,EAAI,EAAO,EACrB,EAAc,KAAK,IAAI,KAAM,KAAK,MAAM,EAAI,CAAE,EAAI,KAAK,MAAM,EAAI,CAAE,CAAC,EAC1E,OAAO,KAAK,KAAK,KAAK,IAAI,GAAI,KAAK,IAAI,GAAI,EAAK,EAAK,EAAK,GAAM,CAAW,CAAC,CAAC,EAAI,IAAM,KAAK,EAC9F,CAEA,SAAS,EAAoB,EAAU,EAAU,EAAU,EAAU,CACnE,IAAM,EAAQ,KAAK,MAAM,EAAE,EAAI,EAAE,EAAG,EAAE,EAAI,EAAE,CAAC,EACvC,EAAS,KAAK,MAAM,EAAE,EAAI,EAAE,EAAG,EAAE,EAAI,EAAE,CAAC,EAC1C,EAAa,KAAK,IAAI,EAAQ,CAAM,EAAI,KAAK,GAEjD,OADI,EAAa,KAAK,GAAK,IAAG,EAAa,KAAK,GAAK,GAC9C,CACT,CAEA,SAAS,EAAuB,EAA0B,CACxD,GAAM,CAAE,QAAS,EAAI,SAAU,EAAI,YAAa,EAAI,WAAY,GAAO,EACjE,EAAM,EAAG,EAAI,EAAG,EAChB,EAAM,EAAG,EAAI,EAAG,EAChB,EAAM,EAAG,EAAI,EAAG,EAChB,EAAM,EAAG,EAAI,EAAG,EAChB,EAAK,EAAG,EAAI,EAAG,EAAI,EAAG,EAAI,EAAG,EAC7B,EAAK,EAAG,EAAI,EAAG,EAAI,EAAG,EAAI,EAAG,EAC7B,EAAc,EAAM,EAAM,EAAM,EAChC,EAAI,KAAK,IAAI,CAAW,EAAI,KAAO,GAAK,EAAK,EAAM,EAAM,GAAM,EAC/D,EAAI,KAAK,IAAI,CAAW,EAAI,KAAO,GAAK,EAAM,EAAK,EAAK,GAAO,EACrE,MAAO,CACL,EAAG,EAAG,EAAI,EAAG,EAAI,EAAI,EAAG,EACxB,EAAG,EAAG,EAAI,EAAG,EAAI,EAAI,EAAG,EACxB,EAAG,EAAG,EACN,EAAG,EAAG,EAAI,EAAG,EAAI,EAAI,EAAG,EACxB,EAAG,EAAG,EAAI,EAAG,EAAI,EAAI,EAAG,EACxB,EAAG,EAAG,EACN,IACA,GACF,CACF,CAEA,SAAS,EAAW,EAAc,EAAyD,CACzF,GAAM,CAAE,IAAG,IAAG,IAAG,IAAG,IAAG,IAAG,IAAG,KAAM,EAC7B,EAAK,EAAI,EAAM,EAAI,EACnB,EAAK,EAAI,EAAM,EAAI,EACnB,EAAK,EAAI,EAAM,EAAI,EACnB,EAAK,EAAI,EAAM,EAAI,EACnB,EAAK,EAAM,EAAI,EACf,EAAK,EAAM,EAAI,EACf,EAAc,EAAK,EAAK,EAAK,EAC/B,UAAK,IAAI,CAAW,EAAI,MAC5B,MAAO,CACL,GAAI,EAAK,EAAK,EAAK,GAAM,EACzB,GAAI,EAAK,EAAK,EAAK,GAAM,CAC3B,CACF,CAEA,SAAS,EAAwB,EAA0B,EAAe,CACxE,GAAI,CAAC,EAAK,OAAQ,MAAO,GACzB,IAAM,EAAe,EAAuB,CAAO,EAC/C,EAAkB,EAClB,EAAU,EACR,EAAS,KAAK,IAAI,EAAG,KAAK,MAAM,EAAK,OAAS,EAAE,CAAC,EACvD,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,GAAS,EAAQ,CACxD,IAAM,EAAO,EAAW,EAAK,GAAQ,CAAY,EACjD,GAAI,CAAC,EAAM,MAAO,GAQlB,IAAM,EAPmB,KAAK,IAC5B,KAAK,IAAI,EAAK,CAAC,EACf,KAAK,IAAI,EAAI,EAAK,CAAC,EACnB,KAAK,IAAI,EAAK,CAAC,EACf,KAAK,IAAI,EAAI,EAAK,CAAC,CAGJ,EADD,KAAK,IAAI,EAAG,CAAC,EAAK,EAAG,EAAK,EAAI,EAAG,CAAC,EAAK,EAAG,EAAK,EAAI,CAC/B,EAAU,EAC9C,GAAmB,EAAW,EAC9B,GAAW,CACb,CACA,IAAM,EAAM,KAAK,KAAK,EAAkB,KAAK,IAAI,EAAG,CAAO,CAAC,EAC5D,OAAO,KAAK,IAAI,CAAC,EAAM,GAAI,CAC7B,CAEA,SAAgB,EACd,EACA,EACA,EACA,EAAgB,CAAC,EAAQ,QAAS,EAAQ,SAAU,EAAQ,YAAa,EAAQ,UAAU,EACrE,CACtB,IAAM,EAAU,CAAC,EAAQ,QAAS,EAAQ,SAAU,EAAQ,YAAa,EAAQ,UAAU,EACrF,EAAa,KAAK,IAAI,EAAG,KAAK,IAAI,EAAY,CAAW,CAAC,EAC1D,EAAe,KAAK,IACxB,EACA,GAAG,EAAQ,IAAK,GAAU,KAAK,IAC7B,CAAC,EAAM,EACP,CAAC,EAAM,EACP,EAAM,EAAI,EACV,EAAM,EAAI,CACZ,EAAI,CAAU,CAChB,EACM,EAAgB,EAAQ,KAAK,EAAO,IACxC,EAAM,EAAO,GAAS,EAAQ,GAAK,GAAI,GAAS,EAAQ,GAAK,EAAE,CACjE,EACM,EAAS,EAAc,MAAO,GAAU,EAAQ,CAAC,GACrD,EAAc,MAAO,GAAU,EAAQ,CAAC,EACpC,EAAY,EAAY,CAAO,EAAI,KAAK,IAAI,EAAG,EAAa,CAAW,EACvE,EAAU,EAEZ,EAAY,IACV,aAAa,EAAU,QAAQ,CAAC,EAAE,gBAClC,EAAY,KACV,aAAa,EAAU,QAAQ,CAAC,EAAE,mBAClC,EAAe,IACb,kCAAkC,EAAa,QAAQ,CAAC,EAAE,GAC1D,IAAA,GAPN,yBAQJ,GAAI,EACF,MAAO,CACL,MAAO,EACP,YACA,WAAY,EACZ,mBAAoB,EACpB,cAAe,EACf,gBAAiB,EACjB,YAAa,EACb,QACF,EAGF,IAAM,EAAS,EAAQ,KAAK,EAAO,IACjC,EAAc,GAAS,EAAQ,GAAK,GAAI,EAAO,GAAS,EAAQ,GAAK,EAAE,CACzE,EACM,EAAc,EAAQ,KAAK,EAAO,IAAU,EAAS,EAAO,GAAS,EAAQ,GAAK,EAAE,CAAC,EACrF,EAAuB,KAAK,IAAI,EAAY,GAAI,EAAY,EAAE,EAClE,KAAK,IAAI,EAAG,KAAK,IAAI,EAAY,GAAI,EAAY,EAAE,CAAC,EAChD,EAAqB,KAAK,IAAI,EAAY,GAAI,EAAY,EAAE,EAChE,KAAK,IAAI,EAAG,KAAK,IAAI,EAAY,GAAI,EAAY,EAAE,CAAC,EACtD,GACE,EAAO,KAAM,GAAU,EAAQ,IAAM,EAAQ,GAAG,GAChD,EAAuB,GACvB,EAAqB,EAErB,MAAO,CACL,MAAO,EACP,YACA,WAAY,EACZ,mBAAoB,EACpB,cAAe,EACf,gBAAiB,EACjB,YAAa,CACf,EAEF,IAAM,EAAa,EAAO,QACvB,EAAK,IAAU,EAAM,KAAK,IAAI,CAAC,KAAK,IAAI,EAAQ,EAAE,EAAI,EAAE,EACzD,CACF,EAAI,EACE,GACJ,KAAK,IAAI,CAAC,KAAK,IAAI,EAAO,GAAK,EAAO,GAAK,GAAG,EAAI,EAAE,EACpD,KAAK,IAAI,CAAC,KAAK,IAAI,EAAO,GAAK,EAAO,GAAK,GAAG,EAAI,EAAE,GAClD,EACE,GACJ,KAAK,IAAI,CAAC,EAAoB,EAAQ,GAAI,EAAQ,GAAI,EAAQ,GAAI,EAAQ,EAAE,EAAI,GAAI,EACpF,KAAK,IAAI,CAAC,EAAoB,EAAQ,GAAI,EAAQ,GAAI,EAAQ,GAAI,EAAQ,EAAE,EAAI,GAAI,GAClF,EACE,EAAkB,EAAwB,EAAS,CAAI,EACvD,EAAY,GAAS,EAAY,KAAQ,EAAG,GAC/C,EAAY,IAAO,GAAS,KAAQ,GAAa,IAAK,EAAI,GACvD,EAAwB,KAAK,IACjC,CAAC,KAAK,IAAI,EAAG,KAAK,IAAI,EAAsB,CAAkB,EAAI,GAAG,EAAI,GAC3E,EACM,EAAuB,EAAe,KAAK,IAAI,CAAC,EAAe,GAAI,EAAI,EACvE,EAAQ,KAAK,IAAI,EAAS,EAAQ,GAAI,EAAQ,EAAE,EAAG,EAAS,EAAQ,GAAI,EAAQ,EAAE,CAAC,EACnF,EAAS,KAAK,IAAI,EAAS,EAAQ,GAAI,EAAQ,EAAE,EAAG,EAAS,EAAQ,GAAI,EAAQ,EAAE,CAAC,EACpF,EAAS,KAAK,IAAI,EAAO,CAAM,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,EAAO,CAAM,CAAC,EACtE,EAAU,KAAK,IAAI,CAAC,KAAK,IAAI,EAAS,IAAI,EAAI,GAAI,EAClD,EAAc,KAAK,IAAI,CAAC,KAAK,IAAI,EAAS,IAAI,EAAI,GAAI,EACtD,EAAe,GAAU,EAAI,GAAS,EAAS,GAAK,CAAC,EAAI,EACzD,EAAc,KAAK,IAAI,EAAS,EAAa,CAAY,EAAI,IAUnE,MAAO,CACL,MAVqB,EACrB,EAAY,IACZ,EAAa,GACb,EAAqB,IACrB,EAAgB,IAChB,EAAkB,GAClB,CAEY,GAAkB,IAAO,EAAwB,KAAQ,EAGrE,YACA,aACA,qBACA,gBACA,kBACA,aACF,CACF,CCrNA,SAAgB,EACd,EACA,EACA,EAAY,IACO,CACnB,IAAM,EAAc,KAAK,IAAI,EAAG,CAAK,EAC/B,EAAe,KAAK,IAAI,EAAG,CAAM,EACjC,EAAQ,KAAK,IAAI,EAAY,EAAa,EAAY,CAAY,EAClE,EAAe,EAAc,EAC7B,EAAgB,EAAe,EACrC,MAAO,CACL,YACA,eACA,gBACA,SAAU,EAAY,GAAgB,EACtC,SAAU,EAAY,GAAiB,EACvC,cACA,eACA,MAAO,EACP,MAAO,EACP,WAAY,EACZ,YAAa,CACf,CACF,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,CACA,MAAO,CACL,EAAG,EAAM,EAAI,EAAe,EAAU,UACtC,EAAG,EAAM,EAAI,EAAgB,EAAU,SACzC,CACF,CAEA,SAAgB,EACd,EACA,EACA,EACA,EACA,EAAS,IACT,CACA,GACE,CAAC,GACD,EAAU,cAAgB,GAC1B,EAAU,eAAiB,GAC3B,CAAC,GACD,CAAC,EAED,MAAO,GAET,IAAM,EAAQ,EAAoB,EAAO,EAAc,EAAe,CAAS,EAC/E,OAAO,EAAM,GAAK,EAAU,QAAU,GACpC,EAAM,GAAK,EAAU,QAAU,EAAU,aAAe,GACxD,EAAM,GAAK,EAAU,QAAU,GAC/B,EAAM,GAAK,EAAU,QAAU,EAAU,cAAgB,CAC7D,CAEA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACA,EACO,CACP,GACE,CAAC,GACD,EAAU,cAAgB,GAC1B,EAAU,eAAiB,GAC3B,CAAC,GACD,CAAC,EAED,MAAO,CACL,EAAG,KAAK,IAAI,EAAG,KAAK,IAAI,EAAa,EAAG,EAAM,EAAI,EAAe,CAAU,CAAC,EAC5E,EAAG,KAAK,IAAI,EAAG,KAAK,IAAI,EAAc,EAAG,EAAM,EAAI,EAAgB,CAAW,CAAC,CACjF,EAEF,IAAM,EAAQ,EAAoB,EAAO,EAAc,EAAe,CAAS,EACzE,EAAU,EAAU,OACvB,EAAM,EAAI,EAAU,SAAW,EAAU,aAAe,EAAU,YAC/D,EAAU,EAAU,OACvB,EAAM,EAAI,EAAU,SAAW,EAAU,cAAgB,EAAU,aACtE,MAAO,CACL,EAAG,KAAK,IAAI,EAAG,KAAK,IAAI,EAAa,EAAG,CAAO,CAAC,EAChD,EAAG,KAAK,IAAI,EAAG,KAAK,IAAI,EAAc,EAAG,CAAO,CAAC,CACnD,CACF,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACA,CACA,IAAM,EAAK,KAAK,IAAI,EAAQ,EAAG,KAAK,IAAI,EAAG,KAAK,MAAM,CAAO,CAAC,CAAC,EACzD,EAAK,KAAK,IAAI,EAAS,EAAG,KAAK,IAAI,EAAG,KAAK,MAAM,CAAO,CAAC,CAAC,EAC1D,EAAK,KAAK,IAAI,EAAQ,EAAG,EAAK,CAAC,EAC/B,EAAK,KAAK,IAAI,EAAS,EAAG,EAAK,CAAC,EAChC,EAAK,EAAU,EACf,EAAK,EAAU,EACf,GAAO,EAAK,EAAQ,GAAM,EAAI,EAC9B,GAAO,EAAK,EAAQ,GAAM,EAAI,EAC9B,GAAO,EAAK,EAAQ,GAAM,EAAI,EAC9B,GAAO,EAAK,EAAQ,GAAM,EAAI,EACpC,OACE,EAAO,IAAQ,EAAI,IAAO,EAAI,GAC9B,EAAO,GAAO,GAAM,EAAI,GACxB,EAAO,IAAQ,EAAI,GAAM,EACzB,EAAO,GAAO,EAAK,GACjB,GACN,CAEA,SAAgB,EACd,EACA,EACA,EACA,EAAY,IACQ,CACpB,IAAM,EAAY,EAAuB,EAAO,EAAQ,CAAS,EAC3D,EAAY,EAAY,EACxB,EAAO,IAAI,aAAa,EAAY,CAAC,EACrC,EAAa,EAAU,YAAc,EAAU,cACnD,EAAU,aAAe,EAAU,cACrC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAW,GAAK,EAAG,CACrC,IAAM,GAAe,EAAI,GAAM,EAAU,SAAW,EAAU,cAC1D,OAAc,GAAK,EAAc,GACrC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAW,GAAK,EAAG,CACrC,IAAM,GAAe,EAAI,GAAM,EAAU,SAAW,EAAU,aAC9D,GAAI,EAAc,GAAK,EAAc,EAAG,SACxC,IAAM,EAAe,EAAI,EAAY,EACrC,GAAI,EAAY,CACd,IAAM,EAAU,EAAU,MAAQ,EAAc,EAAU,YAAc,GAClE,EAAU,EAAU,MAAQ,EAAc,EAAU,aAAe,GACzE,EAAK,GAAgB,EAAsB,EAAQ,EAAO,EAAQ,EAAS,EAAS,CAAC,EACrF,EAAK,EAAY,GAAgB,EAAsB,EAAQ,EAAO,EAAQ,EAAS,EAAS,CAAC,EACjG,EAAK,EAAY,EAAI,GAAgB,EAAsB,EAAQ,EAAO,EAAQ,EAAS,EAAS,CAAC,EACrG,QACF,CACA,IAAM,EAAU,KAAK,IAAI,EAAQ,EAAG,KAAK,IAAI,EAAG,KAAK,MACnD,EAAU,MAAQ,EAAc,EAAU,YAAc,EAC1D,CAAC,CAAC,EAII,GAHU,KAAK,IAAI,EAAS,EAAG,KAAK,IAAI,EAAG,KAAK,MACpD,EAAU,MAAQ,EAAc,EAAU,aAAe,EAC3D,CAAC,CACqB,EAAU,EAAQ,GAAW,EACnD,EAAK,GAAgB,EAAO,GAAgB,IAC5C,EAAK,EAAY,GAAgB,EAAO,EAAe,GAAK,IAC5D,EAAK,EAAY,EAAI,GAAgB,EAAO,EAAe,GAAK,GAClE,CACF,CACA,MAAO,CAAE,OAAM,WAAY,CAAC,EAAG,EAAG,EAAW,CAAS,EAAG,WAAU,CACrE,CAEA,SAAS,EAAkB,EAAsB,EAAc,EAAe,CAC5E,IAAI,EAAM,KACV,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAO,GAAS,EAAG,CAC7C,IAAM,EAAQ,EAAO,EAAO,GACxB,EAAQ,IAAK,EAAM,EACzB,CACA,OAAO,OAAO,SAAS,CAAG,EAAI,EAAM,CACtC,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,CACA,IAAM,EAAU,IAAI,WAAW,EAAQ,CAAM,EACvC,EAAQ,IAAI,WAAW,EAAQ,CAAM,EACvC,EAAY,EACZ,EACA,EAAW,EACf,IAAK,IAAI,EAAO,EAAG,EAAO,EAAQ,EAAQ,GAAQ,EAAG,CACnD,GAAI,EAAQ,IAAS,EAAO,EAAO,GAAQ,EAAW,SACtD,IAAI,EAAO,EACP,EAAQ,EACR,EAAY,EACZ,EAAY,EACZ,EAAc,EACd,EAAO,EAGX,IAFA,EAAM,KAAW,EACjB,EAAQ,GAAQ,EACT,EAAO,GAAO,CACnB,IAAM,EAAQ,EAAM,KACd,EAAI,EAAQ,EACZ,EAAI,KAAK,MAAM,EAAQ,CAAK,EAC5B,EAAS,KAAK,IAAI,EAAG,EAAO,EAAO,EAAM,EAC/C,IAAc,EAAI,IAAO,EACzB,IAAc,EAAI,IAAO,EACzB,GAAe,EACf,EAAO,KAAK,IAAI,EAAM,CAAM,EAC5B,IAAM,EAAY,CAAC,EAAQ,EAAG,EAAQ,EAAG,EAAQ,EAAO,EAAQ,CAAK,EACrE,IAAK,IAAM,KAAY,EAAW,CAChC,GACE,EAAW,GACX,GAAY,EAAQ,GACpB,EAAQ,IACR,EAAO,EAAO,GAAY,EAC1B,SACF,IAAM,EAAY,EAAW,EACzB,KAAK,IAAI,EAAY,CAAC,EAAI,IAC9B,EAAQ,GAAY,EACpB,EAAM,KAAW,EACnB,CACF,CACI,EAAQ,GAAa,EAAc,IACrC,EAAY,EACZ,EAAW,EACX,EAAY,CACV,EAAG,EAAY,EACf,EAAG,EAAY,CACjB,EAEJ,CACA,OAAO,EAAY,CAAE,MAAO,EAAW,KAAM,CAAS,EAAI,IAAA,EAC5D,CAEA,SAAgB,EACd,EACA,EACA,EACA,EACA,EAAY,GACZ,EACyB,CACzB,GACE,EAAW,SAAW,GACtB,OAAO,EAAW,EAAE,IAAM,GAC1B,OAAO,EAAW,EAAE,IAAM,EAE1B,MAAU,MAAM,qDAAqD,EAAW,KAAK,GAAG,EAAE,GAAG,EAE/F,IAAM,EAAgB,OAAO,EAAW,EAAE,EACpC,EAAe,OAAO,EAAW,EAAE,EACnC,EAAY,EAAe,EACjC,GAAI,EAAS,OAAS,EAAY,GAAK,CAAC,GAAgB,CAAC,EACvD,MAAU,MAAM,wCAAwC,EAE1D,IAAM,EAAkB,CAAC,EACnB,EAA8B,CAAC,EACrC,IAAK,IAAI,EAAU,EAAG,EAAU,EAAG,GAAW,EAAG,CAC/C,IAAM,EAAY,EAChB,EACA,EAAU,EACV,EACA,EACA,CACF,EACA,GAAI,CAAC,EAAW,CACd,IAAM,EAAM,EAAkB,EAAU,EAAU,EAAW,CAAS,EACtE,MAAO,CACL,oBACA,aAAc,8BAA8B,EAAU,EAAE,WAAW,EAAU,QAAQ,EAAI,QAAQ,CAAC,EAAE,OAAO,EAAW,GAAG,GAC3H,CACF,CAEA,GADA,EAAkB,KAAK,EAAU,IAAI,EACjC,CAAC,EAAwB,EAAU,MAAO,EAAc,EAAe,CAAS,EAClF,MAAO,CACL,oBACA,aAAc,EACV,UAAU,EAAU,EAAE,qCAAqC,EAAW,GAAG,EAAY,YAAY,KAAK,MAAM,EAAU,YAAY,EAAE,GAAG,KAAK,MAAM,EAAU,aAAa,EAAE,UAAU,EAAU,UAAU,IACzM,UAAU,EAAU,EAAE,kCAAkC,EAAW,GAAG,GAC5E,EAEF,EAAO,KAAK,EACV,EAAU,MACV,EACA,EACA,EACA,EACA,CACF,CAAC,CACH,CACA,IAAM,EAAU,EAAa,CAAM,EACnC,GAAI,CAAC,EACH,MAAO,CACL,oBACA,aAAc,mDAAmD,EAAW,GAAG,GACjF,EAEF,IAAM,EAAW,EAAqB,EAAS,EAAY,CAAW,EACtE,GAAI,EAAS,MAAQ,IACnB,MAAO,CACL,oBACA,aAAc,EAAS,QACrB,kBAAkB,EAAS,MAAM,QAAQ,CAAC,EAAE,uBAAuB,EAAS,UAAU,QAAQ,CAAC,EAAE,OAAO,EAAW,GAAG,GAC1H,EAEF,IAAM,EAAoB,EAAkB,QAAQ,EAAK,IAAU,EAAM,EAAO,CAAC,EAAI,EACrF,MAAO,CACL,UAAW,CACT,UACA,UAAW,EAAS,UACpB,WAAY,KAAK,IAAI,IAAM,EAAoB,IAAO,EAAS,MAAQ,GAAI,CAC7E,EACA,mBACF,CACF,CCjUA,IAAa,EAAb,cAAkC,KAAM,CACtC,KAEA,YAAY,EAAwB,EAAiB,CACnD,MAAM,CAAO,EACb,KAAK,KAAO,eACZ,KAAK,KAAO,CACd,CACF,ECPA,MAAM,EAAQ,OAAO,IAAI,uCAAuC,EAUhE,SAAS,GAA4B,CACnC,IAAM,EAAQ,WASd,OARK,EAAM,KACT,EAAM,GAAS,CACb,uBAAQ,IAAI,IACZ,0BAAW,IAAI,IACf,sBAAO,IAAI,IACX,yBAAU,IAAI,GAChB,GAEK,EAAM,EACf,CAEe,EAAM,CAAC,CAAC,OACL,EAAM,CAAC,CAAC,UACZ,EAAM,CAAC,CAAC,MACL,EAAM,CAAC,CAAC,SAEzB,SAAS,EAAc,EAAoB,CACzC,IAAM,EAAS,OAAO,EAAS,QAAQ,IAAI,gBAAgB,CAAC,EAC5D,OAAO,OAAO,SAAS,CAAM,GAAK,EAAS,EAAI,EAAS,IAC1D,CAoCA,eAAsB,EACpB,EACA,EACA,CACA,IAAM,EAAQ,EAAc,CAAQ,EACpC,GAAI,CAAC,EAAS,KAAM,CAClB,IAAM,EAAQ,MAAM,EAAS,YAAY,EAEzC,OADA,IAAa,CAAE,OAAQ,EAAM,WAAY,MAAO,GAAS,EAAM,UAAW,CAAC,EACpE,CACT,CACA,IAAM,EAAS,EAAS,KAAK,UAAU,EACjC,EAAuB,CAAC,EAC1B,EAAS,EACb,OAAS,CACP,GAAM,CAAE,OAAM,SAAU,MAAM,EAAO,KAAK,EAC1C,GAAI,EAAM,MACL,GAAO,aACZ,EAAO,KAAK,CAAK,EACjB,GAAU,EAAM,WAChB,IAAa,CAAE,SAAQ,OAAM,CAAC,EAChC,CACA,IAAM,EAAQ,IAAI,WAAW,CAAM,EAC/B,EAAS,EACb,IAAK,IAAM,KAAS,EAClB,EAAM,IAAI,EAAO,CAAM,EACvB,GAAU,EAAM,WAGlB,OADI,IAAW,GAAG,IAAa,CAAE,OAAQ,EAAG,OAAM,CAAC,EAC5C,EAAM,MACf,CClGA,MAAa,EAAiC,SAEjC,EACX,4CAA4C,EAA+B,GAEhE,EACX,gDAAgD,EAA+B,QAGpE,EAAkB,cAClB,EAAoB,mCACpB,EAAwB,kCAMrC,SAAgB,EAAc,EAAc,EAA0B,CAEpE,MAAO,GADM,EAAK,SAAS,GAAG,EAAI,EAAO,GAAG,EAAK,KAChC,GACnB,CAMA,SAAgB,GAA6C,CAC3D,MAAO,CACL,EAAc,EAAwB,CAAe,EACrD,EAAc,EAA0B,CAAe,CACzD,CACF,CAEA,SAAgB,EAAmB,EAAe,EAA8C,CAC9F,MAAO,CACL,KAAM,EAAc,EAAM,CAAiB,EAC3C,IAAK,EAAc,EAAM,CAAqB,CAChD,CACF,CAEA,SAAgB,EAAmB,EAA2C,CAO5E,OANI,OAAO,GAAU,SACZ,CACL,KAAM,EAAc,EAAO,CAAiB,EAC5C,IAAK,EAAc,EAAO,CAAqB,CACjD,EAEK,CAAE,KAAM,EAAM,KAAM,IAAK,EAAM,GAAI,CAC5C,CAEA,SAAS,EAAgB,EAA6C,CAEpE,OADI,OAAO,GAAU,SAAiB,EAAM,KAAK,CAAC,CAAC,OAAS,EACrD,GAAQ,GAAO,KAAK,KAAK,GAAK,EAAM,IAAI,KAAK,EACtD,CAEA,SAAS,EAA0B,EAA6B,CAG9D,MAFI,CAAC,EAAS,IAAM,EAAS,SAAW,IAAY,GAE7C,EADM,EAAS,QAAQ,IAAI,cAAc,GAAK,GAAA,CACxC,YAAY,CAAC,CAAC,SAAS,WAAW,CACjD,CAGA,eAAsB,EACpB,EACA,EAA0B,MACR,CAClB,GAAI,CAAC,EAAK,MAAO,GACjB,GAAI,CACF,IAAM,EAAO,MAAM,EAAU,EAAK,CAAE,OAAQ,MAAO,CAAC,EACpD,GAAI,EAA0B,CAAI,EAAG,MAAO,GAC5C,GAAI,EAAK,SAAW,KAAO,EAAK,SAAW,IAAK,MAAO,EACzD,MAAQ,CAER,CACA,GAAI,CAKF,OAAO,EAA0B,MAJf,EAAU,EAAK,CAC/B,OAAQ,MACR,QAAS,CAAE,MAAO,YAAa,CACjC,CAAC,CACmC,CACtC,MAAQ,CACN,MAAO,EACT,CACF,CAEA,eAAsB,EACpB,EACA,EAA0B,MACR,CAClB,GAAM,CAAE,OAAM,OAAQ,EAAmB,CAAK,EACxC,CAAC,EAAQ,GAAS,MAAM,QAAQ,IAAI,CACxC,EAAa,EAAM,CAAS,EAC5B,EAAa,EAAK,CAAS,CAC7B,CAAC,EACD,OAAO,GAAU,CACnB,CAMA,eAAsB,EACpB,EACA,EAA0B,MACH,CACvB,GAAI,EAAgB,CAAK,GAAK,MAAM,EAAkB,EAAO,CAAS,EAEpE,OADI,OAAO,GAAU,SAAiB,EAAM,KAAK,EAC1C,CAAE,KAAM,EAAM,KAAK,KAAK,EAAG,IAAK,EAAM,IAAI,KAAK,CAAE,EAE1D,IAAM,EAAY,EAAmB,CAAsB,EAE3D,OADI,MAAM,EAAkB,EAAW,CAAS,EAAU,EACnD,EAAmB,CAAwB,CACpD,CAEA,eAAsB,EAAmB,EAAuC,CAC9E,OAAO,OAAO,EAChB,CAEA,eAAsB,EACpB,EAA4B,EACR,CACpB,IAAM,EAAoB,CAAC,EAC3B,IAAK,IAAM,KAAO,EAAgB,EAChC,GAAI,CACF,OAAO,MAAM,EAAa,CAAG,CAC/B,OAAS,EAAO,CACd,EAAO,KAAK,CAAK,CACnB,CAEF,MAAU,MACR,kCAAkC,EAA+B,uBACjE,CAAE,MAAO,EAAO,GAAG,EAAE,CAAE,CACzB,CACF,CC7FA,MAAM,EAAc,WACd,kBAAa,IAAI,IAOjB,kBAAW,IAAI,IAGf,EAAc,cAEpB,SAAS,GAAoB,CAC3B,OAAO,IAAI,SAAsB,EAAS,IAAW,CACnD,IAAM,EAAU,UAAU,KAAK,2BAAgB,CAAC,EAChD,EAAQ,oBAAwB,CACzB,EAAQ,OAAO,iBAAiB,SAAS,CAAW,GACvD,EAAQ,OAAO,kBAAkB,CAAW,CAEhD,EACA,EAAQ,cAAkB,EAAQ,EAAQ,MAAM,EAChD,EAAQ,YAAgB,EAAO,EAAQ,KAAK,CAC9C,CAAC,CACH,CAEA,eAAe,EAAgB,EAAkB,CAC/C,GAAI,OAAO,UAAc,IAAa,OACtC,IAAM,EAAW,MAAM,EAAkB,EACzC,GAAI,CACF,OAAO,MAAM,IAAI,SAAkC,EAAS,IAAW,CACrE,IAAM,EAAU,EAAS,YAAY,CAAW,CAAC,CAAC,YAAY,CAAW,CAAC,CAAC,IAAI,CAAQ,EACvF,EAAQ,cAAkB,EACxB,EAAQ,kBAAkB,YAAc,EAAQ,OAAS,IAAA,EAC3D,EACA,EAAQ,YAAgB,EAAO,EAAQ,KAAK,CAC9C,CAAC,CACH,QAAU,CACR,EAAS,MAAM,CACjB,CACF,CAEA,eAAe,EAAiB,EAAkB,EAAoB,CACpE,GAAI,OAAO,UAAc,IAAa,OACtC,IAAM,EAAW,MAAM,EAAkB,EACzC,GAAI,CACF,MAAM,IAAI,SAAe,EAAS,IAAW,CAC3C,IAAM,EAAc,EAAS,YAAY,EAAa,WAAW,EACjE,EAAY,YAAY,CAAW,CAAC,CAAC,IAAI,EAAO,CAAQ,EACxD,EAAY,eAAmB,EAAQ,EACvC,EAAY,YAAgB,EAAO,EAAY,KAAK,CACtD,CAAC,CACH,QAAU,CACR,EAAS,MAAM,CACjB,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,CACA,IAAI,EAAU,EAAW,IAAI,CAAQ,EAwCrC,OAvCK,IACH,GAAW,SAAY,CACrB,GAAI,EACF,GAAI,CACF,IAAM,EAAS,MAAM,EAAgB,CAAQ,EAC7C,GAAI,EAAQ,OAAO,CACrB,MAAQ,CAER,CAEF,IAAa,CAAE,OAAQ,EAAG,MAAO,IAAK,CAAC,EACvC,IAAI,EACJ,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,EAAU,CACrC,MAAO,EAAa,cAAgB,UACtC,CAAC,EACD,GAAI,CAAC,EAAS,GACZ,MAAM,IAAI,EACR,aACA,2CAA2C,EAAS,OAAO,EAC7D,EAEF,EAAQ,MAAM,EAAkB,EAAU,CAAU,CACtD,OAAS,EAAO,CAEd,MADA,IAAa,CAAE,OAAQ,EAAG,MAAO,KAAM,KAAM,EAAK,CAAC,EAC7C,CACR,CAEA,GADA,IAAa,CAAE,OAAQ,EAAM,WAAY,MAAO,EAAM,WAAY,KAAM,EAAK,CAAC,EAC1E,EACF,GAAI,CACF,MAAM,EAAiB,EAAU,CAAK,CACxC,MAAQ,CAER,CAEF,OAAO,CACT,EAAA,CAAG,EACH,EAAW,IAAI,EAAU,CAAO,GAE3B,CACT,CAEA,SAAS,EAAW,EAAuD,CAGzE,OAFK,EACD,OAAO,GAAiB,SAAiB,EACtC,GAAG,EAAa,KAAK,GAAG,EAAa,MAFlB,EAG5B,CAEA,eAAe,GAAgB,CAC7B,GAAI,CACF,OAAO,MAAM,OAAO,eACtB,MAAQ,CACN,OAAO,EAAuB,CAChC,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,EACA,CACA,IAAM,EAAa,GAAG,EAAS,GAAG,EAAW,CAAY,IACrD,EAAU,EAAS,IAAI,CAAU,EAkBrC,OAjBK,IACH,GAAW,SAAY,CACrB,GAAM,CAAC,EAAK,GAAS,MAAM,QAAQ,IAAI,CACrC,EAAc,EACd,EAAW,EAAU,EAAY,CAAU,CAC7C,CAAC,EAQD,MAPA,GAAI,IAAI,KAAK,UAAY,MAAM,EAA0B,CAAY,EAO9D,CAAE,QAAA,MAJa,EAAI,iBAAiB,OAAO,EAAO,CACvD,mBAAoB,CAAC,MAAM,EAC3B,uBAAwB,KAC1B,CAAC,EACiB,MAAK,SAAU,MAAgB,CACnD,EAAA,CAAG,EACH,EAAS,IAAI,EAAY,CAAO,GAE3B,CACT,CAEA,eAAe,GAAiB,EAAkD,CAChF,GAAI,CAAC,EAAQ,SACX,MAAU,MAAM,oCAAoC,EAGtD,IAAM,EAAQ,EAAwB,IADnB,kBAAkB,EAAQ,MACP,EAAQ,EAAQ,MAAO,EAAQ,MAAM,EACrE,CAAE,UAAS,MAAK,YAAa,MAAM,EACvC,EAAQ,SACR,EAAQ,aAAe,GACvB,EAAQ,aACP,GAAa,CACZ,EAAY,YAAY,CACtB,GAAI,EAAQ,GACZ,SAAU,CAAE,IAAK,EAAQ,UAAY,GAAI,GAAG,CAAS,CACvD,CAAC,CACH,CACF,EACM,EAAY,YAAY,IAAI,EAI5B,GAAS,MAHO,EAAQ,IAAI,EAC/B,EAAQ,WAAW,IAAK,IAAI,EAAI,OAAO,UAAW,EAAM,KAAM,EAAM,UAAU,CACjF,CAAC,EAAA,CACsB,EAAQ,YAAY,IAC3C,GAAI,CAAC,EAAQ,MAAU,MAAM,2CAA2C,EACxE,IAAM,EAAU,EACd,EAAO,KACP,EAAO,KAAK,IAAI,MAAM,EACtB,EAAQ,MACR,EAAQ,OACR,EAAQ,kBAAoB,GAC5B,EAAM,SACR,EACA,MAAO,CACL,UAAW,GACX,UAAW,EAAQ,UACnB,SAAU,KACV,YAAa,QACb,cAAe,EACf,WAAY,EAAQ,UAAY,IAAA,GAAY,EAAQ,aACpD,YAAa,YAAY,IAAI,EAAI,CACnC,CACF,CAEA,eAAe,GAAQ,EAAkD,CACvE,GAAI,CACF,IAAM,EAAK,MAAM,GAAiB,CAAO,EAQzC,OAPK,EAAG,UAOD,EANE,CACL,GAAG,EACH,YAAa,SACb,WAAY,EAAG,YAAc,sDAC/B,CAGJ,OAAS,EAAO,CACd,GAAI,aAAiB,GAAgB,EAAM,OAAS,aAAc,MAAM,EAExE,MAAO,CACL,UAAW,GACX,SAAU,KACV,YAAa,SACb,WALc,aAAiB,MAAQ,EAAM,QAAU,wBAMzD,CACF,CACF,CAEA,IAAI,EAAgB,QAAQ,QAAQ,EACpC,EAAY,UAAa,GAAU,CACjC,IAAM,EAAU,EAAM,KACtB,EAAgB,EAAc,KAAK,SAAY,CAC7C,GAAI,CACF,EAAY,YAAY,CAAE,GAAI,EAAQ,GAAI,OAAQ,MAAM,GAAQ,CAAO,CAAE,CAAC,CAC5E,OAAS,EAAO,CACd,EAAY,YAAY,CACtB,GAAI,EAAQ,GACZ,MAAO,aAAiB,MAAQ,EAAM,QAAU,4BAChD,UAAW,aAAiB,EAAe,EAAM,KAAO,IAAA,EAC1D,CAAC,CACH,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"document-worker.js","names":[],"sources":["../../src/sdk/document-geometry.ts","../../src/sdk/document-ml.ts","../../src/sdk/errors.ts","../../src/sdk/model-download.ts","../../src/sdk/ort-cdn.ts","../../src/sdk/document-worker.ts"],"sourcesContent":["import type { DocumentCorners, Point } from './types'\n\nexport interface QuadCandidate {\n corners: DocumentCorners\n confidence: number\n areaRatio: number\n}\n\nexport interface PerspectiveQuadScore {\n total: number\n areaRatio: number\n angleScore: number\n oppositeAngleScore: number\n parallelScore: number\n homographyScore: number\n aspectBoost: number\n /** Set when the quad is rejected before a perspective score is computed. */\n reject?: string\n}\n\nfunction cross(origin: Point, a: Point, b: Point) {\n return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x)\n}\n\nexport function polygonArea(points: Point[]) {\n let sum = 0\n for (let index = 0; index < points.length; index += 1) {\n const current = points[index]\n const next = points[(index + 1) % points.length]\n sum += current.x * next.y - next.x * current.y\n }\n return Math.abs(sum) / 2\n}\n\nexport function orderCorners(points: Point[]): DocumentCorners | undefined {\n if (points.length < 4) return undefined\n const topLeft = points.reduce((best, point) => point.x + point.y < best.x + best.y ? point : best)\n const bottomRight = points.reduce((best, point) => point.x + point.y > best.x + best.y ? point : best)\n const topRight = points.reduce((best, point) => point.x - point.y > best.x - best.y ? point : best)\n const bottomLeft = points.reduce((best, point) => point.x - point.y < best.x - best.y ? point : best)\n const unique = new Set([topLeft, topRight, bottomRight, bottomLeft])\n if (unique.size !== 4) return undefined\n return { topLeft, topRight, bottomRight, bottomLeft }\n}\n\nfunction clamp01(value: number) {\n return Math.max(0, Math.min(1, value))\n}\n\nfunction distance(a: Point, b: Point) {\n return Math.hypot(a.x - b.x, a.y - b.y)\n}\n\nfunction interiorAngle(previous: Point, vertex: Point, next: Point) {\n const ax = previous.x - vertex.x\n const ay = previous.y - vertex.y\n const bx = next.x - vertex.x\n const by = next.y - vertex.y\n const denominator = Math.max(1e-6, Math.hypot(ax, ay) * Math.hypot(bx, by))\n return Math.acos(Math.max(-1, Math.min(1, (ax * bx + ay * by) / denominator))) * 180 / Math.PI\n}\n\nfunction directionDifference(a: Point, b: Point, c: Point, d: Point) {\n const first = Math.atan2(b.y - a.y, b.x - a.x)\n const second = Math.atan2(d.y - c.y, d.x - c.x)\n let difference = Math.abs(first - second) % Math.PI\n if (difference > Math.PI / 2) difference = Math.PI - difference\n return difference\n}\n\nfunction unitToQuadCoefficients(corners: DocumentCorners) {\n const { topLeft: tl, topRight: tr, bottomRight: br, bottomLeft: bl } = corners\n const dx1 = tr.x - br.x\n const dx2 = bl.x - br.x\n const dy1 = tr.y - br.y\n const dy2 = bl.y - br.y\n const sx = tl.x - tr.x + br.x - bl.x\n const sy = tl.y - tr.y + br.y - bl.y\n const denominator = dx1 * dy2 - dx2 * dy1\n const g = Math.abs(denominator) < 1e-6 ? 0 : (sx * dy2 - dx2 * sy) / denominator\n const h = Math.abs(denominator) < 1e-6 ? 0 : (dx1 * sy - sx * dy1) / denominator\n return {\n a: tr.x - tl.x + g * tr.x,\n b: bl.x - tl.x + h * bl.x,\n c: tl.x,\n d: tr.y - tl.y + g * tr.y,\n e: bl.y - tl.y + h * bl.y,\n f: tl.y,\n g,\n h,\n }\n}\n\nfunction quadToUnit(point: Point, coefficients: ReturnType<typeof unitToQuadCoefficients>) {\n const { a, b, c, d, e, f, g, h } = coefficients\n const aa = a - point.x * g\n const bb = b - point.x * h\n const dd = d - point.y * g\n const ee = e - point.y * h\n const xx = point.x - c\n const yy = point.y - f\n const determinant = aa * ee - bb * dd\n if (Math.abs(determinant) < 1e-8) return undefined\n return {\n x: (xx * ee - bb * yy) / determinant,\n y: (aa * yy - xx * dd) / determinant,\n }\n}\n\nfunction homographyBoundaryScore(corners: DocumentCorners, hull: Point[]) {\n if (!hull.length) return 0\n const coefficients = unitToQuadCoefficients(corners)\n let squaredResidual = 0\n let samples = 0\n const stride = Math.max(1, Math.floor(hull.length / 80))\n for (let index = 0; index < hull.length; index += stride) {\n const unit = quadToUnit(hull[index], coefficients)\n if (!unit) return 0\n const boundaryDistance = Math.min(\n Math.abs(unit.x),\n Math.abs(1 - unit.x),\n Math.abs(unit.y),\n Math.abs(1 - unit.y),\n )\n const outside = Math.max(0, -unit.x, unit.x - 1, -unit.y, unit.y - 1)\n const residual = boundaryDistance + outside * 2\n squaredResidual += residual * residual\n samples += 1\n }\n const rms = Math.sqrt(squaredResidual / Math.max(1, samples))\n return Math.exp(-rms / 0.08)\n}\n\nexport function scorePerspectiveQuad(\n corners: DocumentCorners,\n frameWidth: number,\n frameHeight: number,\n hull: Point[] = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft],\n): PerspectiveQuadScore {\n const ordered = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft]\n const frameScale = Math.max(1, Math.min(frameWidth, frameHeight))\n const outsideRatio = Math.max(\n 0,\n ...ordered.map((point) => Math.max(\n -point.x,\n -point.y,\n point.x - frameWidth,\n point.y - frameHeight,\n ) / frameScale),\n )\n const signedCrosses = ordered.map((point, index) =>\n cross(point, ordered[(index + 1) % 4], ordered[(index + 2) % 4]),\n )\n const convex = signedCrosses.every((value) => value > 0) ||\n signedCrosses.every((value) => value < 0)\n const areaRatio = polygonArea(ordered) / Math.max(1, frameWidth * frameHeight)\n const reject = !convex\n ? 'corners are not convex'\n : areaRatio < 0.04\n ? `quad area ${areaRatio.toFixed(3)} is under 0.04`\n : areaRatio > 0.995\n ? `quad area ${areaRatio.toFixed(3)} covers the frame`\n : outsideRatio > 0.12\n ? `corners sit outside the frame (${outsideRatio.toFixed(2)})`\n : undefined\n if (reject) {\n return {\n total: 0,\n areaRatio,\n angleScore: 0,\n oppositeAngleScore: 0,\n parallelScore: 0,\n homographyScore: 0,\n aspectBoost: 0,\n reject,\n }\n }\n\n const angles = ordered.map((point, index) =>\n interiorAngle(ordered[(index + 3) % 4], point, ordered[(index + 1) % 4]),\n )\n const edgeLengths = ordered.map((point, index) => distance(point, ordered[(index + 1) % 4]))\n const horizontalScaleRatio = Math.max(edgeLengths[0], edgeLengths[2]) /\n Math.max(1, Math.min(edgeLengths[0], edgeLengths[2]))\n const verticalScaleRatio = Math.max(edgeLengths[1], edgeLengths[3]) /\n Math.max(1, Math.min(edgeLengths[1], edgeLengths[3]))\n if (\n angles.some((angle) => angle < 18 || angle > 162) ||\n horizontalScaleRatio > 4 ||\n verticalScaleRatio > 4\n ) {\n return {\n total: 0,\n areaRatio,\n angleScore: 0,\n oppositeAngleScore: 0,\n parallelScore: 0,\n homographyScore: 0,\n aspectBoost: 0,\n }\n }\n const angleScore = angles.reduce(\n (sum, angle) => sum + Math.exp(-Math.abs(angle - 90) / 48),\n 0,\n ) / 4\n const oppositeAngleScore = (\n Math.exp(-Math.abs(angles[0] + angles[2] - 180) / 55) +\n Math.exp(-Math.abs(angles[1] + angles[3] - 180) / 55)\n ) / 2\n const parallelScore = (\n Math.exp(-directionDifference(ordered[0], ordered[1], ordered[3], ordered[2]) / 0.62) +\n Math.exp(-directionDifference(ordered[0], ordered[3], ordered[1], ordered[2]) / 0.62)\n ) / 2\n const homographyScore = homographyBoundaryScore(corners, hull)\n const areaScore = clamp01((areaRatio - 0.04) / 0.5) *\n (areaRatio > 0.94 ? clamp01((0.995 - areaRatio) / 0.055) : 1)\n const perspectiveScaleScore = Math.exp(\n -Math.max(0, Math.max(horizontalScaleRatio, verticalScaleRatio) - 1.6) / 1.5,\n )\n const frameVisibilityScore = outsideRatio ? Math.exp(-outsideRatio / 0.07) : 1\n const width = Math.max(distance(ordered[0], ordered[1]), distance(ordered[3], ordered[2]))\n const height = Math.max(distance(ordered[0], ordered[3]), distance(ordered[1], ordered[2]))\n const aspect = Math.max(width, height) / Math.max(1, Math.min(width, height))\n const idBoost = Math.exp(-Math.abs(aspect - 1.58) / 0.18)\n const letterBoost = Math.exp(-Math.abs(aspect - 1.29) / 0.18)\n const receiptBoost = aspect >= 2 ? clamp01((aspect - 2) / 2) : 0\n const aspectBoost = Math.max(idBoost, letterBoost, receiptBoost) * 0.08\n const geometricScore = clamp01(\n areaScore * 0.18 +\n angleScore * 0.2 +\n oppositeAngleScore * 0.14 +\n parallelScore * 0.18 +\n homographyScore * 0.3 +\n aspectBoost\n )\n const total = geometricScore * (0.72 + perspectiveScaleScore * 0.28) * frameVisibilityScore\n return {\n total,\n areaRatio,\n angleScore,\n oppositeAngleScore,\n parallelScore,\n homographyScore,\n aspectBoost,\n }\n}\n","import { orderCorners, scorePerspectiveQuad, type QuadCandidate } from './document-geometry'\nimport type { Point } from './types'\n\nexport interface DocumentLetterbox {\n inputSize: number\n contentWidth: number\n contentHeight: number\n offsetX: number\n offsetY: number\n sourceWidth: number\n sourceHeight: number\n /** Always 0: the full analysis bitmap is letterboxed, never center-cropped. */\n cropX: number\n /** Always 0: the full analysis bitmap is letterboxed, never center-cropped. */\n cropY: number\n imageWidth: number\n imageHeight: number\n}\n\nexport interface DocumentModelInput {\n data: Float32Array\n dimensions: [1, 3, number, number]\n letterbox: DocumentLetterbox\n}\n\nexport interface DecodedDocumentHeatmaps {\n candidate?: QuadCandidate\n cornerConfidences: number[]\n /** Why four corners were not returned. Absent when `candidate` is set. */\n rejectReason?: string\n}\n\n/** Empty WebGPU heatmaps are retried on WASM. A found document stays on WebGPU. */\nexport function shouldRetryDocumentModelOnWasm(provider?: string, hasCandidate = false) {\n return provider === 'webgpu' && !hasCandidate\n}\n\nexport function documentModelLetterbox(\n width: number,\n height: number,\n inputSize = 256,\n): DocumentLetterbox {\n const sourceWidth = Math.max(1, width)\n const sourceHeight = Math.max(1, height)\n const scale = Math.min(inputSize / sourceWidth, inputSize / sourceHeight)\n const contentWidth = sourceWidth * scale\n const contentHeight = sourceHeight * scale\n return {\n inputSize,\n contentWidth,\n contentHeight,\n offsetX: (inputSize - contentWidth) / 2,\n offsetY: (inputSize - contentHeight) / 2,\n sourceWidth,\n sourceHeight,\n cropX: 0,\n cropY: 0,\n imageWidth: width,\n imageHeight: height,\n }\n}\n\nfunction letterboxInputPoint(\n point: Point,\n heatmapWidth: number,\n heatmapHeight: number,\n letterbox: DocumentLetterbox,\n) {\n return {\n x: point.x / heatmapWidth * letterbox.inputSize,\n y: point.y / heatmapHeight * letterbox.inputSize,\n }\n}\n\nexport function heatmapPointInLetterbox(\n point: Point,\n heatmapWidth: number,\n heatmapHeight: number,\n letterbox?: DocumentLetterbox,\n margin = 1.5,\n) {\n if (\n !letterbox ||\n letterbox.contentWidth <= 0 ||\n letterbox.contentHeight <= 0 ||\n !heatmapWidth ||\n !heatmapHeight\n ) {\n return true\n }\n const input = letterboxInputPoint(point, heatmapWidth, heatmapHeight, letterbox)\n return input.x >= letterbox.offsetX - margin &&\n input.x <= letterbox.offsetX + letterbox.contentWidth + margin &&\n input.y >= letterbox.offsetY - margin &&\n input.y <= letterbox.offsetY + letterbox.contentHeight + margin\n}\n\nexport function mapHeatmapPointToImage(\n point: Point,\n heatmapWidth: number,\n heatmapHeight: number,\n imageWidth: number,\n imageHeight: number,\n letterbox?: DocumentLetterbox,\n): Point {\n if (\n !letterbox ||\n letterbox.contentWidth <= 0 ||\n letterbox.contentHeight <= 0 ||\n !heatmapWidth ||\n !heatmapHeight\n ) {\n return {\n x: Math.max(0, Math.min(imageWidth - 1, point.x / heatmapWidth * imageWidth)),\n y: Math.max(0, Math.min(imageHeight - 1, point.y / heatmapHeight * imageHeight)),\n }\n }\n const input = letterboxInputPoint(point, heatmapWidth, heatmapHeight, letterbox)\n const mappedX = letterbox.cropX +\n (input.x - letterbox.offsetX) / letterbox.contentWidth * letterbox.sourceWidth\n const mappedY = letterbox.cropY +\n (input.y - letterbox.offsetY) / letterbox.contentHeight * letterbox.sourceHeight\n return {\n x: Math.max(0, Math.min(imageWidth - 1, mappedX)),\n y: Math.max(0, Math.min(imageHeight - 1, mappedY)),\n }\n}\n\nfunction sampleBilinearChannel(\n pixels: Uint8ClampedArray,\n width: number,\n height: number,\n sourceX: number,\n sourceY: number,\n channel: number,\n) {\n const x0 = Math.min(width - 1, Math.max(0, Math.floor(sourceX)))\n const y0 = Math.min(height - 1, Math.max(0, Math.floor(sourceY)))\n const x1 = Math.min(width - 1, x0 + 1)\n const y1 = Math.min(height - 1, y0 + 1)\n const tx = sourceX - x0\n const ty = sourceY - y0\n const i00 = (y0 * width + x0) * 4 + channel\n const i10 = (y0 * width + x1) * 4 + channel\n const i01 = (y1 * width + x0) * 4 + channel\n const i11 = (y1 * width + x1) * 4 + channel\n return (\n pixels[i00] * (1 - tx) * (1 - ty) +\n pixels[i10] * tx * (1 - ty) +\n pixels[i01] * (1 - tx) * ty +\n pixels[i11] * tx * ty\n ) / 255\n}\n\nexport function preprocessDocumentModel(\n pixels: Uint8ClampedArray,\n width: number,\n height: number,\n inputSize = 256,\n): DocumentModelInput {\n const letterbox = documentModelLetterbox(width, height, inputSize)\n const planeSize = inputSize * inputSize\n const data = new Float32Array(planeSize * 3)\n const downsample = letterbox.sourceWidth > letterbox.contentWidth ||\n letterbox.sourceHeight > letterbox.contentHeight\n for (let y = 0; y < inputSize; y += 1) {\n const normalizedY = (y + 0.5 - letterbox.offsetY) / letterbox.contentHeight\n if (normalizedY < 0 || normalizedY > 1) continue\n for (let x = 0; x < inputSize; x += 1) {\n const normalizedX = (x + 0.5 - letterbox.offsetX) / letterbox.contentWidth\n if (normalizedX < 0 || normalizedX > 1) continue\n const targetOffset = y * inputSize + x\n if (downsample) {\n const sourceX = letterbox.cropX + normalizedX * letterbox.sourceWidth - 0.5\n const sourceY = letterbox.cropY + normalizedY * letterbox.sourceHeight - 0.5\n data[targetOffset] = sampleBilinearChannel(pixels, width, height, sourceX, sourceY, 0)\n data[planeSize + targetOffset] = sampleBilinearChannel(pixels, width, height, sourceX, sourceY, 1)\n data[planeSize * 2 + targetOffset] = sampleBilinearChannel(pixels, width, height, sourceX, sourceY, 2)\n continue\n }\n const sourceX = Math.min(width - 1, Math.max(0, Math.round(\n letterbox.cropX + normalizedX * letterbox.sourceWidth - 0.5,\n )))\n const sourceY = Math.min(height - 1, Math.max(0, Math.round(\n letterbox.cropY + normalizedY * letterbox.sourceHeight - 0.5,\n )))\n const sourceOffset = (sourceY * width + sourceX) * 4\n data[targetOffset] = pixels[sourceOffset] / 255\n data[planeSize + targetOffset] = pixels[sourceOffset + 1] / 255\n data[planeSize * 2 + targetOffset] = pixels[sourceOffset + 2] / 255\n }\n }\n return { data, dimensions: [1, 3, inputSize, inputSize], letterbox }\n}\n\nfunction heatmapChannelMax(values: Float32Array, base: number, count: number) {\n let max = Number.NEGATIVE_INFINITY\n for (let index = 0; index < count; index += 1) {\n const value = values[base + index]\n if (value > max) max = value\n }\n return Number.isFinite(max) ? max : 0\n}\n\nfunction largestHeatmapComponent(\n values: Float32Array,\n base: number,\n width: number,\n height: number,\n threshold: number,\n) {\n const visited = new Uint8Array(width * height)\n const queue = new Int32Array(width * height)\n let bestCount = 0\n let bestPoint: Point | undefined\n let bestPeak = 0\n for (let seed = 0; seed < width * height; seed += 1) {\n if (visited[seed] || values[base + seed] < threshold) continue\n let read = 0\n let write = 0\n let weightedX = 0\n let weightedY = 0\n let totalWeight = 0\n let peak = 0\n queue[write++] = seed\n visited[seed] = 1\n while (read < write) {\n const index = queue[read++]\n const x = index % width\n const y = Math.floor(index / width)\n const weight = Math.max(0, values[base + index])\n weightedX += (x + 0.5) * weight\n weightedY += (y + 0.5) * weight\n totalWeight += weight\n peak = Math.max(peak, weight)\n const neighbors = [index - 1, index + 1, index - width, index + width]\n for (const neighbor of neighbors) {\n if (\n neighbor < 0 ||\n neighbor >= width * height ||\n visited[neighbor] ||\n values[base + neighbor] < threshold\n ) continue\n const neighborX = neighbor % width\n if (Math.abs(neighborX - x) > 1) continue\n visited[neighbor] = 1\n queue[write++] = neighbor\n }\n }\n if (write > bestCount && totalWeight > 0) {\n bestCount = write\n bestPeak = peak\n bestPoint = {\n x: weightedX / totalWeight,\n y: weightedY / totalWeight,\n }\n }\n }\n return bestPoint ? { point: bestPoint, peak: bestPeak } : undefined\n}\n\nexport function decodeDocumentHeatmaps(\n heatmaps: Float32Array,\n dimensions: readonly number[],\n imageWidth: number,\n imageHeight: number,\n threshold = 0.3,\n letterbox?: DocumentLetterbox,\n): DecodedDocumentHeatmaps {\n if (\n dimensions.length !== 4 ||\n Number(dimensions[0]) !== 1 ||\n Number(dimensions[1]) !== 4\n ) {\n throw new Error(`Expected document heatmaps shaped [1,4,H,W], got [${dimensions.join(',')}].`)\n }\n const heatmapHeight = Number(dimensions[2])\n const heatmapWidth = Number(dimensions[3])\n const planeSize = heatmapWidth * heatmapHeight\n if (heatmaps.length < planeSize * 4 || !heatmapWidth || !heatmapHeight) {\n throw new Error('Document heatmap tensor is incomplete.')\n }\n const points: Point[] = []\n const cornerConfidences: number[] = []\n for (let channel = 0; channel < 4; channel += 1) {\n const component = largestHeatmapComponent(\n heatmaps,\n channel * planeSize,\n heatmapWidth,\n heatmapHeight,\n threshold,\n )\n if (!component) {\n const max = heatmapChannelMax(heatmaps, channel * planeSize, planeSize)\n return {\n cornerConfidences,\n rejectReason: `no heatmap peak for corner ${channel + 1}/4 above ${threshold} (max ${max.toFixed(3)}) on ${imageWidth}×${imageHeight}`,\n }\n }\n cornerConfidences.push(component.peak)\n if (!heatmapPointInLetterbox(component.point, heatmapWidth, heatmapHeight, letterbox)) {\n return {\n cornerConfidences,\n rejectReason: letterbox\n ? `corner ${channel + 1}/4 peak is in the letterbox pad on ${imageWidth}×${imageHeight} (content ${Math.round(letterbox.contentWidth)}×${Math.round(letterbox.contentHeight)} inside ${letterbox.inputSize}²)`\n : `corner ${channel + 1}/4 peak is outside the frame on ${imageWidth}×${imageHeight}`,\n }\n }\n points.push(mapHeatmapPointToImage(\n component.point,\n heatmapWidth,\n heatmapHeight,\n imageWidth,\n imageHeight,\n letterbox,\n ))\n }\n const corners = orderCorners(points)\n if (!corners) {\n return {\n cornerConfidences,\n rejectReason: `four peaks could not be ordered into corners on ${imageWidth}×${imageHeight}`,\n }\n }\n const geometry = scorePerspectiveQuad(corners, imageWidth, imageHeight)\n if (geometry.total < 0.35) {\n return {\n cornerConfidences,\n rejectReason: geometry.reject ??\n `geometry score ${geometry.total.toFixed(2)} is under 0.35 (area ${geometry.areaRatio.toFixed(3)}) on ${imageWidth}×${imageHeight}`,\n }\n }\n const heatmapConfidence = cornerConfidences.reduce((sum, value) => sum + value, 0) / 4\n return {\n candidate: {\n corners,\n areaRatio: geometry.areaRatio,\n confidence: Math.min(0.99, heatmapConfidence * 0.55 + geometry.total * 0.45),\n },\n cornerConfidences,\n }\n}\n","/**\n * Operational failure from the scanner runtime.\n *\n * Scan quality stays a returned status (`success`, `no-document`,\n * `low-confidence`, or `low-quality`). Redaction match quality stays\n * `success`, `partial`, or `unmatched`. Neither of those outcomes uses this error.\n * A bad image does not throw.\n */\nexport type ScannerErrorCode =\n | 'secure-context'\n | 'worker'\n | 'model-http'\n | 'language'\n | 'canvas'\n\nexport class ScannerError extends Error {\n readonly code: ScannerErrorCode\n\n constructor(code: ScannerErrorCode, message: string) {\n super(message)\n this.name = 'ScannerError'\n this.code = code\n }\n}\n\nexport function canvasUnavailable(): ScannerError {\n return new ScannerError('canvas', 'Canvas 2D is unavailable in this browser.')\n}\n","import { ScannerError } from './errors'\n\nexport interface ModelDownloadProgress {\n loaded: number\n /** Byte length from Content-Length. Null when that header is absent. */\n total: number | null\n done?: boolean\n}\n\nexport interface ModelDownloadSnapshot {\n downloading: boolean\n loaded: number\n /** Sum of Content-Length values when every active transfer has one. */\n total: number | null\n}\n\nconst STORE = Symbol.for('exige.web-scanner-sdk.model-downloads')\n\ninterface ModelDownloadStore {\n active: Map<string, { loaded: number; total: number | null }>\n listeners: Set<(snapshot: ModelDownloadSnapshot) => void>\n cache: Map<string, ArrayBuffer>\n inflight: Map<string, Promise<ArrayBuffer>>\n}\n\n/** One store across the package entry and the React entry. */\nfunction store(): ModelDownloadStore {\n const scope = globalThis as typeof globalThis & { [STORE]?: ModelDownloadStore }\n if (!scope[STORE]) {\n scope[STORE] = {\n active: new Map(),\n listeners: new Set(),\n cache: new Map(),\n inflight: new Map(),\n }\n }\n return scope[STORE]\n}\n\nconst active = store().active\nconst listeners = store().listeners\nconst cache = store().cache\nconst inflight = store().inflight\n\nfunction contentLength(response: Response) {\n const parsed = Number(response.headers.get('content-length'))\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null\n}\n\nexport function modelDownloadSnapshot(): ModelDownloadSnapshot {\n if (active.size === 0) return { downloading: false, loaded: 0, total: null }\n let loaded = 0\n let total = 0\n let known = true\n for (const item of active.values()) {\n loaded += item.loaded\n if (item.total == null) known = false\n else total += item.total\n }\n return { downloading: true, loaded, total: known ? total : null }\n}\n\nfunction emit() {\n const snapshot = modelDownloadSnapshot()\n for (const listener of listeners) listener(snapshot)\n}\n\n/** Subscribing does not fetch. The document worker and `downloadModelBytes` report into the same snapshot. */\nexport function subscribeModelDownloads(listener: (snapshot: ModelDownloadSnapshot) => void) {\n listener(modelDownloadSnapshot())\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\n/** Progress reported from another realm, such as the document worker. */\nexport function reportModelDownload(url: string, progress: ModelDownloadProgress) {\n if (progress.done) active.delete(url)\n else active.set(url, { loaded: progress.loaded, total: progress.total })\n emit()\n}\n\nexport async function readResponseBytes(\n response: Response,\n onProgress?: (progress: ModelDownloadProgress) => void,\n) {\n const total = contentLength(response)\n if (!response.body) {\n const bytes = await response.arrayBuffer()\n onProgress?.({ loaded: bytes.byteLength, total: total ?? bytes.byteLength })\n return bytes\n }\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let loaded = 0\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n if (!value?.byteLength) continue\n chunks.push(value)\n loaded += value.byteLength\n onProgress?.({ loaded, total })\n }\n const bytes = new Uint8Array(loaded)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n if (loaded === 0) onProgress?.({ loaded: 0, total })\n return bytes.buffer\n}\n\n/**\n * Fetch a model file and report loaded/total while the body transfers.\n * A second call for the same URL reuses the cached bytes and does not fetch.\n */\nexport async function downloadModelBytes(url: string, init?: { cache?: RequestCache }) {\n const cached = cache.get(url)\n if (cached) return cached.slice(0)\n const existing = inflight.get(url)\n if (existing) return existing.then((bytes) => bytes.slice(0))\n const request = (async () => {\n reportModelDownload(url, { loaded: 0, total: null })\n const response = await fetch(url, { cache: init?.cache })\n if (!response.ok) {\n throw new ScannerError('model-http', `Model request failed with HTTP ${response.status}.`)\n }\n return readResponseBytes(response, (progress) => reportModelDownload(url, progress))\n })()\n inflight.set(url, request)\n try {\n const bytes = await request\n cache.set(url, bytes)\n return bytes.slice(0)\n } finally {\n inflight.delete(url)\n if (active.has(url)) {\n active.delete(url)\n emit()\n }\n }\n}\n","export interface PackagedOrtWasmPaths {\n wasm: string\n mjs: string\n}\n\n/**\n * Override for the packaged ONNX Runtime files.\n * A string is a directory that contains `ort-wasm-simd-threaded.jsep.wasm`\n * and `ort-wasm-simd-threaded.jsep.mjs`. An object sets each file URL.\n * Omit the option to use the copies shipped with the SDK.\n */\nexport type OrtWasmPaths = string | { wasm: string; mjs: string }\n\n/** Pinned onnxruntime-web version. Do not float `latest`. */\nexport const PINNED_ONNXRUNTIME_WEB_VERSION = '1.30.0' as const\n\nexport const PREFERRED_ORT_CDN_BASE =\n `https://exige-scanner-sdk.vercel.app/ort/${PINNED_ONNXRUNTIME_WEB_VERSION}/`\n\nexport const LAST_RESORT_ORT_CDN_BASE =\n `https://cdn.jsdelivr.net/npm/onnxruntime-web@${PINNED_ONNXRUNTIME_WEB_VERSION}/dist/`\n\nexport const SIBLING_ORT_MODULE = 'onnxruntime-web'\nexport const ORT_JS_FILENAME = 'ort.min.mjs'\nexport const ORT_WASM_FILENAME = 'ort-wasm-simd-threaded.jsep.wasm'\nexport const ORT_WASM_MJS_FILENAME = 'ort-wasm-simd-threaded.jsep.mjs'\n\nexport type OrtModule = typeof import('onnxruntime-web')\nexport type OrtLoadSource = 'sibling' | 'preferred-cdn' | 'last-resort-cdn'\nexport type OrtImporter = (specifier: string) => Promise<OrtModule>\n\nexport function joinOrtCdnUrl(base: string, fileName: string): string {\n const root = base.endsWith('/') ? base : `${base}/`\n return `${root}${fileName}`\n}\n\nexport function siblingOrtUrl(workerHref: string): string {\n return new URL('./ort.min.js', workerHref).href\n}\n\nexport function pinnedOrtJsUrls(): readonly [string, string] {\n return [\n joinOrtCdnUrl(PREFERRED_ORT_CDN_BASE, ORT_JS_FILENAME),\n joinOrtCdnUrl(LAST_RESORT_ORT_CDN_BASE, ORT_JS_FILENAME),\n ]\n}\n\nexport function pinnedOrtWasmPaths(base: string = PREFERRED_ORT_CDN_BASE): PackagedOrtWasmPaths {\n return {\n wasm: joinOrtCdnUrl(base, ORT_WASM_FILENAME),\n mjs: joinOrtCdnUrl(base, ORT_WASM_MJS_FILENAME),\n }\n}\n\nexport function expandOrtWasmPaths(value: OrtWasmPaths): PackagedOrtWasmPaths {\n if (typeof value === 'string') {\n return {\n wasm: joinOrtCdnUrl(value, ORT_WASM_FILENAME),\n mjs: joinOrtCdnUrl(value, ORT_WASM_MJS_FILENAME),\n }\n }\n return { wasm: value.wasm, mjs: value.mjs }\n}\n\nfunction hasOrtWasmValue(value?: OrtWasmPaths): value is OrtWasmPaths {\n if (typeof value === 'string') return value.trim().length > 0\n return Boolean(value?.wasm.trim() && value.mjs.trim())\n}\n\nfunction isSuccessfulAssetResponse(response: Response): boolean {\n if (!response.ok && response.status !== 206) return false\n const type = response.headers.get('content-type') ?? ''\n return !type.toLowerCase().includes('text/html')\n}\n\n/** HEAD, then GET. Treat HTML (SPA fallback) and 404 as missing. */\nexport async function probeHttpUrl(\n url: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<boolean> {\n if (!url) return false\n try {\n const head = await fetchImpl(url, { method: 'HEAD' })\n if (isSuccessfulAssetResponse(head)) return true\n if (head.status === 404 || head.status === 410) return false\n } catch {\n // Some hosts reject HEAD; try GET.\n }\n try {\n const get = await fetchImpl(url, {\n method: 'GET',\n headers: { Range: 'bytes=0-63' },\n })\n return isSuccessfulAssetResponse(get)\n } catch {\n return false\n }\n}\n\nexport async function probeOrtWasmPaths(\n value: OrtWasmPaths,\n fetchImpl: typeof fetch = fetch,\n): Promise<boolean> {\n const { wasm, mjs } = expandOrtWasmPaths(value)\n const [wasmOk, mjsOk] = await Promise.all([\n probeHttpUrl(wasm, fetchImpl),\n probeHttpUrl(mjs, fetchImpl),\n ])\n return wasmOk && mjsOk\n}\n\n/**\n * Prefer same-origin / caller wasm when those URLs exist.\n * A 404 override still falls back to the pinned CDN pair.\n */\nexport async function resolveUsableOrtWasmPaths(\n value?: OrtWasmPaths,\n fetchImpl: typeof fetch = fetch,\n): Promise<OrtWasmPaths> {\n if (hasOrtWasmValue(value) && await probeOrtWasmPaths(value, fetchImpl)) {\n if (typeof value === 'string') return value.trim()\n return { wasm: value.wasm.trim(), mjs: value.mjs.trim() }\n }\n const preferred = pinnedOrtWasmPaths(PREFERRED_ORT_CDN_BASE)\n if (await probeOrtWasmPaths(preferred, fetchImpl)) return preferred\n return pinnedOrtWasmPaths(LAST_RESORT_ORT_CDN_BASE)\n}\n\nexport async function importOrtSpecifier(specifier: string): Promise<OrtModule> {\n return import(specifier) as Promise<OrtModule>\n}\n\nexport async function loadOrtJsFromPinnedCdn(\n importModule: OrtImporter = importOrtSpecifier,\n): Promise<OrtModule> {\n const errors: unknown[] = []\n for (const url of pinnedOrtJsUrls()) {\n try {\n return await importModule(url)\n } catch (error) {\n errors.push(error)\n }\n }\n throw new Error(\n `Failed to load onnxruntime-web@${PINNED_ONNXRUNTIME_WEB_VERSION} from the pinned CDN.`,\n { cause: errors.at(-1) },\n )\n}\n\n/**\n * Sibling `onnxruntime-web` first (webpack copies `./ort.min.js`).\n * Next 15 turbo leaves that sibling missing, so use the pinned CDN.\n */\nexport async function loadOrtRuntime(\n importModule: OrtImporter = importOrtSpecifier,\n): Promise<{ ort: OrtModule; source: OrtLoadSource }> {\n try {\n return { ort: await importModule(SIBLING_ORT_MODULE), source: 'sibling' }\n } catch {\n // Next turbo emits the worker without a sibling ort.min.js.\n }\n const [preferred, lastResort] = pinnedOrtJsUrls()\n try {\n return { ort: await importModule(preferred), source: 'preferred-cdn' }\n } catch {\n return { ort: await importModule(lastResort), source: 'last-resort-cdn' }\n }\n}\n","import {\n decodeDocumentHeatmaps,\n preprocessDocumentModel,\n shouldRetryDocumentModelOnWasm,\n} from './document-ml'\nimport { ScannerError, type ScannerErrorCode } from './errors'\nimport { readResponseBytes, type ModelDownloadProgress } from './model-download'\nimport {\n loadOrtJsFromPinnedCdn,\n resolveUsableOrtWasmPaths,\n} from './ort-cdn'\nimport type { DocumentModelStatus } from './types'\nimport type { QuadCandidate } from './document-geometry'\n\ninterface PixelAnalysis {\n sharpness: number\n candidate?: QuadCandidate\n detector?: 'ml'\n modelStatus?: DocumentModelStatus\n modelProvider?: 'webgpu' | 'wasm'\n modelError?: string\n /** WebGPU attempt that fell back to WASM, or that WebGPU is missing. */\n modelNote?: string\n inferenceMs?: number\n}\n\ninterface AnalysisRequest {\n id: number\n pixels: ArrayBuffer\n width: number\n height: number\n modelUrl?: string\n heatmapThreshold?: number\n cacheModel?: boolean\n ortWasmPaths?: string | { wasm: string; mjs: string }\n /** WebKit may try WebGPU. Android and other Blink engines stay WASM-only. */\n preferWebGpu?: boolean\n}\n\ninterface AnalysisResponse {\n id: number\n result?: PixelAnalysis\n error?: string\n errorCode?: ScannerErrorCode\n download?: {\n url: string\n loaded: number\n total: number | null\n done?: boolean\n }\n}\n\ninterface WorkerScope {\n onmessage: ((event: MessageEvent<AnalysisRequest>) => void) | null\n postMessage: (message: AnalysisResponse) => void\n}\n\nconst workerScope = globalThis as unknown as WorkerScope\nconst modelBytes = new Map<string, Promise<ArrayBuffer>>()\ntype DocumentSession = {\n session: import('onnxruntime-web').InferenceSession\n ort: typeof import('onnxruntime-web')\n provider: 'webgpu' | 'wasm'\n providerNote?: string\n}\n\nconst sessions = new Map<string, Promise<DocumentSession>>()\n/** Model keys whose WebGPU heatmaps produced no document. Later frames stay on WASM. */\nconst wasmOnlyKeys = new Set<string>()\n\nconst MODEL_DATABASE = 'exige-scanner-sdk-models'\nconst MODEL_STORE = 'model-bytes'\n\nfunction openModelDatabase() {\n return new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(MODEL_DATABASE, 1)\n request.onupgradeneeded = () => {\n if (!request.result.objectStoreNames.contains(MODEL_STORE)) {\n request.result.createObjectStore(MODEL_STORE)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n}\n\nasync function readCachedModel(modelUrl: string) {\n if (typeof indexedDB === 'undefined') return undefined\n const database = await openModelDatabase()\n try {\n return await new Promise<ArrayBuffer | undefined>((resolve, reject) => {\n const request = database.transaction(MODEL_STORE).objectStore(MODEL_STORE).get(modelUrl)\n request.onsuccess = () => resolve(\n request.result instanceof ArrayBuffer ? request.result : undefined,\n )\n request.onerror = () => reject(request.error)\n })\n } finally {\n database.close()\n }\n}\n\nasync function writeCachedModel(modelUrl: string, bytes: ArrayBuffer) {\n if (typeof indexedDB === 'undefined') return\n const database = await openModelDatabase()\n try {\n await new Promise<void>((resolve, reject) => {\n const transaction = database.transaction(MODEL_STORE, 'readwrite')\n transaction.objectStore(MODEL_STORE).put(bytes, modelUrl)\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => reject(transaction.error)\n })\n } finally {\n database.close()\n }\n}\n\nasync function fetchModel(\n modelUrl: string,\n cacheModel: boolean,\n onProgress?: (progress: ModelDownloadProgress) => void,\n) {\n let request = modelBytes.get(modelUrl)\n if (!request) {\n request = (async () => {\n if (cacheModel) {\n try {\n const cached = await readCachedModel(modelUrl)\n if (cached) return cached\n } catch {\n // IndexedDB may be unavailable in private/embedded contexts.\n }\n }\n onProgress?.({ loaded: 0, total: null })\n let bytes: ArrayBuffer\n try {\n const response = await fetch(modelUrl, {\n cache: cacheModel ? 'force-cache' : 'no-store',\n })\n if (!response.ok) {\n throw new ScannerError(\n 'model-http',\n `Document model request failed with HTTP ${response.status}.`,\n )\n }\n bytes = await readResponseBytes(response, onProgress)\n } catch (error) {\n onProgress?.({ loaded: 0, total: null, done: true })\n throw error\n }\n onProgress?.({ loaded: bytes.byteLength, total: bytes.byteLength, done: true })\n if (cacheModel) {\n try {\n await writeCachedModel(modelUrl, bytes)\n } catch {\n // HTTP caching and the in-worker promise cache still apply.\n }\n }\n return bytes\n })()\n modelBytes.set(modelUrl, request)\n }\n return request\n}\n\nfunction ortWasmKey(ortWasmPaths?: string | { wasm: string; mjs: string }) {\n if (!ortWasmPaths) return ''\n if (typeof ortWasmPaths === 'string') return ortWasmPaths\n return `${ortWasmPaths.wasm}|${ortWasmPaths.mjs}`\n}\n\nasync function loadOrtModule() {\n try {\n return await import('onnxruntime-web')\n } catch {\n return loadOrtJsFromPinnedCdn()\n }\n}\n\nfunction sessionBaseKey(\n modelUrl: string,\n ortWasmPaths?: string | { wasm: string; mjs: string },\n) {\n return `${modelUrl}|${ortWasmKey(ortWasmPaths)}`\n}\n\nasync function releaseSession(sessionKey: string) {\n const pending = sessions.get(sessionKey)\n sessions.delete(sessionKey)\n if (!pending) return\n try {\n const loaded = await pending\n await loaded.session.release?.()\n } catch {\n // The session is already unusable. The WASM session replaces it.\n }\n}\n\nasync function loadSession(\n modelUrl: string,\n cacheModel: boolean,\n ortWasmPaths?: string | { wasm: string; mjs: string },\n onProgress?: (progress: ModelDownloadProgress) => void,\n wasmOnly = false,\n preferWebGpu = false,\n) {\n const baseKey = sessionBaseKey(modelUrl, ortWasmPaths)\n const forceWasm = wasmOnly || wasmOnlyKeys.has(baseKey) || !preferWebGpu\n const sessionKey = forceWasm ? `${baseKey}|wasm` : baseKey\n let request = sessions.get(sessionKey)\n if (!request) {\n request = (async () => {\n const [ort, bytes] = await Promise.all([\n loadOrtModule(),\n fetchModel(modelUrl, cacheModel, onProgress),\n ])\n ort.env.wasm.wasmPaths = await resolveUsableOrtWasmPaths(ortWasmPaths)\n const hasWebGpu = typeof navigator !== 'undefined' &&\n 'gpu' in (navigator as Navigator & { gpu?: unknown })\n let providerNote: string | undefined\n if (!forceWasm && hasWebGpu) {\n try {\n const session = await ort.InferenceSession.create(bytes, {\n executionProviders: ['webgpu'],\n graphOptimizationLevel: 'all',\n })\n return { session, ort, provider: 'webgpu' as const }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'WebGPU session failed'\n providerNote = `WebGPU session failed (${message}); using WASM`\n }\n } else if (preferWebGpu && !hasWebGpu) {\n providerNote = 'WebGPU is not exposed; using WASM'\n }\n const session = await ort.InferenceSession.create(bytes, {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n })\n return { session, ort, provider: 'wasm' as const, providerNote }\n })()\n sessions.set(sessionKey, request)\n }\n return request\n}\n\nasync function analyzeWithModel(\n request: AnalysisRequest,\n wasmOnly = false,\n): Promise<PixelAnalysis> {\n if (!request.modelUrl) {\n throw new Error('A DocAligner modelUrl is required.')\n }\n const pixels = new Uint8ClampedArray(request.pixels)\n const input = preprocessDocumentModel(pixels, request.width, request.height)\n const { session, ort, provider, providerNote } = await loadSession(\n request.modelUrl,\n request.cacheModel !== false,\n request.ortWasmPaths,\n (progress) => {\n workerScope.postMessage({\n id: request.id,\n download: { url: request.modelUrl ?? '', ...progress },\n })\n },\n wasmOnly,\n request.preferWebGpu === true,\n )\n const startedAt = performance.now()\n const outputs = await session.run({\n [session.inputNames[0]]: new ort.Tensor('float32', input.data, input.dimensions),\n })\n const tensor = outputs[session.outputNames[0]]\n if (!tensor) throw new Error('Document model returned no output tensor.')\n const decoded = decodeDocumentHeatmaps(\n tensor.data as Float32Array,\n tensor.dims.map(Number),\n request.width,\n request.height,\n request.heatmapThreshold ?? 0.3,\n input.letterbox,\n )\n return {\n sharpness: 20,\n candidate: decoded.candidate,\n detector: 'ml',\n modelStatus: 'ready',\n modelProvider: provider,\n modelError: decoded.candidate ? undefined : decoded.rejectReason,\n modelNote: providerNote,\n inferenceMs: performance.now() - startedAt,\n }\n}\n\nasync function analyze(request: AnalysisRequest): Promise<PixelAnalysis> {\n try {\n const ml = await analyzeWithModel(request)\n if (request.modelUrl && shouldRetryDocumentModelOnWasm(ml.modelProvider, Boolean(ml.candidate))) {\n const baseKey = sessionBaseKey(request.modelUrl, request.ortWasmPaths)\n wasmOnlyKeys.add(baseKey)\n await releaseSession(baseKey)\n const wasm = await analyzeWithModel(request, true)\n const modelNote = `WebGPU ran but ${ml.modelError ?? 'returned no corners'}; using WASM`\n if (!wasm.candidate) {\n return {\n ...wasm,\n modelStatus: 'failed',\n modelError: wasm.modelError ?? 'ML detector returned no geometrically valid corners.',\n modelNote,\n }\n }\n return { ...wasm, modelNote }\n }\n if (!ml.candidate) {\n return {\n ...ml,\n modelStatus: 'failed',\n modelError: ml.modelError ?? 'ML detector returned no geometrically valid corners.',\n }\n }\n return ml\n } catch (error) {\n if (error instanceof ScannerError && error.code === 'model-http') throw error\n const message = error instanceof Error ? error.message : 'Document model failed.'\n return {\n sharpness: 20,\n detector: 'ml',\n modelStatus: 'failed',\n modelError: message,\n }\n }\n}\n\nlet analysisQueue = Promise.resolve()\nworkerScope.onmessage = (event) => {\n const request = event.data\n analysisQueue = analysisQueue.then(async () => {\n try {\n workerScope.postMessage({ id: request.id, result: await analyze(request) })\n } catch (error) {\n workerScope.postMessage({\n id: request.id,\n error: error instanceof Error ? error.message : 'Document analysis failed.',\n errorCode: error instanceof ScannerError ? error.code : undefined,\n })\n }\n })\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Ze-Zheng Wu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ zxing-wasm (MIT) wraps ZXing-C++ compiled to WebAssembly.
2
+
3
+ ZXing-C++ is licensed under Apache License 2.0.
4
+ https://github.com/zxing-cpp/zxing-cpp
Binary file
@@ -7,5 +7,6 @@
7
7
  "tesseractCoreLstm": "./assets/tesseract/tesseract-core-lstm.wasm.js",
8
8
  "tesseractCoreSimdLstm": "./assets/tesseract/tesseract-core-simd-lstm.wasm.js",
9
9
  "tesseractCoreRelaxedSimdLstm": "./assets/tesseract/tesseract-core-relaxedsimd-lstm.wasm.js",
10
- "engTraineddata": "./assets/tesseract/eng.traineddata.gz"
10
+ "engTraineddata": "./assets/tesseract/eng.traineddata.gz",
11
+ "zxingReaderWasm": "./assets/zxing_reader.wasm"
11
12
  }