@exige/web-scanner-sdk 0.2.17 → 0.2.23

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
@@ -63,25 +63,28 @@ Every API accepts browser image sources (`Blob`, `HTMLImageElement`, `HTMLCanvas
63
63
 
64
64
  `minConfidence` uses the normalized `0–1` scale and defaults to `0.5`. Words and characters are filtered independently; `text` is rebuilt from retained words and overall `confidence` is their arithmetic mean. When every word is below the threshold, text is empty and confidence is `0`. OCR box extraction is opt-in: `includeWords` and `includeCharacters` default to `false` and can be enabled independently. Returned bounding boxes use pixel coordinates in the exact input canvas passed to `recognizeText`; after scanning, pass `scan.canvas` so boxes map directly to the perspective-corrected crop. `OcrResult` always includes filtered `text`, `confidence`, `language`, `imageWidth`, and `imageHeight`, with filtered `words` and/or `characters` only when requested.
65
65
 
66
- Recognition merges Tesseract Auto on the original crop with Sparse Text on a
67
- bounded Otsu-binarized copy. Boxes are mapped back to original coordinates,
68
- overlap-deduplicated with higher confidence preferred, and spatially reordered.
69
- Standalone punctuation and geometrically or visually implausible boxes are
70
- rejected before the confidence filter, preventing graphic fragments from
71
- becoming text while recovering isolated display words.
66
+ Recognition merges Tesseract Auto on the original color crop with Sparse Text
67
+ on a bounded copy that flattens full-page illumination and then applies a
68
+ local (Bradley–Roth) threshold. A single-page Otsu cut is not used: on a
69
+ soft lighting gradient it paints either the shade or the lit paper as ink.
70
+ Boxes are mapped back to original coordinates, overlap-deduplicated with
71
+ higher confidence preferred, and spatially reordered. Standalone punctuation
72
+ and geometrically or visually implausible boxes are rejected before the
73
+ confidence filter, preventing graphic fragments from becoming text while
74
+ recovering isolated display words.
72
75
 
73
76
  `shadowPass` defaults to `true`. A 12×12 grid finds connected dark paper
74
77
  regions in any direction, including the central or offset blob commonly cast
75
78
  by a phone and hand under an overhead light. Thin rules and isolated artwork
76
- do not meet the area/printed-interior test. When accepted OCR density inside
77
- the blob is low relative to the rest of the page, the SDK estimates local
78
- illumination with a large box blur, divides it out, and lets Tesseract
79
- re-binarize a padded Sparse Text tile (maximum 2× enlargement and 1800px
80
- source dimension). Boxes map back into original crop coordinates before the
81
- normal merge and rejection pipeline. Worst case is one bounded tile
82
- recognition; pages without a qualifying low-yield blob pay only a 72×72
83
- analysis sample. `OcrResult.diagnostics` reports whether the pass ran, its
84
- strength, and the original-pixel shadow region.
79
+ do not meet the area/printed-interior test. The optional tile is gated on
80
+ lighting and token quality inside that blob, not on accepted-word count, so
81
+ a shaded corner full of 92% garbage still gets a flatten re-read. The tile
82
+ is cropped from the same full-page flattened grayscale used for the sparse
83
+ pass. Boxes map back into original crop coordinates before the normal merge
84
+ and rejection pipeline. Worst case is one bounded tile recognition; pages
85
+ without a qualifying shade pay only a 72×72 analysis sample.
86
+ `OcrResult.diagnostics` reports whether the pass ran, its strength, and the
87
+ original-pixel shadow region.
85
88
 
86
89
  ## Programmatic redaction
87
90
 
@@ -170,7 +173,7 @@ Auto-capture modes are exactly `defaultOn`, `defaultOff`, `alwaysOn`, `alwaysOff
170
173
 
171
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.
172
175
 
173
- The worker lazy-loads `onnxruntime-web` (kept external; it is not concatenated into `scanner-sdk.js`), tries WebGPU, then 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 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).
174
177
 
175
178
  ### Full-screen React camera
176
179
 
@@ -212,7 +215,7 @@ await setLicenseKey(licenseKey)
212
215
  </ScannerProvider>
213
216
  ```
214
217
 
215
- `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. It requests an ideal 4K rear-camera stream, except Android Chrome where preview negotiation prefers portrait 1080×1920 and caps only `height.max` at 1920. Pairing the same 1920 max on both axes collapsed to a 1920×1920 square stream that center-cropped tall receipts. Chrome can still grant landscape 1920×1080 and display it upright. iOS reports portrait dimensions for the same pose, so only Android was analyzing a 1280×720 frame. The finder now analyzes the viewfinder cover crop (about 608×1080 on a 9:16 phone) and `mapDocumentCornersToFrame()` maps that quad back onto the raw frame. The preview is unchanged: `object-fit: cover`, no extra rotation. Capture stills stay at the negotiated stream size. The overlay exposes actual track settings, continuously mirrors the last non-black frame, and synchronously copies the unscaled live frame before shutter/flash/track teardown. Capture never calls `ImageCapture.takePhoto()`, which returns black stills on WebKit: the live copy is used when it has visible pixels, the mirrored frame otherwise, and a genuinely blank capture fails loudly instead of exporting black pixels. 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. It requests an ideal 4K rear-camera stream on Android and iOS (no 1080×1920 Android cap), shows that stream in the preview, and continuously mirrors the last non-black frame. Outside WebKit, 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.
216
219
 
217
220
  ## Run the demo
218
221
 
@@ -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);if(!h||g<.04||g>.995||p>.12)return{total:0,areaRatio:g,angleScore:0,oppositeAngleScore:0,parallelScore:0,homographyScore:0,aspectBoost:0};let _=d.map((e,t)=>a(d[(t+3)%4],e,d[(t+1)%4])),v=d.map((e,t)=>i(e,d[(t+1)%4])),y=Math.max(v[0],v[2])/Math.max(1,Math.min(v[0],v[2])),b=Math.max(v[1],v[3])/Math.max(1,Math.min(v[1],v[3]));if(_.some(e=>e<18||e>162)||y>4||b>4)return{total:0,areaRatio:g,angleScore:0,oppositeAngleScore:0,parallelScore:0,homographyScore:0,aspectBoost:0};let x=_.reduce((e,t)=>e+Math.exp(-Math.abs(t-90)/48),0)/4,S=(Math.exp(-Math.abs(_[0]+_[2]-180)/55)+Math.exp(-Math.abs(_[1]+_[3]-180)/55))/2,C=(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,w=l(n,u),T=r((g-.04)/.5)*(g>.94?r((.995-g)/.055):1),E=Math.exp(-Math.max(0,Math.max(y,b)-1.6)/1.5),D=p?Math.exp(-p/.07):1,O=Math.max(i(d[0],d[1]),i(d[3],d[2])),k=Math.max(i(d[0],d[3]),i(d[1],d[2])),A=Math.max(O,k)/Math.max(1,Math.min(O,k)),j=Math.exp(-Math.abs(A-1.58)/.18),M=Math.exp(-Math.abs(A-1.29)/.18),N=A>=2?r((A-2)/2):0,P=Math.max(j,M,N)*.08;return{total:r(T*.18+x*.2+S*.14+C*.18+w*.3+P)*(.72+E*.28)*D,areaRatio:g,angleScore:x,oppositeAngleScore:S,parallelScore:C,homographyScore:w,aspectBoost:P}}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,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 v(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=_(e,t*l,c,s,a);if(!n||(f.push(n.peak),!p(n.point,c,s,o)))return{cornerConfidences:f};d.push(m(n.point,c,s,r,i,o))}let h=n(d);if(!h)return{cornerConfidences:f};let g=u(h,r,i);if(g.total<.35)return{cornerConfidences:f};let v=f.reduce((e,t)=>e+t,0)/4;return{candidate:{corners:h,areaRatio:g.areaRatio,confidence:Math.min(.99,v*.55+g.total*.45)},cornerConfidences:f}}var y=class extends Error{code;constructor(e,t){super(t),this.name=`ScannerError`,this.code=e}};const b=Symbol.for(`exige.web-scanner-sdk.model-downloads`);function x(){let e=globalThis;return e[b]||(e[b]={active:/* @__PURE__ */ new Map,listeners:/* @__PURE__ */ new Set,cache:/* @__PURE__ */ new Map,inflight:/* @__PURE__ */ new Map}),e[b]}x().active,x().listeners,x().cache,x().inflight;function S(e){let t=Number(e.headers.get(`content-length`));return Number.isFinite(t)&&t>0?t:null}async function C(e,t){let n=S(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 w=`1.30.0`,T=`https://exige-scanner-sdk.vercel.app/ort/${w}/`,E=`https://cdn.jsdelivr.net/npm/onnxruntime-web@${w}/dist/`,D=`ort.min.mjs`,O=`ort-wasm-simd-threaded.jsep.wasm`,k=`ort-wasm-simd-threaded.jsep.mjs`;function A(e,t){return`${e.endsWith(`/`)?e:`${e}/`}${t}`}function j(){return[A(T,D),A(E,D)]}function M(e=T){return{wasm:A(e,O),mjs:A(e,k)}}function N(e){return typeof e==`string`?{wasm:A(e,O),mjs:A(e,k)}:{wasm:e.wasm,mjs:e.mjs}}function P(e){return typeof e==`string`?e.trim().length>0:!!(e?.wasm.trim()&&e.mjs.trim())}function F(e){return!e.ok&&e.status!==206?!1:!(e.headers.get(`content-type`)??``).toLowerCase().includes(`text/html`)}async function I(e,t=fetch){if(!e)return!1;try{let n=await t(e,{method:`HEAD`});if(F(n))return!0;if(n.status===404||n.status===410)return!1}catch{}try{return F(await t(e,{method:`GET`,headers:{Range:`bytes=0-63`}}))}catch{return!1}}async function L(e,t=fetch){let{wasm:n,mjs:r}=N(e),[i,a]=await Promise.all([I(n,t),I(r,t)]);return i&&a}async function R(e,t=fetch){if(P(e)&&await L(e,t))return typeof e==`string`?e.trim():{wasm:e.wasm.trim(),mjs:e.mjs.trim()};let n=M(T);return await L(n,t)?n:M(E)}async function z(e){return import(e)}async function B(e=z){let t=[];for(let n of j())try{return await e(n)}catch(e){t.push(e)}throw Error(`Failed to load onnxruntime-web@${w} from the pinned CDN.`,{cause:t.at(-1)})}const V=globalThis,H=/* @__PURE__ */ new Map,U=/* @__PURE__ */ new Map,W=`model-bytes`;function G(){return new Promise((e,t)=>{let n=indexedDB.open(`exige-scanner-sdk-models`,1);n.onupgradeneeded=()=>{n.result.objectStoreNames.contains(W)||n.result.createObjectStore(W)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}async function K(e){if(typeof indexedDB>`u`)return;let t=await G();try{return await new Promise((n,r)=>{let i=t.transaction(W).objectStore(W).get(e);i.onsuccess=()=>n(i.result instanceof ArrayBuffer?i.result:void 0),i.onerror=()=>r(i.error)})}finally{t.close()}}async function q(e,t){if(typeof indexedDB>`u`)return;let n=await G();try{await new Promise((r,i)=>{let a=n.transaction(W,`readwrite`);a.objectStore(W).put(t,e),a.oncomplete=()=>r(),a.onerror=()=>i(a.error)})}finally{n.close()}}async function J(e,t,n){let r=H.get(e);return r||(r=(async()=>{if(t)try{let t=await K(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 y(`model-http`,`Document model request failed with HTTP ${i.status}.`);r=await C(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 q(e,r)}catch{}return r})(),H.set(e,r)),r}function Y(e){return e?typeof e==`string`?e:`${e.wasm}|${e.mjs}`:``}async function X(){try{return await import("./ort.min.js")}catch{return B()}}async function Z(e,t,n,r){let i=`${e}|${Y(n)}`,a=U.get(i);return a||(a=(async()=>{let[i,a]=await Promise.all([X(),J(e,t,r)]);if(i.env.wasm.wasmPaths=await R(n),typeof navigator<`u`&&`gpu`in navigator)try{return{session:await i.InferenceSession.create(a,{executionProviders:[`webgpu`],graphOptimizationLevel:`all`}),ort:i,provider:`webgpu`}}catch{}return{session:await i.InferenceSession.create(a,{executionProviders:[`wasm`],graphOptimizationLevel:`all`}),ort:i,provider:`wasm`}})(),U.set(i,a)),a}async function Q(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 Z(e.modelUrl,e.cacheModel!==!1,e.ortWasmPaths,t=>{V.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.`);return{sharpness:20,candidate:v(o.data,o.dims.map(Number),e.width,e.height,e.heatmapThreshold??.3,t.letterbox).candidate,detector:`ml`,modelStatus:`ready`,modelProvider:i,inferenceMs:performance.now()-a}}async function ee(e){try{let t=await Q(e);return t.candidate?t:{...t,modelStatus:`failed`,modelError:`ML detector returned no geometrically valid corners.`}}catch(e){if(e instanceof y&&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();V.onmessage=e=>{let t=e.data;$=$.then(async()=>{try{V.postMessage({id:t.id,result:await ee(t)})}catch(e){V.postMessage({id:t.id,error:e instanceof Error?e.message:`Document analysis failed.`,errorCode:e instanceof y?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,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 +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}\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 if (\n !convex ||\n areaRatio < 0.04 ||\n areaRatio > 0.995 ||\n outsideRatio > 0.12\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\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}\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 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) return { cornerConfidences }\n cornerConfidences.push(component.peak)\n if (!heatmapPointInLetterbox(component.point, heatmapWidth, heatmapHeight, letterbox)) {\n return { cornerConfidences }\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) return { cornerConfidences }\n const geometry = scorePerspectiveQuad(corners, imageWidth, imageHeight)\n if (geometry.total < 0.35) return { cornerConfidences }\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 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>>()\nconst sessions = new Map<string, Promise<{\n session: import('onnxruntime-web').InferenceSession\n ort: typeof import('onnxruntime-web')\n provider: 'webgpu' | 'wasm'\n}>>()\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 const hasWebGpu = typeof navigator !== 'undefined' &&\n 'gpu' in (navigator as Navigator & { gpu?: unknown })\n if (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 {\n // Fall through to portable WASM.\n }\n }\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 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 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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"}
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":"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"}