@exige/web-scanner-sdk 0.2.28 → 0.2.30
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 +3 -3
- package/dist/assets/zxing-wasm.LICENSE +21 -0
- package/dist/assets/zxing-wasm.NOTICE +4 -0
- package/dist/assets/zxing_reader.wasm +0 -0
- package/dist/assets-manifest.json +2 -1
- package/dist/driver-license-DpjQqOKx.js +2876 -0
- package/dist/driver-license-DpjQqOKx.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/types.d.ts +23 -2
- package/dist/react.d.ts +1 -1
- package/dist/react.js +427 -331
- package/dist/react.js.map +1 -1
- package/dist/scanner-sdk.js +17 -924
- package/dist/scanner-sdk.js.map +1 -1
- package/dist/sdk/aamva.d.ts +58 -0
- package/dist/sdk/barcode.d.ts +12 -0
- package/dist/sdk/driver-license.d.ts +63 -0
- package/dist/sdk/zxing-runtime.d.ts +22 -0
- package/package.json +4 -2
- package/dist/finder-BvBpdbHa.js +0 -1507
- package/dist/finder-BvBpdbHa.js.map +0 -1
package/dist/scanner-sdk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner-sdk.js","names":[],"sources":["../src/sdk/frame-geometry.ts","../src/sdk/finder-simulator.ts","../src/sdk/ocr-runtime.ts","../src/sdk/ocr.ts","../src/sdk/redact.ts","../src/sdk/face-model.ts","../src/sdk/field-model.ts","../src/sdk/programmatic-redaction.ts"],"sourcesContent":["import type { DocumentCorners, Point } from './types'\n\nexport interface FrameSize {\n width: number\n height: number\n}\n\nfunction mapPointFromVisibleSourceToTarget(\n point: Point,\n source: FrameSize,\n target: FrameSize,\n): Point {\n const sourceWidth = Math.max(1, source.width)\n const sourceHeight = Math.max(1, source.height)\n const targetWidth = Math.max(1, target.width)\n const targetHeight = Math.max(1, target.height)\n // The analysis frame is the center-cropped view of a differently-shaped\n // still. Invert object-fit: cover so source pixels map into the full still.\n const scale = Math.max(sourceWidth / targetWidth, sourceHeight / targetHeight)\n const offsetX = (sourceWidth - targetWidth * scale) / 2\n const offsetY = (sourceHeight - targetHeight * scale) / 2\n return {\n x: (point.x - offsetX) / scale,\n y: (point.y - offsetY) / scale,\n }\n}\n\nexport function mapDocumentCornersToFrame(\n corners: DocumentCorners,\n source: FrameSize,\n target: FrameSize,\n): DocumentCorners {\n return {\n topLeft: mapPointFromVisibleSourceToTarget(corners.topLeft, source, target),\n topRight: mapPointFromVisibleSourceToTarget(corners.topRight, source, target),\n bottomRight: mapPointFromVisibleSourceToTarget(corners.bottomRight, source, target),\n bottomLeft: mapPointFromVisibleSourceToTarget(corners.bottomLeft, source, target),\n }\n}\n","import { canvasUnavailable } from './errors'\nimport type { DocumentQuad, Point } from './types'\n\nexport interface SimulatorPose {\n centerX: number\n centerY: number\n rotation: number\n width: number\n height: number\n phase: 'entering' | 'jittering' | 'settled'\n}\n\nexport interface FinderSimulator {\n start: () => void\n stop: () => void\n reset: () => void\n}\n\nexport function simulatorPose(elapsedMs: number, canvasWidth = 960, canvasHeight = 600): SimulatorPose {\n // Settle comfortably above the finder's default 72% limiting-axis fill target so\n // the simulator demonstrates Move closer before becoming eligible for auto-capture.\n const width = canvasWidth * 0.86\n const height = canvasHeight * 0.86\n if (elapsedMs < 1200) {\n const progress = elapsedMs / 1200\n return {\n centerX: -width / 2 + (canvasWidth / 2 + width / 2) * (1 - Math.pow(1 - progress, 3)),\n centerY: canvasHeight / 2,\n rotation: -0.14 + progress * 0.08,\n width,\n height,\n phase: 'entering',\n }\n }\n if (elapsedMs < 3600) {\n const progress = (elapsedMs - 1200) / 2400\n const amplitude = (1 - progress) * 18\n return {\n centerX: canvasWidth / 2 + Math.sin(elapsedMs / 85) * amplitude,\n centerY: canvasHeight / 2 + Math.cos(elapsedMs / 110) * amplitude * 0.55,\n rotation: -0.055 + Math.sin(elapsedMs / 125) * (1 - progress) * 0.045,\n width,\n height,\n phase: 'jittering',\n }\n }\n return {\n centerX: canvasWidth / 2,\n centerY: canvasHeight / 2,\n rotation: 0,\n width,\n height,\n phase: 'settled',\n }\n}\n\nfunction rotatePoint(x: number, y: number, pose: SimulatorPose): Point {\n const cosine = Math.cos(pose.rotation)\n const sine = Math.sin(pose.rotation)\n return {\n x: pose.centerX + x * cosine - y * sine,\n y: pose.centerY + x * sine + y * cosine,\n }\n}\n\nexport function simulatorQuad(pose: SimulatorPose): DocumentQuad {\n const topLeft = rotatePoint(-pose.width / 2, -pose.height / 2, pose)\n const topRight = rotatePoint(pose.width / 2, -pose.height / 2, pose)\n const bottomRight = rotatePoint(pose.width / 2, pose.height / 2, pose)\n const bottomLeft = rotatePoint(-pose.width / 2, pose.height / 2, pose)\n return {\n topLeft,\n topRight,\n bottomRight,\n bottomLeft,\n edges: {\n top: { start: topLeft, end: topRight },\n right: { start: topRight, end: bottomRight },\n bottom: { start: bottomRight, end: bottomLeft },\n left: { start: bottomLeft, end: topLeft },\n },\n }\n}\n\nfunction drawFrame(canvas: HTMLCanvasElement, pose: SimulatorPose) {\n const context = canvas.getContext('2d')\n if (!context) throw canvasUnavailable()\n const background = context.createLinearGradient(0, 0, canvas.width, canvas.height)\n background.addColorStop(0, '#16231c')\n background.addColorStop(1, '#32443a')\n context.fillStyle = background\n context.fillRect(0, 0, canvas.width, canvas.height)\n context.save()\n context.translate(pose.centerX, pose.centerY)\n context.rotate(pose.rotation)\n context.shadowColor = 'rgba(0,0,0,.45)'\n context.shadowBlur = 24\n context.fillStyle = '#fffdf4'\n context.strokeStyle = '#111914'\n context.lineWidth = 7\n context.fillRect(-pose.width / 2, -pose.height / 2, pose.width, pose.height)\n context.strokeRect(-pose.width / 2, -pose.height / 2, pose.width, pose.height)\n context.shadowColor = 'transparent'\n const left = -pose.width / 2\n const top = -pose.height / 2\n context.fillStyle = '#d7ded8'\n context.fillRect(left + 38, top + 84, 145, 190)\n context.fillStyle = '#617269'\n context.beginPath()\n context.arc(left + 110, top + 140, 38, 0, Math.PI * 2)\n context.fill()\n context.fillRect(left + 70, top + 185, 80, 65)\n context.fillStyle = '#17231c'\n context.font = '700 25px Arial'\n context.fillText('IDENTITY CARD', left + 38, top + 48)\n context.font = '700 31px Arial'\n context.fillText('TAYLOR MORGAN', left + 220, top + 118)\n context.font = '23px Arial'\n context.fillText('01/02/1990', left + 220, top + 166)\n context.fillText('A1234567', left + 220, top + 208)\n context.fillText('123 MAIN ST', left + 220, top + 270)\n context.fillText('AUSTIN TX 78701', left + 220, top + 307)\n context.restore()\n}\n\nexport function createFinderSimulator(canvas: HTMLCanvasElement): FinderSimulator {\n if (!canvas.width) canvas.width = 960\n if (!canvas.height) canvas.height = 600\n let running = false\n let startedAt = 0\n let animationFrame = 0\n const render = (time: number) => {\n if (!running) return\n if (!startedAt) startedAt = time\n drawFrame(canvas, simulatorPose(time - startedAt, canvas.width, canvas.height))\n animationFrame = requestAnimationFrame(render)\n }\n const reset = () => {\n startedAt = 0\n drawFrame(canvas, simulatorPose(0, canvas.width, canvas.height))\n }\n reset()\n return {\n start() {\n if (running) return\n running = true\n animationFrame = requestAnimationFrame(render)\n },\n stop() {\n running = false\n cancelAnimationFrame(animationFrame)\n },\n reset,\n }\n}\n","import { ScannerError } from './errors'\n\n/**\n * Browser files copied from tesseract.js 7, tesseract.js-core 7.0.0 LSTM builds,\n * and @tesseract.js-data/eng 4.0.0_best_int. Literal `new URL` keeps them visible\n * to Webpack and Turbopack.\n */\nexport const tesseractWorkerUrl = new URL("./assets/tesseract/worker.min.js", import.meta.url)\nexport const tesseractCoreLstmUrl = new URL("./assets/tesseract/tesseract-core-lstm.wasm.js", import.meta.url)\nexport const tesseractCoreSimdLstmUrl = new URL("./assets/tesseract/tesseract-core-simd-lstm.wasm.js", import.meta.url)\nexport const tesseractCoreRelaxedSimdLstmUrl = new URL(\n './runtime/tesseract-core-relaxedsimd-lstm.wasm.js',\n import.meta.url,\n)\nexport const bundledEnglishTraineddataUrl = new URL("./assets/tesseract/eng.traineddata.gz", import.meta.url)\n\nconst RELAXED_SIMD = Uint8Array.of(\n 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 15, 1, 13, 0,\n 65, 1, 253, 15, 65, 2, 253, 15, 253, 128, 2, 11,\n)\nconst SIMD = Uint8Array.of(\n 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0,\n 65, 0, 253, 15, 253, 98, 11,\n)\n\nexport interface LstmCoreSupport {\n relaxedSimd: boolean\n simd: boolean\n}\n\nfunction wasmValid(bytes: Uint8Array) {\n try {\n return typeof WebAssembly !== 'undefined' &&\n WebAssembly.validate(bytes as unknown as BufferSource)\n } catch {\n return false\n }\n}\n\n/** Same relaxed-SIMD then SIMD order as tesseract.js `getCore` for an LSTM core. */\nexport function detectLstmCoreSupport(): LstmCoreSupport {\n return {\n relaxedSimd: wasmValid(RELAXED_SIMD),\n simd: wasmValid(SIMD),\n }\n}\n\nexport function selectLstmCoreUrl(support: LstmCoreSupport) {\n if (support.relaxedSimd) return tesseractCoreRelaxedSimdLstmUrl\n if (support.simd) return tesseractCoreSimdLstmUrl\n return tesseractCoreLstmUrl\n}\n\nexport type OcrLanguagePlan =\n | { kind: 'bundled-eng'; language: 'eng' }\n | { kind: 'lang-path'; language: string; langPath: string }\n\nexport function resolveOcrLanguage(language?: string, langPath?: string): OcrLanguagePlan {\n const resolved = language?.trim() || 'eng'\n const override = langPath?.trim() || ''\n if (override) return { kind: 'lang-path', language: resolved, langPath: override }\n if (resolved === 'eng') return { kind: 'bundled-eng', language: 'eng' }\n throw new ScannerError(\n 'language',\n `OCR language \"${resolved}\" is not packaged. Pass langPath, a directory URL that contains ${resolved}.traineddata.gz.`,\n )\n}\n\n/** Directory that contains `eng.traineddata.gz` next to the packaged worker and cores. */\nexport function bundledEnglishLangPath(traineddataUrl = bundledEnglishTraineddataUrl) {\n const href = traineddataUrl.href\n const slash = href.lastIndexOf('/')\n return slash === -1 ? href : href.slice(0, slash)\n}\n\nexport function tesseractLanguagePath(plan: OcrLanguagePlan) {\n return plan.kind === 'bundled-eng' ? bundledEnglishLangPath() : plan.langPath\n}\n\nexport async function loadBundledEnglishTraineddata() {\n const response = await fetch(bundledEnglishTraineddataUrl)\n if (!response.ok) {\n throw new ScannerError(\n 'model-http',\n `Packaged English OCR data failed to load (HTTP ${response.status}).`,\n )\n }\n return new Uint8Array(await response.arrayBuffer())\n}\n\n/**\n * tesseract.js 7 `createWorker` swallows initialize failures (`.catch(() => {})`)\n * unless `errorHandler` rejects. Passing `{ code, data }` is also unsafe: initialize\n * uses `Lang.data` as the language name, so a Uint8Array becomes\n * `\"24,0,0,0,…\"`. Always pass a language string and `langPath`.\n */\nexport async function createTesseractWorker<T>(\n createWorker: (\n langs: string,\n oem: undefined,\n options: {\n workerPath: string\n corePath: string\n langPath: string\n gzip: boolean\n logger?: (message: { progress?: number; status?: string }) => void\n errorHandler?: (error: unknown) => void\n },\n ) => Promise<T>,\n language: string,\n options: {\n workerPath: string\n corePath: string\n langPath: string\n logger?: (message: { progress?: number; status?: string }) => void\n },\n): Promise<T> {\n return new Promise((resolve, reject) => {\n let settled = false\n const fail = (error: unknown) => {\n if (settled) return\n settled = true\n const message = error instanceof Error ? error.message : String(error)\n reject(new ScannerError('worker', `OCR worker failed to start (${message}).`))\n }\n void createWorker(language, undefined, {\n workerPath: options.workerPath,\n corePath: options.corePath,\n langPath: options.langPath,\n gzip: true,\n logger: options.logger,\n errorHandler: fail,\n }).then((worker) => {\n if (settled) {\n const close = worker as { terminate?: () => unknown }\n void close.terminate?.()\n return\n }\n settled = true\n resolve(worker)\n }, fail)\n })\n}\n","import { canvasUnavailable } from './errors'\nimport { sourceToCanvas } from './image'\nimport { assertLicense } from './license'\nimport {\n createTesseractWorker,\n detectLstmCoreSupport,\n resolveOcrLanguage,\n selectLstmCoreUrl,\n tesseractLanguagePath,\n tesseractWorkerUrl,\n} from './ocr-runtime'\nimport {\n adaptiveThresholdLuminance,\n analyzeCastShadowLuminance,\n flattenIlluminationLuminance,\n type CastShadowAnalysis,\n type CastShadowRegion,\n} from './lighting'\nimport type { ImageSource, OcrBox, OcrResult } from './types'\n\nexport interface OcrOptions {\n /**\n * Tesseract language code. Defaults to `eng`, which loads the packaged\n * English LSTM traineddata. Any other code needs `langPath`.\n */\n language?: string\n /**\n * Directory URL that contains `${language}.traineddata.gz`.\n * Required when `language` is not `eng`. For `eng`, set this only to\n * replace the packaged English model.\n */\n langPath?: string\n /**\n * Minimum normalized confidence accepted for OCR tokens, from 0 to 1.\n * Defaults to 0.5. Words and characters are filtered independently.\n * A value outside 0–1 throws `RangeError`.\n */\n minConfidence?: number\n /**\n * Re-reads one phone/hand cast-shadow tile after full-page\n * illumination flattening when that region's lighting or token\n * quality is poor. Defaults to true. Does not use accepted-word\n * count as the skip condition.\n */\n shadowPass?: boolean\n /**\n * Include word boxes on the result. Defaults to false.\n * Redaction that matches words needs `true`.\n */\n includeWords?: boolean\n /**\n * Include character boxes on the result. Defaults to false.\n */\n includeCharacters?: boolean\n onProgress?: (progress: number, status: string) => void\n}\n\ninterface TesseractBbox {\n x0: number\n y0: number\n x1: number\n y1: number\n}\n\ninterface TesseractSymbol {\n text: string\n confidence: number\n bbox: TesseractBbox\n}\n\ninterface TesseractWord {\n text: string\n confidence: number\n bbox: TesseractBbox\n symbols: TesseractSymbol[]\n}\n\ninterface TesseractBlock {\n paragraphs: Array<{\n lines: Array<{\n words: TesseractWord[]\n }>\n }>\n}\n\nexport interface OcrPass {\n blocks: TesseractBlock[] | null\n /** Maps pass coordinates back into the original input image. */\n scaleX?: number\n scaleY?: number\n offsetX?: number\n offsetY?: number\n}\n\nfunction toBox(\n item: { text: string; confidence: number; bbox: TesseractBbox },\n scaleX = 1,\n scaleY = 1,\n offsetX = 0,\n offsetY = 0,\n): OcrBox {\n return {\n text: item.text,\n confidence: Math.max(0, Math.min(1, item.confidence / 100)),\n bbox: {\n x: item.bbox.x0 * scaleX + offsetX,\n y: item.bbox.y0 * scaleY + offsetY,\n width: Math.max(0, item.bbox.x1 - item.bbox.x0) * scaleX,\n height: Math.max(0, item.bbox.y1 - item.bbox.y0) * scaleY,\n },\n }\n}\n\nfunction resolveMinConfidence(value = 0.5) {\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n throw new RangeError('OCR minConfidence must be between 0 and 1.')\n }\n return value\n}\n\nfunction overlapOverSmaller(first: OcrBox, second: OcrBox) {\n const left = Math.max(first.bbox.x, second.bbox.x)\n const top = Math.max(first.bbox.y, second.bbox.y)\n const right = Math.min(\n first.bbox.x + first.bbox.width,\n second.bbox.x + second.bbox.width,\n )\n const bottom = Math.min(\n first.bbox.y + first.bbox.height,\n second.bbox.y + second.bbox.height,\n )\n const intersection = Math.max(0, right - left) * Math.max(0, bottom - top)\n const smaller = Math.min(\n first.bbox.width * first.bbox.height,\n second.bbox.width * second.bbox.height,\n )\n return smaller > 0 ? intersection / smaller : 0\n}\n\nfunction dedupeBoxes(boxes: OcrBox[]) {\n const ranked = [...boxes].sort((first, second) =>\n second.confidence - first.confidence ||\n first.bbox.y - second.bbox.y ||\n first.bbox.x - second.bbox.x ||\n first.text.localeCompare(second.text),\n )\n const retained: OcrBox[] = []\n for (const candidate of ranked) {\n if (!retained.some((existing) => overlapOverSmaller(existing, candidate) >= 0.65)) {\n retained.push(candidate)\n }\n }\n return retained\n}\n\nfunction isLikelyText(\n item: OcrBox,\n kind: 'word' | 'character',\n imageWidth: number,\n imageHeight: number,\n inkDensity?: (box: OcrBox['bbox']) => number,\n) {\n const text = item.text.trim()\n if (!text || !/[\\p{L}\\p{N}]/u.test(text)) return false\n const area = item.bbox.width * item.bbox.height\n const areaRatio = area / Math.max(1, imageWidth * imageHeight)\n const aspect = item.bbox.width / Math.max(1, item.bbox.height)\n const minimumArea = kind === 'word' ? 0.000015 : 0.0000015\n const maximumAspect = kind === 'word' ? 50 : 8\n if (areaRatio < minimumArea || aspect < 0.05 || aspect > maximumAspect) return false\n if (inkDensity) {\n const density = inkDensity(item.bbox)\n if (density < 0.01 || density > 0.90) return false\n }\n return true\n}\n\nfunction rebuildText(words: OcrBox[]) {\n const sorted = [...words].sort((first, second) =>\n first.bbox.y + first.bbox.height / 2 - (second.bbox.y + second.bbox.height / 2) ||\n first.bbox.x - second.bbox.x,\n )\n const lines: Array<{ centerY: number; height: number; words: OcrBox[] }> = []\n for (const word of sorted) {\n const centerY = word.bbox.y + word.bbox.height / 2\n const line = lines.find((candidate) =>\n Math.abs(candidate.centerY - centerY) <=\n Math.max(candidate.height, word.bbox.height) * 0.6,\n )\n if (line) {\n line.words.push(word)\n const count = line.words.length\n line.centerY = (line.centerY * (count - 1) + centerY) / count\n line.height = Math.max(line.height, word.bbox.height)\n } else {\n lines.push({ centerY, height: word.bbox.height, words: [word] })\n }\n }\n return lines\n .sort((first, second) => first.centerY - second.centerY)\n .map((line) => line.words\n .sort((first, second) => first.bbox.x - second.bbox.x)\n .map((word) => word.text.trim())\n .join(' '))\n .join('\\n')\n}\n\nexport function mergeOcrPasses(\n passes: OcrPass[],\n options: Pick<OcrOptions, 'minConfidence' | 'includeWords' | 'includeCharacters'>,\n imageSize?: { width: number; height: number },\n inkDensity?: (box: OcrBox['bbox']) => number,\n) {\n const minConfidence = resolveMinConfidence(options.minConfidence)\n const allWords: OcrBox[] = []\n const allCharacters: OcrBox[] = []\n let inferredWidth = 1\n let inferredHeight = 1\n\n for (const pass of passes) {\n const scaleX = pass.scaleX ?? 1\n const scaleY = pass.scaleY ?? 1\n const offsetX = pass.offsetX ?? 0\n const offsetY = pass.offsetY ?? 0\n for (const block of pass.blocks ?? []) {\n for (const paragraph of block.paragraphs) {\n for (const line of paragraph.lines) {\n for (const word of line.words) {\n const wordBox = toBox(word, scaleX, scaleY, offsetX, offsetY)\n inferredWidth = Math.max(inferredWidth, wordBox.bbox.x + wordBox.bbox.width)\n inferredHeight = Math.max(inferredHeight, wordBox.bbox.y + wordBox.bbox.height)\n if (wordBox.confidence >= minConfidence) allWords.push(wordBox)\n for (const symbol of word.symbols) {\n const character = toBox(symbol, scaleX, scaleY, offsetX, offsetY)\n if (character.confidence >= minConfidence) allCharacters.push(character)\n }\n }\n }\n }\n }\n }\n\n const width = imageSize?.width ?? inferredWidth\n const height = imageSize?.height ?? inferredHeight\n const words = dedupeBoxes(allWords)\n .filter((word) => isLikelyText(word, 'word', width, height, inkDensity))\n const characters = dedupeBoxes(allCharacters)\n .filter((character) => isLikelyText(character, 'character', width, height, inkDensity))\n const text = rebuildText(words)\n\n return {\n text,\n confidence: words.length\n ? words.reduce((total, word) => total + word.confidence, 0) / words.length\n : 0,\n words: options.includeWords ? words : [],\n characters: options.includeCharacters ? characters : [],\n }\n}\n\nexport function extractOcrLayout(\n blocks: TesseractBlock[] | null,\n options: Pick<OcrOptions, 'minConfidence' | 'includeWords' | 'includeCharacters'>,\n) {\n return mergeOcrPasses([{ blocks }], options)\n}\n\nexport function extractOcrBoxes(\n blocks: TesseractBlock[] | null,\n options: Pick<OcrOptions, 'minConfidence' | 'includeWords' | 'includeCharacters'>,\n) {\n const { words, characters } = extractOcrLayout(blocks, options)\n return { words, characters }\n}\n\nfunction canvasLuminance(canvas: HTMLCanvasElement) {\n const context = canvas.getContext('2d', { willReadFrequently: true })\n if (!context) throw canvasUnavailable()\n const image = context.getImageData(0, 0, canvas.width, canvas.height)\n const luminance = new Uint8Array(canvas.width * canvas.height)\n for (let pixel = 0, offset = 0; pixel < luminance.length; pixel += 1, offset += 4) {\n luminance[pixel] = Math.round(\n image.data[offset] * 0.299 +\n image.data[offset + 1] * 0.587 +\n image.data[offset + 2] * 0.114,\n )\n }\n return { context, image, luminance }\n}\n\nfunction sampleCastShadow(source: HTMLCanvasElement) {\n const sample = document.createElement('canvas')\n sample.width = 72\n sample.height = 72\n const context = sample.getContext('2d', { willReadFrequently: true })\n if (!context) return { strong: false, strength: 0, darkness: 0, areaRatio: 0 }\n context.drawImage(source, 0, 0, sample.width, sample.height)\n const { luminance } = canvasLuminance(sample)\n return analyzeCastShadowLuminance(luminance, sample.width, sample.height)\n}\n\nfunction wordCenterInRegion(\n word: OcrBox,\n region: CastShadowRegion,\n width: number,\n height: number,\n) {\n const x = (word.bbox.x + word.bbox.width / 2) / width\n const y = (word.bbox.y + word.bbox.height / 2) / height\n return (\n x >= region.x &&\n x <= region.x + region.width &&\n y >= region.y &&\n y <= region.y + region.height\n )\n}\n\nconst SHADOW_REGION_QUALITY_MIN = 0.58\n\nexport function shadowTokenQuality(text: string) {\n const token = text.trim()\n if (!token || !/[\\p{L}\\p{N}]/u.test(token)) return 0\n if (/^\\$?\\d{1,5}([.,]\\d{2})?$/.test(token)) return 1\n const letters = token.replace(/[^\\p{L}]/gu, '')\n if (letters.length >= 3 && /[aeiouy]/i.test(letters)) return 0.9\n if (letters.length >= 4) return 0.7\n if (token.length <= 2) return 0.35\n if (/[^\\p{L}\\p{N}.,'’$/-]/u.test(token)) return 0.25\n return 0.5\n}\n\nexport function shadowRegionQuality(\n analysis: CastShadowAnalysis,\n words: OcrBox[],\n inkDensity?: (box: OcrBox['bbox']) => number,\n) {\n const lighting = Math.max(\n 0,\n Math.min(1, (1 - analysis.darkness / 80) * (1 - analysis.strength / 24)),\n )\n if (!words.length) return lighting * 0.35\n let total = 0\n for (const word of words) {\n let score = shadowTokenQuality(word.text)\n if (inkDensity) {\n const ink = inkDensity(word.bbox)\n if (ink < 0.04 || ink > 0.62) score *= 0.4\n }\n total += score\n }\n // Lighting dominates. Many accepted words cannot clear a deep shade —\n // that is how 92% garbage skipped the flatten tile.\n return lighting * 0.7 + (total / words.length) * 0.3\n}\n\nexport function shadowRegionNeedsOcr(\n analysis: CastShadowAnalysis,\n words: OcrBox[],\n imageSize: { width: number; height: number },\n inkDensity?: (box: OcrBox['bbox']) => number,\n) {\n if (!analysis.strong || !analysis.region) return false\n const inside = words.filter((word) =>\n wordCenterInRegion(word, analysis.region!, imageSize.width, imageSize.height)\n )\n return shadowRegionQuality(analysis, inside, inkDensity) < SHADOW_REGION_QUALITY_MIN\n}\n\nfunction cropShadowTile(\n adaptive: HTMLCanvasElement,\n region: CastShadowRegion,\n originalWidth: number,\n originalHeight: number,\n) {\n const left = Math.max(0, Math.floor(region.x * adaptive.width))\n const top = Math.max(0, Math.floor(region.y * adaptive.height))\n const right = Math.min(\n adaptive.width,\n Math.ceil((region.x + region.width) * adaptive.width),\n )\n const bottom = Math.min(\n adaptive.height,\n Math.ceil((region.y + region.height) * adaptive.height),\n )\n const tile = document.createElement('canvas')\n tile.width = Math.max(1, right - left)\n tile.height = Math.max(1, bottom - top)\n tile.getContext('2d')?.drawImage(\n adaptive,\n left,\n top,\n tile.width,\n tile.height,\n 0,\n 0,\n tile.width,\n tile.height,\n )\n const scaleX = originalWidth / adaptive.width\n const scaleY = originalHeight / adaptive.height\n return {\n tile,\n passMapping: {\n scaleX,\n scaleY,\n offsetX: left * scaleX,\n offsetY: top * scaleY,\n },\n }\n}\n\nfunction writeLuminance(\n image: ImageData,\n values: Uint8Array,\n) {\n for (let pixel = 0, offset = 0; pixel < values.length; pixel += 1, offset += 4) {\n image.data[offset] = values[pixel]\n image.data[offset + 1] = values[pixel]\n image.data[offset + 2] = values[pixel]\n image.data[offset + 3] = 255\n }\n}\n\nfunction prepareOcrCanvases(source: HTMLCanvasElement) {\n const maxDimension = 2200\n const sourceMax = Math.max(source.width, source.height)\n const scale = sourceMax > maxDimension\n ? maxDimension / sourceMax\n : Math.min(1.5, maxDimension / Math.max(1, sourceMax))\n const sparseCanvas = document.createElement('canvas')\n sparseCanvas.width = Math.max(1, Math.round(source.width * scale))\n sparseCanvas.height = Math.max(1, Math.round(source.height * scale))\n const context = sparseCanvas.getContext('2d', { willReadFrequently: true })\n if (!context) throw canvasUnavailable()\n context.drawImage(source, 0, 0, sparseCanvas.width, sparseCanvas.height)\n const { image, luminance } = canvasLuminance(sparseCanvas)\n const flattened = flattenIlluminationLuminance(\n luminance,\n sparseCanvas.width,\n sparseCanvas.height,\n )\n const binary = adaptiveThresholdLuminance(\n flattened,\n sparseCanvas.width,\n sparseCanvas.height,\n )\n const flattenedCanvas = document.createElement('canvas')\n flattenedCanvas.width = sparseCanvas.width\n flattenedCanvas.height = sparseCanvas.height\n const flattenedContext = flattenedCanvas.getContext('2d', { willReadFrequently: true })\n if (!flattenedContext) throw canvasUnavailable()\n const flattenedImage = flattenedContext.createImageData(\n flattenedCanvas.width,\n flattenedCanvas.height,\n )\n writeLuminance(flattenedImage, flattened)\n flattenedContext.putImageData(flattenedImage, 0, 0)\n writeLuminance(image, binary)\n context.putImageData(image, 0, 0)\n return { sparseCanvas, flattenedCanvas }\n}\n\nfunction createInkDensitySampler(source: HTMLCanvasElement) {\n const sample = document.createElement('canvas')\n sample.width = 24\n sample.height = 24\n const context = sample.getContext('2d', { willReadFrequently: true })\n return (box: OcrBox['bbox']) => {\n if (!context || box.width <= 0 || box.height <= 0) return 0\n context.clearRect(0, 0, sample.width, sample.height)\n context.drawImage(\n source,\n box.x,\n box.y,\n box.width,\n box.height,\n 0,\n 0,\n sample.width,\n sample.height,\n )\n const pixels = context.getImageData(0, 0, sample.width, sample.height).data\n let minimum = 255\n let maximum = 0\n const values = new Uint8Array(sample.width * sample.height)\n for (let pixel = 0, offset = 0; pixel < values.length; pixel += 1, offset += 4) {\n const value = Math.round(\n pixels[offset] * 0.299 +\n pixels[offset + 1] * 0.587 +\n pixels[offset + 2] * 0.114,\n )\n values[pixel] = value\n minimum = Math.min(minimum, value)\n maximum = Math.max(maximum, value)\n }\n if (maximum - minimum < 12) return 0\n const threshold = minimum + (maximum - minimum) * 0.55\n let ink = 0\n for (const value of values) if (value < threshold) ink += 1\n return ink / values.length\n }\n}\n\n/**\n * Reads text from a browser image. English uses the packaged traineddata.\n * `includeWords` and `includeCharacters` default to false.\n * `minConfidence` defaults to 0.5.\n *\n * @throws {LicenseError} LicenseError when the OCR feature is not licensed. Recognition does not start.\n * @throws {RangeError} RangeError when `minConfidence` is outside 0–1.\n * @throws {ScannerError} ScannerError `language` when `language` is not `eng` and `langPath` is omitted.\n * `canvas` when canvas 2D is unavailable.\n * `worker` when the Tesseract worker fails to start or language data cannot load.\n */\nexport async function recognizeText(\n source: ImageSource,\n options: OcrOptions = {},\n): Promise<OcrResult> {\n await assertLicense('ocr')\n const languagePlan = resolveOcrLanguage(options.language, options.langPath)\n const language = languagePlan.language\n resolveMinConfidence(options.minConfidence)\n const canvas = await sourceToCanvas(source, { cloneCanvas: false })\n const { sparseCanvas, flattenedCanvas } = prepareOcrCanvases(canvas)\n const shadow: CastShadowAnalysis = options.shadowPass === false\n ? { strong: false, strength: 0, darkness: 0, areaRatio: 0 }\n : sampleCastShadow(canvas)\n const { createWorker, PSM } = await import('tesseract.js')\n let passIndex = 0\n const passCount = shadow.strong ? 3 : 2\n // passCount is 3 when a cast shadow is present, else 2. Logger events use\n // (passIndex + tesseractProgress) / passCount, so worker init at\n // progress=1 is 33% or 50% before the first recognize() call.\n const worker = await createTesseractWorker(createWorker, language, {\n workerPath: tesseractWorkerUrl.href,\n corePath: selectLstmCoreUrl(detectLstmCoreSupport()).href,\n langPath: tesseractLanguagePath(languagePlan),\n logger(message) {\n if (typeof message.progress === 'number') {\n options.onProgress?.(\n Math.min(1, (passIndex + message.progress) / passCount),\n message.status ?? 'recognizing text',\n )\n }\n },\n })\n try {\n await worker.setParameters({ tessedit_pageseg_mode: PSM.AUTO })\n const automatic = await worker.recognize(\n canvas,\n {},\n { text: true, blocks: true },\n )\n passIndex = 1\n await worker.setParameters({\n tessedit_pageseg_mode: PSM.SPARSE_TEXT,\n preserve_interword_spaces: '1',\n })\n const sparse = await worker.recognize(\n sparseCanvas,\n {},\n { text: true, blocks: true },\n )\n const passes: OcrPass[] = [\n { blocks: automatic.data.blocks as TesseractBlock[] | null },\n {\n blocks: sparse.data.blocks as TesseractBlock[] | null,\n scaleX: canvas.width / sparseCanvas.width,\n scaleY: canvas.height / sparseCanvas.height,\n },\n ]\n const mergeOptions = { ...options, includeWords: true }\n const inkDensity = createInkDensitySampler(canvas)\n const baseLayout = mergeOcrPasses(\n passes,\n mergeOptions,\n { width: canvas.width, height: canvas.height },\n inkDensity,\n )\n const useShadowPass =\n options.shadowPass !== false &&\n shadowRegionNeedsOcr(\n shadow,\n baseLayout.words,\n { width: canvas.width, height: canvas.height },\n inkDensity,\n )\n if (useShadowPass && shadow.region) {\n passIndex = 2\n const { tile, passMapping } = cropShadowTile(\n flattenedCanvas,\n shadow.region,\n canvas.width,\n canvas.height,\n )\n await worker.setParameters({\n tessedit_pageseg_mode: PSM.SPARSE_TEXT,\n preserve_interword_spaces: '1',\n })\n const adaptive = await worker.recognize(\n tile,\n {},\n { text: true, blocks: true },\n )\n passes.push({\n blocks: adaptive.data.blocks as TesseractBlock[] | null,\n ...passMapping,\n })\n }\n const layout = passes.length === 2\n ? baseLayout\n : mergeOcrPasses(\n passes,\n mergeOptions,\n { width: canvas.width, height: canvas.height },\n inkDensity,\n )\n options.onProgress?.(1, 'recognizing text')\n return {\n text: layout.text,\n confidence: layout.confidence,\n language,\n imageWidth: canvas.width,\n imageHeight: canvas.height,\n diagnostics: {\n shadowPassUsed: useShadowPass,\n castShadowStrength: shadow.strength,\n shadowRegion: shadow.region\n ? {\n x: shadow.region.x * canvas.width,\n y: shadow.region.y * canvas.height,\n width: shadow.region.width * canvas.width,\n height: shadow.region.height * canvas.height,\n }\n : undefined,\n },\n ...(options.includeWords ? { words: layout.words } : {}),\n ...(options.includeCharacters ? { characters: layout.characters } : {}),\n }\n } finally {\n await worker.terminate()\n }\n}\n","import { canvasUnavailable } from './errors'\nimport { canvasToBlob, clampBox, sourceToCanvas } from './image'\nimport { assertLicense } from './license'\nimport type { ImageSource, RedactionRegion, RedactOptions } from './types'\n\nexport interface RedactResult {\n canvas: HTMLCanvasElement\n blob: Blob\n}\n\n/**\n * Paints trusted redaction regions onto a copy of an image and encodes it.\n * Use this when the rectangles are already known. `redactProgrammatically`\n * calls it after matching rules.\n *\n * The source is not mutated. Regions use source-image pixel coordinates and\n * are clipped to the image bounds.\n *\n * @throws {LicenseError} LicenseError when the redaction feature is not licensed.\n * @throws {ScannerError} ScannerError `canvas` when canvas 2D is unavailable.\n */\nexport async function applyRedactions(\n source: ImageSource,\n regions: RedactionRegion[],\n options: RedactOptions = {},\n): Promise<RedactResult> {\n await assertLicense('redaction')\n const canvas = await sourceToCanvas(source)\n const context = canvas.getContext('2d')\n if (!context) throw canvasUnavailable()\n\n for (const region of regions) {\n const box = clampBox(region, canvas.width, canvas.height)\n if (box.width < 1 || box.height < 1) continue\n const mode = region.mode ?? options.mode ?? 'blackout'\n if (mode === 'blackout') {\n context.fillStyle = '#050706'\n context.fillRect(box.x, box.y, box.width, box.height)\n continue\n }\n const buffer = document.createElement('canvas')\n buffer.width = Math.ceil(box.width)\n buffer.height = Math.ceil(box.height)\n const bufferContext = buffer.getContext('2d')\n if (!bufferContext) continue\n bufferContext.filter = `blur(${options.blurRadius ?? 18}px)`\n const bleed = Math.max(12, options.blurRadius ?? 18)\n bufferContext.drawImage(\n canvas,\n box.x - bleed,\n box.y - bleed,\n box.width + bleed * 2,\n box.height + bleed * 2,\n -bleed,\n -bleed,\n box.width + bleed * 2,\n box.height + bleed * 2,\n )\n context.drawImage(buffer, box.x, box.y, box.width, box.height)\n }\n\n return {\n canvas,\n blob: await canvasToBlob(canvas, options.mimeType ?? 'image/png', options.quality),\n }\n}\n","import { canvasUnavailable } from './errors'\nimport { sourceToCanvas } from './image'\nimport type { Box, ImageSource } from './types'\n\nconst sessions = new Map<string, Promise<import('onnxruntime-web').InferenceSession>>()\n\nexport const faceInputSize = 640\nexport const faceYoloxAnchorsAt640 = 80 * 80 + 40 * 40 + 20 * 20\n/** `cx, cy, w, h, objectness`, then one face probability. */\nexport const faceYoloxFeatures = 6\n\nexport interface FaceDetection {\n bbox: Box\n confidence: number\n}\n\nexport interface DecodeFaceTensorOptions {\n confidence?: number\n imageWidth: number\n imageHeight: number\n inputSize?: number\n}\n\nfunction overlap(a: Box, b: Box) {\n const intersection =\n Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)) *\n Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y))\n return intersection / Math.max(1, a.width * a.height + b.width * b.height - intersection)\n}\n\nfunction scaledBox(\n centerX: number,\n centerY: number,\n width: number,\n height: number,\n inputSize: number,\n imageWidth: number,\n imageHeight: number,\n): Box {\n return {\n x: ((centerX - width / 2) / inputSize) * imageWidth,\n y: ((centerY - height / 2) / inputSize) * imageHeight,\n width: (width / inputSize) * imageWidth,\n height: (height / inputSize) * imageHeight,\n }\n}\n\n/**\n * Decode a one-class YOLOX-Nano face tensor.\n * Shape is `[1, 8400, 6]` at 640: `cx, cy, w, h, objectness`, then the face probability.\n * Confidence is objectness times that probability. Boxes are stretched back onto the source image.\n */\nexport function decodeFaceTensor(\n values: ArrayLike<number>,\n dimensions: readonly number[],\n options: DecodeFaceTensorOptions,\n): FaceDetection[] {\n const anchors = dimensions.length === 3 ? dimensions[1] : dimensions[0]\n const features = dimensions.length === 3 ? dimensions[2] : dimensions[1]\n if (features !== faceYoloxFeatures || anchors < 1) {\n throw new Error(\n `Face model expects [1, anchors, ${faceYoloxFeatures}]. A 640 YOLOX-Nano face export is [1, ${faceYoloxAnchorsAt640}, ${faceYoloxFeatures}].`,\n )\n }\n const confidence = options.confidence ?? 0.35\n const inputSize = options.inputSize ?? faceInputSize\n const detections: FaceDetection[] = []\n for (let anchor = 0; anchor < anchors; anchor += 1) {\n const base = anchor * features\n const score = values[base + 4] * values[base + 5]\n if (score < confidence) continue\n detections.push({\n confidence: score,\n bbox: scaledBox(\n values[base],\n values[base + 1],\n values[base + 2],\n values[base + 3],\n inputSize,\n options.imageWidth,\n options.imageHeight,\n ),\n })\n }\n return detections\n .sort((a, b) => b.confidence - a.confidence)\n .filter((candidate, index, all) =>\n all.slice(0, index).every((accepted) => overlap(candidate.bbox, accepted.bbox) < 0.45),\n )\n .slice(0, 8)\n}\n\nexport async function detectFaces(\n source: ImageSource,\n modelUrl: string,\n confidence = 0.35,\n): Promise<FaceDetection[]> {\n const canvas = await sourceToCanvas(source, { cloneCanvas: false })\n const ort = await import('onnxruntime-web')\n const size = faceInputSize\n const input = document.createElement('canvas')\n input.width = size\n input.height = size\n const context = input.getContext('2d', { willReadFrequently: true })\n if (!context) throw canvasUnavailable()\n context.drawImage(canvas, 0, 0, size, size)\n const pixels = context.getImageData(0, 0, size, size).data\n const channelSize = size * size\n const data = new Float32Array(channelSize * 3)\n for (let index = 0; index < channelSize; index += 1) {\n data[index] = pixels[index * 4] / 255\n data[channelSize + index] = pixels[index * 4 + 1] / 255\n data[channelSize * 2 + index] = pixels[index * 4 + 2] / 255\n }\n let sessionPromise = sessions.get(modelUrl)\n if (!sessionPromise) {\n sessionPromise = ort.InferenceSession.create(modelUrl, {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n })\n sessions.set(modelUrl, sessionPromise)\n }\n const session = await sessionPromise\n const output = await session.run({\n [session.inputNames[0]]: new ort.Tensor('float32', data, [1, 3, size, size]),\n })\n const tensor = output[session.outputNames[0]]\n return decodeFaceTensor(tensor.data as Float32Array, tensor.dims.map(Number), {\n confidence,\n imageWidth: canvas.width,\n imageHeight: canvas.height,\n inputSize: size,\n })\n}\n","import { canvasUnavailable } from './errors'\nimport { sourceToCanvas } from './image'\nimport { downloadModelBytes } from './model-download'\nimport type { ImageSource } from './types'\nimport type { SemanticField, SemanticModelDetection } from './programmatic-redaction'\n\nconst sessions = new Map<string, Promise<import('onnxruntime-web').InferenceSession>>()\n\nexport const defaultFieldClasses: SemanticField[] = [\n 'person_name',\n 'date_of_birth',\n 'address',\n 'document_number',\n 'expiration_date',\n 'postal_code',\n 'state',\n]\n\n/** YOLOv8 channel-major output, or decoded YOLOX-Nano anchor-major output. */\nexport type FieldLayout = 'yolov8' | 'yolox'\n\n/**\n * Decoded YOLOX-Nano at 640×640 (strides 8, 16, 32).\n * 80×80 + 40×40 + 20×20. Official nano ships at 416; this SDK resizes to 640.\n */\nexport const yoloxNanoAnchorsAt640 = 80 * 80 + 40 * 40 + 20 * 20\n\n/** `cx, cy, w, h, objectness` plus one probability per class. Seven classes → 12. */\nexport function yoloxFeatureWidth(classCount: number) {\n return 5 + classCount\n}\n\nconst fieldInputSize = 640\n\nexport interface DecodeFieldTensorOptions {\n classes?: SemanticField[]\n confidence?: number\n layout?: FieldLayout\n imageWidth: number\n imageHeight: number\n inputSize?: number\n}\n\nfunction overlap(a: SemanticModelDetection, b: SemanticModelDetection) {\n const intersection =\n Math.max(0, Math.min(a.bbox.x + a.bbox.width, b.bbox.x + b.bbox.width) - Math.max(a.bbox.x, b.bbox.x)) *\n Math.max(0, Math.min(a.bbox.y + a.bbox.height, b.bbox.y + b.bbox.height) - Math.max(a.bbox.y, b.bbox.y))\n return intersection / Math.max(1, a.bbox.width * a.bbox.height + b.bbox.width * b.bbox.height - intersection)\n}\n\nfunction suppress(detections: SemanticModelDetection[]) {\n return detections\n .sort((a, b) => b.confidence - a.confidence)\n .filter((candidate, index, all) =>\n all.slice(0, index).every((accepted) =>\n accepted.field !== candidate.field || overlap(candidate, accepted) < 0.45,\n ),\n )\n .slice(0, 30)\n}\n\nfunction scaledBox(\n centerX: number,\n centerY: number,\n width: number,\n height: number,\n inputSize: number,\n imageWidth: number,\n imageHeight: number,\n) {\n return {\n x: ((centerX - width / 2) / inputSize) * imageWidth,\n y: ((centerY - height / 2) / inputSize) * imageHeight,\n width: (width / inputSize) * imageWidth,\n height: (height / inputSize) * imageHeight,\n }\n}\n\n/** Rank-3 `[1, anchors, 5+classes]` or rank-2 `[anchors, 5+classes]`. */\nfunction yoloxTensorAxes(dimensions: readonly number[], classCount: number) {\n const features = yoloxFeatureWidth(classCount)\n if (\n dimensions.length === 3 &&\n dimensions[0] === 1 &&\n dimensions[2] === features &&\n dimensions[1] > dimensions[2]\n ) {\n return { anchors: dimensions[1], features }\n }\n if (dimensions.length === 2 && dimensions[1] === features && dimensions[0] > dimensions[1]) {\n return { anchors: dimensions[0], features }\n }\n return null\n}\n\nfunction isYolov8Shape(dimensions: readonly number[]) {\n return dimensions.length === 3 && dimensions[1] >= 5 && dimensions[2] >= 1\n}\n\nfunction yoloxShapeError(classCount: number) {\n const features = yoloxFeatureWidth(classCount)\n return new Error(\n `YOLOX field layout expects [1, anchors, ${features}] (cx, cy, w, h, objectness, ${classCount} class scores). ` +\n `A 640 YOLOX-Nano export with decode baked in is [1, ${yoloxNanoAnchorsAt640}, ${features}].`,\n )\n}\n\nfunction decodeYolov8(\n values: ArrayLike<number>,\n dimensions: readonly number[],\n classes: SemanticField[],\n confidence: number,\n inputSize: number,\n imageWidth: number,\n imageHeight: number,\n) {\n if (!isYolov8Shape(dimensions)) throw new Error('Unsupported field-model YOLO output shape.')\n const features = dimensions[1]\n const candidates = dimensions[2]\n const detections: SemanticModelDetection[] = []\n for (let index = 0; index < candidates; index += 1) {\n let score = 0\n let classIndex = 0\n for (let feature = 4; feature < Math.min(features, classes.length + 4); feature += 1) {\n const candidateScore = values[feature * candidates + index]\n if (candidateScore > score) {\n score = candidateScore\n classIndex = feature - 4\n }\n }\n const field = classes[classIndex]\n if (!field || score < confidence) continue\n detections.push({\n field,\n confidence: score,\n bbox: scaledBox(\n values[index],\n values[candidates + index],\n values[candidates * 2 + index],\n values[candidates * 3 + index],\n inputSize,\n imageWidth,\n imageHeight,\n ),\n })\n }\n return suppress(detections)\n}\n\nfunction decodeYolox(\n values: ArrayLike<number>,\n dimensions: readonly number[],\n classes: SemanticField[],\n confidence: number,\n inputSize: number,\n imageWidth: number,\n imageHeight: number,\n) {\n const axes = yoloxTensorAxes(dimensions, classes.length)\n if (!axes) throw yoloxShapeError(classes.length)\n const detections: SemanticModelDetection[] = []\n for (let anchor = 0; anchor < axes.anchors; anchor += 1) {\n const base = anchor * axes.features\n const objectness = values[base + 4]\n let classScore = 0\n let classIndex = 0\n for (let offset = 0; offset < classes.length; offset += 1) {\n const candidateScore = values[base + 5 + offset]\n if (candidateScore > classScore) {\n classScore = candidateScore\n classIndex = offset\n }\n }\n const field = classes[classIndex]\n const score = objectness * classScore\n if (!field || score < confidence) continue\n detections.push({\n field,\n confidence: score,\n bbox: scaledBox(\n values[base],\n values[base + 1],\n values[base + 2],\n values[base + 3],\n inputSize,\n imageWidth,\n imageHeight,\n ),\n })\n }\n return suppress(detections)\n}\n\n/**\n * Decode a field detector tensor.\n * YOLOv8 is `[1, classes+4, anchors]` (channel-major `cx, cy, w, h`, then class scores).\n * YOLOX-Nano with `--decode_in_inference` is `[1, anchors, 5+classes]`\n * (`cx, cy, w, h, objectness`, then class probabilities already in 640-pixel space).\n * An omitted layout follows the tensor shape and otherwise stays YOLOv8.\n */\nexport function decodeFieldTensor(\n values: ArrayLike<number>,\n dimensions: readonly number[],\n options: DecodeFieldTensorOptions,\n): SemanticModelDetection[] {\n const classes = options.classes ?? defaultFieldClasses\n const confidence = options.confidence ?? 0.35\n const inputSize = options.inputSize ?? fieldInputSize\n const yolox = yoloxTensorAxes(dimensions, classes.length)\n if (options.layout === 'yolov8' && yolox) {\n throw new Error(\n 'This tensor is YOLOX [1, anchors, 5+classes]. Pass fieldLayout: \\'yolox\\', or omit fieldLayout to detect it from the shape.',\n )\n }\n const layout: FieldLayout = options.layout ?? (yolox ? 'yolox' : 'yolov8')\n if (layout === 'yolox') {\n return decodeYolox(values, dimensions, classes, confidence, inputSize, options.imageWidth, options.imageHeight)\n }\n return decodeYolov8(values, dimensions, classes, confidence, inputSize, options.imageWidth, options.imageHeight)\n}\n\nexport async function detectFieldModel(\n source: ImageSource,\n modelUrl: string,\n classes: SemanticField[] = defaultFieldClasses,\n confidence = 0.35,\n layout?: FieldLayout,\n): Promise<SemanticModelDetection[]> {\n const canvas = await sourceToCanvas(source, { cloneCanvas: false })\n const ortPromise = import('onnxruntime-web')\n const size = fieldInputSize\n const input = document.createElement('canvas')\n input.width = size\n input.height = size\n const context = input.getContext('2d', { willReadFrequently: true })\n if (!context) throw canvasUnavailable()\n context.drawImage(canvas, 0, 0, size, size)\n const pixels = context.getImageData(0, 0, size, size).data\n const channelSize = size * size\n const data = new Float32Array(channelSize * 3)\n for (let index = 0; index < channelSize; index += 1) {\n data[index] = pixels[index * 4] / 255\n data[channelSize + index] = pixels[index * 4 + 1] / 255\n data[channelSize * 2 + index] = pixels[index * 4 + 2] / 255\n }\n let sessionPromise = sessions.get(modelUrl)\n if (!sessionPromise) {\n sessionPromise = (async () => {\n try {\n const [runtime, bytes] = await Promise.all([\n ortPromise,\n downloadModelBytes(modelUrl),\n ])\n return await runtime.InferenceSession.create(bytes, {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n })\n } catch (error) {\n sessions.delete(modelUrl)\n throw error\n }\n })()\n sessions.set(modelUrl, sessionPromise)\n }\n const [ort, session] = await Promise.all([ortPromise, sessionPromise])\n const output = await session.run({\n [session.inputNames[0]]: new ort.Tensor('float32', data, [1, 3, size, size]),\n })\n const tensor = output[session.outputNames[0]]\n return decodeFieldTensor(tensor.data as Float32Array, tensor.dims.map(Number), {\n classes,\n confidence,\n layout,\n imageWidth: canvas.width,\n imageHeight: canvas.height,\n inputSize: size,\n })\n}\n","import { sourceToCanvas } from './image'\nimport { assertLicense } from './license'\nimport { detectFaces, type FaceDetection } from './face-model'\nimport { detectFieldModel, type FieldLayout } from './field-model'\nimport { recognizeText } from './ocr'\nimport { applyRedactions, type RedactResult } from './redact'\nimport type { Box, ImageSource, OcrBox, OcrResult, RedactOptions } from './types'\n\nexport type StringMatchMode = 'exact' | 'case-insensitive' | 'regex'\nexport type LabelDirection = 'above' | 'below' | 'left' | 'right' | 'any'\n\nexport interface StringRedactionRule {\n id: string\n strategy: 'string'\n mode: StringMatchMode\n values?: string[]\n pattern?: string\n flags?: string\n scope?: 'word' | 'line'\n}\n\nexport interface LabelRedactionRule {\n id: string\n strategy: 'label'\n label: string\n direction: LabelDirection\n labelMode?: 'exact' | 'case-insensitive'\n valuePattern?: string\n patternFlags?: string\n window?: {\n distance?: number\n crossAxis?: number\n }\n redactLabel?: boolean\n}\n\nexport type SemanticField =\n | 'person_name'\n | 'date_of_birth'\n | 'address'\n | 'document_number'\n | 'expiration_date'\n | 'postal_code'\n | 'state'\n\n/** `logical` uses aliases, MRZ, and layout. `model` uses a field ONNX. */\nexport type SemanticFieldDetector = 'logical' | 'model'\n\nexport interface SemanticRedactionRule {\n id: string\n strategy: 'semantic'\n field: SemanticField\n /**\n * Defaults to `logical`. A loaded field model does not replace logical rules.\n * Set `model` on a separate rule to redact from `models.fields` or `semanticDetections`.\n */\n detector?: SemanticFieldDetector\n}\n\nexport interface SemanticModelDetection {\n field: SemanticField\n bbox: Box\n confidence: number\n}\n\nexport type ProgrammaticRedactionRule =\n | StringRedactionRule\n | LabelRedactionRule\n | SemanticRedactionRule\n\nexport interface NormalizedRect {\n x: number\n y: number\n width: number\n height: number\n}\n\nexport interface TemplateRectField {\n id: string\n strategy: 'rect'\n rect: NormalizedRect\n}\n\nexport interface TemplateAnchorField extends Omit<LabelRedactionRule, 'id' | 'strategy'> {\n id: string\n strategy: 'anchor'\n}\n\nexport type RedactionTemplateField = TemplateRectField | TemplateAnchorField\n\nexport interface RedactionTemplate {\n id: string\n fields: RedactionTemplateField[]\n}\n\nexport interface MatchRedactionOptions {\n rules: ProgrammaticRedactionRule[]\n template?: RedactionTemplate\n imageWidth?: number\n imageHeight?: number\n semanticDetections?: SemanticModelDetection[]\n /**\n * When true, rules with `detector: 'model'` use `semanticDetections`.\n * Logical rules ignore this flag.\n */\n modelRequested?: boolean\n}\n\nexport interface RedactionMatch {\n ruleId: string\n strategy: 'string' | 'label' | 'template-rect' | 'template-anchor'\n | 'semantic'\n source?: 'string' | 'label' | 'template' | 'alias' | 'mrz' | 'layout' | 'model'\n bbox: Box\n text?: string\n confidence: number\n}\n\nexport interface RedactionMatchResult {\n matches: RedactionMatch[]\n unmatchedRuleIds: string[]\n complete: boolean\n confidence: number\n}\n\nexport interface ProgrammaticRedactionOptions extends MatchRedactionOptions {\n /**\n * Prior OCR. Omit it to run OCR with word boxes.\n * An object without `words` is used as-is. OCR does not re-run, and\n * word, label, and semantic rules then match nothing.\n */\n ocr?: OcrResult\n style?: RedactOptions\n requireAll?: boolean\n models?: {\n fields?: string\n fieldClasses?: SemanticField[]\n /**\n * ONNX output layout. Defaults to `yolov8` (`[1, classes+4, anchors]`).\n * `yolox` is decoded YOLOX-Nano `[1, anchors, 5+classes]`\n * (`cx, cy, w, h, objectness`, then class scores). A tensor of that shape\n * is read as YOLOX even when this is omitted.\n */\n fieldLayout?: FieldLayout\n confidence?: number\n /**\n * When the field model fails to load, model-detector rules use logical finding.\n * Defaults to true. Logical rules are unchanged either way.\n */\n allowHeuristicFallback?: boolean\n /**\n * One-class YOLOX-Nano face ONNX. Boxes are painted on the image.\n * They do not select OCR words. Omitted means no face model is fetched.\n */\n face?: string\n faceConfidence?: number\n }\n}\n\nexport interface ProgrammaticRedactionResult extends RedactionMatchResult {\n status: 'success' | 'partial' | 'unmatched'\n ocr?: OcrResult\n canvas?: HTMLCanvasElement\n blob?: Blob\n modelStatus: 'not-requested' | 'loaded' | 'fallback' | 'failed'\n modelError?: string\n faces: FaceDetection[]\n faceStatus: 'not-requested' | 'loaded' | 'failed'\n faceError?: string\n}\n\ninterface OcrLine {\n words: OcrBox[]\n text: string\n bbox: Box\n}\n\nfunction unionBoxes(items: Array<{ bbox: Box }>): Box {\n const left = Math.min(...items.map((item) => item.bbox.x))\n const top = Math.min(...items.map((item) => item.bbox.y))\n const right = Math.max(...items.map((item) => item.bbox.x + item.bbox.width))\n const bottom = Math.max(...items.map((item) => item.bbox.y + item.bbox.height))\n return { x: left, y: top, width: right - left, height: bottom - top }\n}\n\nfunction groupLines(words: OcrBox[]) {\n const lines: OcrBox[][] = []\n const sorted = [...words].sort((a, b) =>\n a.bbox.y + a.bbox.height / 2 - (b.bbox.y + b.bbox.height / 2) || a.bbox.x - b.bbox.x,\n )\n for (const word of sorted) {\n const center = word.bbox.y + word.bbox.height / 2\n const line = lines.find((candidate) => {\n const box = unionBoxes(candidate)\n const lineCenter = box.y + box.height / 2\n return Math.abs(center - lineCenter) <= Math.max(word.bbox.height, box.height) * 0.65\n })\n if (line) line.push(word)\n else lines.push([word])\n }\n return lines.map<OcrLine>((lineWords) => {\n lineWords.sort((a, b) => a.bbox.x - b.bbox.x)\n return {\n words: lineWords,\n text: lineWords.map((word) => word.text).join(' '),\n bbox: unionBoxes(lineWords),\n }\n })\n}\n\nfunction normalized(value: string, insensitive: boolean, label = false) {\n const trimmed = value.trim().replace(/\\s+/g, ' ')\n const cleaned = label ? trimmed.replace(/[::]\\s*$/, '') : trimmed\n return insensitive ? cleaned.toLocaleLowerCase() : cleaned\n}\n\nfunction averageConfidence(words: OcrBox[]) {\n return words.reduce((total, word) => total + word.confidence, 0) / Math.max(1, words.length)\n}\n\nfunction matchStringRule(rule: StringRedactionRule, lines: OcrLine[]): RedactionMatch[] {\n const mode = rule.mode\n const insensitive = mode === 'case-insensitive'\n const scope = rule.scope ?? 'line'\n let expression: RegExp | undefined\n if (mode === 'regex') {\n const pattern = rule.pattern ?? rule.values?.[0]\n if (!pattern) return []\n expression = new RegExp(pattern, rule.flags?.replace('g', '') ?? '')\n }\n const values = (rule.values ?? []).filter(Boolean)\n const matches: RedactionMatch[] = []\n const add = (words: OcrBox[]) => matches.push({\n ruleId: rule.id,\n strategy: 'string',\n source: 'string',\n bbox: unionBoxes(words),\n text: words.map((word) => word.text).join(' '),\n confidence: averageConfidence(words),\n })\n if (scope === 'word') {\n for (const line of lines) {\n for (const word of line.words) {\n const candidate = normalized(word.text, insensitive)\n const matched = expression\n ? expression.test(word.text)\n : values.some((value) => candidate === normalized(value, insensitive))\n if (matched) add([word])\n }\n }\n return matches\n }\n for (const line of lines) {\n if (expression) {\n if (expression.test(line.text)) add(line.words)\n continue\n }\n for (const value of values) {\n const expected = normalized(value, insensitive).split(' ')\n for (let index = 0; index <= line.words.length - expected.length; index += 1) {\n const candidate = line.words.slice(index, index + expected.length)\n if (candidate.every((word, offset) =>\n normalized(word.text, insensitive) === expected[offset],\n )) add(candidate)\n }\n }\n }\n return matches\n}\n\nfunction findLabelWords(rule: LabelRedactionRule, lines: OcrLine[]) {\n const insensitive = rule.labelMode !== 'exact'\n const expected = normalized(rule.label, insensitive, true).split(' ')\n for (const line of lines) {\n for (let index = 0; index <= line.words.length - expected.length; index += 1) {\n const candidate = line.words.slice(index, index + expected.length)\n if (candidate.every((word, offset) =>\n normalized(word.text, insensitive, true) === expected[offset],\n )) return candidate\n }\n }\n return undefined\n}\n\nfunction directionalWords(\n words: OcrBox[],\n label: Box,\n direction: Exclude<LabelDirection, 'any'>,\n width: number,\n height: number,\n rule: LabelRedactionRule,\n) {\n const distance = rule.window?.distance ?? 0.28\n const crossAxis = rule.window?.crossAxis ?? 0.12\n const labelCenterX = label.x + label.width / 2\n const labelCenterY = label.y + label.height / 2\n return words.filter((word) => {\n const centerX = word.bbox.x + word.bbox.width / 2\n const centerY = word.bbox.y + word.bbox.height / 2\n if (direction === 'right') {\n return word.bbox.x >= label.x + label.width &&\n word.bbox.x - label.x - label.width <= width * distance &&\n Math.abs(centerY - labelCenterY) <= height * crossAxis\n }\n if (direction === 'left') {\n return word.bbox.x + word.bbox.width <= label.x &&\n label.x - word.bbox.x - word.bbox.width <= width * distance &&\n Math.abs(centerY - labelCenterY) <= height * crossAxis\n }\n if (direction === 'below') {\n return word.bbox.y >= label.y + label.height &&\n word.bbox.y - label.y - label.height <= height * distance &&\n Math.abs(centerX - labelCenterX) <= width * crossAxis\n }\n return word.bbox.y + word.bbox.height <= label.y &&\n label.y - word.bbox.y - word.bbox.height <= height * distance &&\n Math.abs(centerX - labelCenterX) <= width * crossAxis\n })\n}\n\nfunction matchLabelRule(\n rule: LabelRedactionRule,\n words: OcrBox[],\n lines: OcrLine[],\n width: number,\n height: number,\n strategy: RedactionMatch['strategy'] = 'label',\n): RedactionMatch[] {\n const labelWords = findLabelWords(rule, lines)\n if (!labelWords) return []\n const labelBox = unionBoxes(labelWords)\n const labelSet = new Set(labelWords)\n const available = words.filter((word) => !labelSet.has(word))\n const directions: Array<Exclude<LabelDirection, 'any'>> =\n rule.direction === 'any' ? ['right', 'below', 'left', 'above'] : [rule.direction]\n let best: { words: OcrBox[]; distance: number } | undefined\n for (const direction of directions) {\n const candidates = directionalWords(available, labelBox, direction, width, height, rule)\n const candidateLines = groupLines(candidates)\n for (const line of candidateLines) {\n if (rule.valuePattern && !new RegExp(rule.valuePattern, rule.patternFlags).test(line.text)) continue\n const centerX = line.bbox.x + line.bbox.width / 2\n const centerY = line.bbox.y + line.bbox.height / 2\n const labelCenterX = labelBox.x + labelBox.width / 2\n const labelCenterY = labelBox.y + labelBox.height / 2\n const distance = Math.hypot((centerX - labelCenterX) / width, (centerY - labelCenterY) / height)\n if (!best || distance < best.distance) best = { words: line.words, distance }\n }\n }\n if (!best) return []\n const matchedWords = rule.redactLabel ? [...labelWords, ...best.words] : best.words\n return [{\n ruleId: rule.id,\n strategy,\n source: strategy === 'template-anchor' ? 'template' : 'label',\n bbox: unionBoxes(matchedWords),\n text: best.words.map((word) => word.text).join(' '),\n confidence: Math.max(0, averageConfidence(best.words) * (1 - Math.min(0.5, best.distance))),\n }]\n}\n\nconst fieldAliases: Record<SemanticField, string[]> = {\n person_name: ['LN', 'FN', 'DCS', 'DAC', 'DAD', 'NAME'],\n date_of_birth: ['DOB', 'DBB', 'BIRTH'],\n address: ['ADDRESS', 'ADDR', 'DAG', 'DAI'],\n document_number: ['DLN', 'DAQ', 'IDNO', 'LICNO'],\n expiration_date: ['EXP', 'DBA', 'EXPIRES'],\n postal_code: ['ZIP', 'DAK', 'POSTAL'],\n state: ['STATE', 'DAJ'],\n}\n\nconst allAliases = new Set(Object.values(fieldAliases).flat())\nconst dateToken = /^(?:\\d{2}[/-]\\d{2}[/-]\\d{2,4}|\\d{4}[/-]\\d{2}[/-]\\d{2}|\\d{6}|\\d{8})$/\nconst streetToken = /\\b(?:ST|STREET|AVE|AVENUE|RD|ROAD|BLVD|BOULEVARD|LN|LANE|DR|DRIVE|WAY|CT|COURT)\\b/i\nconst headerToken = /\\b(?:DRIVER|LICENSE|IDENTIFICATION|CARD|CLASS|SEX|EYES|HEIGHT|RESTRICTIONS)\\b/i\n\nfunction semanticTextIsValid(field: SemanticField, text: string) {\n const value = text.trim()\n if (!value) return false\n if (field === 'date_of_birth' || field === 'expiration_date') return dateToken.test(value.replace(/\\s/g, ''))\n if (field === 'postal_code') return /^(?:\\d{5}(?:-\\d{4})?|[A-Z]\\d[A-Z]\\s?\\d[A-Z]\\d)$/i.test(value)\n if (field === 'state') return /^[A-Z]{2}$/i.test(value)\n if (field === 'document_number') return /^(?=.*\\d)[A-Z0-9-]{5,20}$/i.test(value)\n if (field === 'person_name') {\n return !/\\d/.test(value) && /^[A-ZÀ-ÖØ-öø-ÿ' -]{3,}$/i.test(value) && !headerToken.test(value)\n }\n return /\\d/.test(value) && /[A-Z]/i.test(value)\n}\n\nfunction semanticMatch(\n ruleId: string,\n source: NonNullable<RedactionMatch['source']>,\n words: OcrBox[],\n text?: string,\n confidenceScale = 1,\n): RedactionMatch {\n return {\n ruleId,\n strategy: 'semantic',\n source,\n bbox: unionBoxes(words),\n text: text ?? words.map((word) => word.text).join(' '),\n confidence: Math.max(0, Math.min(1, averageConfidence(words) * confidenceScale)),\n }\n}\n\nfunction aliasSemanticMatches(field: SemanticField, ruleId: string, lines: OcrLine[]) {\n const aliases = fieldAliases[field]\n const matches: RedactionMatch[] = []\n for (const line of lines) {\n for (let index = 0; index < line.words.length; index += 1) {\n const token = line.words[index].text.toUpperCase().replace(/[^A-Z0-9]/g, '')\n const alias = aliases.find((candidate) => token === candidate || token.startsWith(candidate))\n if (!alias) continue\n let valueWords: OcrBox[]\n let valueText: string\n if (token !== alias) {\n valueWords = [line.words[index]]\n valueText = line.words[index].text.replace(new RegExp(`^${alias}[:\\\\s-]*`, 'i'), '')\n } else {\n valueWords = line.words.slice(index + 1).filter((word) =>\n !allAliases.has(word.text.toUpperCase().replace(/[^A-Z0-9]/g, '')),\n )\n valueText = valueWords.map((word) => word.text).join(' ')\n }\n if (!valueWords.length || !semanticTextIsValid(field, valueText)) continue\n matches.push(semanticMatch(ruleId, 'alias', valueWords, valueText, 0.98))\n }\n }\n return matches\n}\n\nfunction mrzSemanticMatches(field: SemanticField, ruleId: string, lines: OcrLine[]) {\n const mrzLines = lines.filter((line) => {\n const compact = line.text.replace(/\\s/g, '')\n return compact.includes('<<') || /^[A-Z0-9<]{25,}$/.test(compact)\n })\n if (!mrzLines.length) return []\n if (field === 'person_name') {\n const line = mrzLines.find((candidate) => candidate.text.includes('<<'))\n if (!line) return []\n const compact = line.text.replace(/\\s/g, '')\n const nameStart = /^[PIV]<[A-Z]{3}/.test(compact) ? 5 : compact.indexOf('<') + 1\n const names = compact.slice(nameStart).replace(/<+/g, ' ').trim()\n return names ? [semanticMatch(ruleId, 'mrz', line.words, names, 0.96)] : []\n }\n if (field === 'date_of_birth') {\n for (const line of mrzLines) {\n const compact = line.text.replace(/\\s/g, '')\n const value = /^\\d{6}/.exec(compact)?.[0] ?? /^.{13}(\\d{6})/.exec(compact)?.[1]\n if (value) return [semanticMatch(ruleId, 'mrz', line.words, value, 0.96)]\n }\n }\n if (field === 'document_number') {\n const line = mrzLines[0]\n return [semanticMatch(ruleId, 'mrz', line.words, line.text, 0.9)]\n }\n return []\n}\n\nfunction yearFromDate(value: string) {\n const compact = value.replace(/\\s/g, '')\n const fourDigit = compact.match(/\\d{4}/)?.[0]\n if (fourDigit) return Number(fourDigit)\n const six = compact.match(/^\\d{2}/)?.[0]\n if (!six) return undefined\n const year = Number(six)\n return year > new Date().getFullYear() % 100 ? 1900 + year : 2000 + year\n}\n\nfunction layoutSemanticMatches(\n field: SemanticField,\n ruleId: string,\n words: OcrBox[],\n lines: OcrLine[],\n height: number,\n) {\n if (field === 'address') {\n const index = lines.findIndex((line) => /\\d/.test(line.text) && streetToken.test(line.text))\n if (index < 0) return []\n const selected = [...lines[index].words]\n const next = lines[index + 1]\n if (next && /(?:\\b[A-Z]{2}\\b.*\\d{5}|\\d{5})/i.test(next.text)) selected.push(...next.words)\n return [semanticMatch(ruleId, 'layout', selected, undefined, 0.76)]\n }\n if (field === 'person_name') {\n const candidates = lines.map((line) => {\n const alphaWords = line.words.filter((word) => /^[A-ZÀ-ÖØ-öø-ÿ'-]+$/i.test(word.text))\n const y = line.bbox.y / Math.max(1, height)\n const valid = alphaWords.length >= 2 && alphaWords.length <= 5 &&\n alphaWords.length === line.words.length && !headerToken.test(line.text) &&\n !streetToken.test(line.text) && !line.text.includes('<')\n const uppercase = line.text.replace(/[^A-Z]/g, '').length / Math.max(1, line.text.replace(/[^A-Za-z]/g, '').length)\n return { line, score: valid ? 0.45 + averageConfidence(line.words) * 0.3 + uppercase * 0.15 + (y < 0.65 ? 0.1 : 0) : 0 }\n }).filter((candidate) => candidate.score >= 0.68).sort((a, b) => b.score - a.score)\n if (!candidates.length || (candidates[1] && candidates[0].score - candidates[1].score < 0.04)) return []\n return [semanticMatch(ruleId, 'layout', candidates[0].line.words, undefined, candidates[0].score)]\n }\n if (field === 'date_of_birth' || field === 'expiration_date') {\n const currentYear = new Date().getFullYear()\n const candidates = words.filter((word) => dateToken.test(word.text.replace(/\\s/g, '')))\n .map((word) => ({ word, year: yearFromDate(word.text) }))\n .filter((candidate) => candidate.year !== undefined)\n const filtered = field === 'date_of_birth'\n ? candidates.filter((candidate) => candidate.year! <= currentYear - 14 && candidate.year! >= currentYear - 120)\n : candidates.filter((candidate) => candidate.year! >= currentYear - 1)\n filtered.sort((a, b) => field === 'date_of_birth' ? a.year! - b.year! : b.year! - a.year!)\n if (!filtered.length) return []\n return [semanticMatch(ruleId, 'layout', [filtered[0].word], undefined, 0.72)]\n }\n if (field === 'postal_code') {\n const candidate = words.find((word) => semanticTextIsValid(field, word.text))\n return candidate ? [semanticMatch(ruleId, 'layout', [candidate], undefined, 0.78)] : []\n }\n if (field === 'state') {\n const candidates = words.filter((word) => semanticTextIsValid(field, word.text))\n return candidates.length === 1 ? [semanticMatch(ruleId, 'layout', candidates, undefined, 0.7)] : []\n }\n if (field === 'document_number') {\n const candidates = words.filter((word) => semanticTextIsValid(field, word.text))\n candidates.sort((a, b) => b.text.length - a.text.length)\n return candidates.length ? [semanticMatch(ruleId, 'layout', [candidates[0]], undefined, 0.7)] : []\n }\n return []\n}\n\nfunction intersectionRatio(box: Box, word: Box) {\n const area =\n Math.max(0, Math.min(box.x + box.width, word.x + word.width) - Math.max(box.x, word.x)) *\n Math.max(0, Math.min(box.y + box.height, word.y + word.height) - Math.max(box.y, word.y))\n return area / Math.max(1, word.width * word.height)\n}\n\nfunction logicalSemanticMatches(\n rule: SemanticRedactionRule,\n words: OcrBox[],\n lines: OcrLine[],\n height: number,\n) {\n const aliases = aliasSemanticMatches(rule.field, rule.id, lines)\n const mrz = mrzSemanticMatches(rule.field, rule.id, lines)\n if (aliases.length || mrz.length) return [...aliases, ...mrz]\n return layoutSemanticMatches(rule.field, rule.id, words, lines, height)\n}\n\nfunction modelSemanticMatch(\n rule: SemanticRedactionRule,\n words: OcrBox[],\n detections: SemanticModelDetection[] | undefined,\n) {\n const modelCandidates = (detections ?? [])\n .filter((detection) => detection.field === rule.field)\n .sort((a, b) => b.confidence - a.confidence)\n for (const detection of modelCandidates) {\n const selected = words.filter((word) => intersectionRatio(detection.bbox, word.bbox) >= 0.3)\n if (!selected.length) continue\n const text = selected.map((word) => word.text).join(' ')\n if (!semanticTextIsValid(rule.field, text)) continue\n return semanticMatch(rule.id, 'model', selected, text, detection.confidence)\n }\n return undefined\n}\n\nfunction matchSemanticRule(\n rule: SemanticRedactionRule,\n words: OcrBox[],\n lines: OcrLine[],\n height: number,\n detections: SemanticModelDetection[] | undefined,\n modelRequested: boolean,\n) {\n const logical = logicalSemanticMatches(rule, words, lines, height)\n if (rule.detector !== 'model') return logical\n if (!modelRequested) return logical\n const modelMatch = modelSemanticMatch(rule, words, detections)\n return modelMatch ? [modelMatch] : []\n}\n\nfunction templateRect(field: TemplateRectField, templateId: string, width: number, height: number): RedactionMatch {\n const clamp = (value: number) => Math.max(0, Math.min(1, value))\n const x = clamp(field.rect.x)\n const y = clamp(field.rect.y)\n return {\n ruleId: `${templateId}.${field.id}`,\n strategy: 'template-rect',\n source: 'template',\n bbox: {\n x: x * width,\n y: y * height,\n width: Math.max(0, Math.min(1 - x, field.rect.width)) * width,\n height: Math.max(0, Math.min(1 - y, field.rect.height)) * height,\n },\n confidence: 1,\n }\n}\n\nexport function matchRedactionRules(\n ocr: OcrResult | undefined,\n options: MatchRedactionOptions,\n): RedactionMatchResult {\n const width = options.imageWidth ?? ocr?.imageWidth ?? 0\n const height = options.imageHeight ?? ocr?.imageHeight ?? 0\n const words = ocr?.words ?? []\n const lines = groupLines(words)\n const matches: RedactionMatch[] = []\n const unmatchedRuleIds: string[] = []\n for (const rule of options.rules) {\n const ruleMatches = rule.strategy === 'string'\n ? matchStringRule(rule, lines)\n : rule.strategy === 'label'\n ? matchLabelRule(rule, words, lines, width, height)\n : matchSemanticRule(\n rule,\n words,\n lines,\n height,\n options.semanticDetections,\n options.modelRequested === true,\n )\n if (ruleMatches.length) matches.push(...ruleMatches)\n else unmatchedRuleIds.push(rule.id)\n }\n for (const field of options.template?.fields ?? []) {\n const ruleId = `${options.template!.id}.${field.id}`\n if (field.strategy === 'rect') {\n if (width && height) matches.push(templateRect(field, options.template!.id, width, height))\n else unmatchedRuleIds.push(ruleId)\n continue\n }\n const anchorRule: LabelRedactionRule = {\n ...field,\n id: ruleId,\n strategy: 'label',\n }\n const fieldMatches = matchLabelRule(anchorRule, words, lines, width, height, 'template-anchor')\n if (fieldMatches.length) matches.push(...fieldMatches)\n else unmatchedRuleIds.push(ruleId)\n }\n const expected = options.rules.length + (options.template?.fields.length ?? 0)\n const matchedIds = new Set(matches.map((match) => match.ruleId))\n const confidence = expected\n ? [...matchedIds].reduce((total, id) => {\n const items = matches.filter((match) => match.ruleId === id)\n return total + Math.max(...items.map((match) => match.confidence))\n }, 0) / expected\n : 1\n return {\n matches,\n unmatchedRuleIds,\n complete: unmatchedRuleIds.length === 0,\n confidence,\n }\n}\n\nfunction needsOcr(options: ProgrammaticRedactionOptions) {\n return options.rules.length > 0 ||\n options.template?.fields.some((field) => field.strategy === 'anchor') === true\n}\n\nexport function redactionStatus(\n summary: RedactionMatchResult,\n requireAll = false,\n): ProgrammaticRedactionResult['status'] {\n if (requireAll && !summary.complete) return 'unmatched'\n return summary.complete ? 'success' : 'partial'\n}\n\n/**\n * Matches rules and paints a new canvas.\n * Returns `success`, `partial`, or `unmatched`. An unmatched rule does not throw.\n *\n * Omit `ocr` to run OCR with `includeWords: true`. An `ocr` object without\n * `words` is used as-is, and OCR does not re-run.\n * Face blackout is `models.face` on this call.\n *\n * @throws {LicenseError} LicenseError when the redaction feature is not licensed. Matching does not start.\n * @throws {ScannerError} ScannerError `canvas` when canvas 2D is unavailable.\n */\nexport async function redactProgrammatically(\n image: ImageSource,\n options: ProgrammaticRedactionOptions,\n): Promise<ProgrammaticRedactionResult> {\n await assertLicense('redaction')\n const canvas = await sourceToCanvas(image, { cloneCanvas: false })\n let ocr = options.ocr\n if (needsOcr(options) && !ocr) {\n ocr = await recognizeText(canvas, { includeWords: true })\n }\n let semanticDetections = options.semanticDetections\n let modelRequested = options.modelRequested === true\n let modelStatus: ProgrammaticRedactionResult['modelStatus'] = 'not-requested'\n let modelError: string | undefined\n if (options.models?.fields) {\n try {\n semanticDetections = await detectFieldModel(\n canvas,\n options.models.fields,\n options.models.fieldClasses,\n options.models.confidence,\n options.models.fieldLayout,\n )\n modelRequested = true\n modelStatus = 'loaded'\n } catch (error) {\n modelError = error instanceof Error ? error.message : 'The field model could not load.'\n const allowFallback = options.models.allowHeuristicFallback !== false\n modelRequested = !allowFallback\n modelStatus = allowFallback ? 'fallback' : 'failed'\n }\n }\n const summary = matchRedactionRules(ocr, {\n rules: options.rules,\n template: options.template,\n imageWidth: canvas.width,\n imageHeight: canvas.height,\n semanticDetections,\n modelRequested,\n })\n const status = redactionStatus(summary, options.requireAll)\n let faces: FaceDetection[] = []\n let faceStatus: ProgrammaticRedactionResult['faceStatus'] = 'not-requested'\n let faceError: string | undefined\n if (options.models?.face) {\n try {\n faces = await detectFaces(canvas, options.models.face, options.models.faceConfidence)\n faceStatus = 'loaded'\n } catch (error) {\n faceError = error instanceof Error ? error.message : 'The face model could not load.'\n faceStatus = 'failed'\n }\n }\n if (status === 'unmatched') {\n return { ...summary, status, ocr, modelStatus, modelError, faces, faceStatus, faceError }\n }\n const output: RedactResult = await applyRedactions(\n canvas,\n [\n ...summary.matches.map((match) => ({ ...match.bbox, mode: options.style?.mode })),\n ...faces.map((face) => ({ ...face.bbox, mode: options.style?.mode })),\n ],\n options.style,\n )\n return {\n ...summary,\n status,\n ocr,\n canvas: output.canvas,\n blob: output.blob,\n modelStatus,\n modelError,\n faces,\n faceStatus,\n faceError,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scanner-sdk.js","names":[],"sources":["../src/sdk/frame-geometry.ts","../src/sdk/finder-simulator.ts"],"sourcesContent":["import type { DocumentCorners, Point } from './types'\n\nexport interface FrameSize {\n width: number\n height: number\n}\n\nfunction mapPointFromVisibleSourceToTarget(\n point: Point,\n source: FrameSize,\n target: FrameSize,\n): Point {\n const sourceWidth = Math.max(1, source.width)\n const sourceHeight = Math.max(1, source.height)\n const targetWidth = Math.max(1, target.width)\n const targetHeight = Math.max(1, target.height)\n // The analysis frame is the center-cropped view of a differently-shaped\n // still. Invert object-fit: cover so source pixels map into the full still.\n const scale = Math.max(sourceWidth / targetWidth, sourceHeight / targetHeight)\n const offsetX = (sourceWidth - targetWidth * scale) / 2\n const offsetY = (sourceHeight - targetHeight * scale) / 2\n return {\n x: (point.x - offsetX) / scale,\n y: (point.y - offsetY) / scale,\n }\n}\n\nexport function mapDocumentCornersToFrame(\n corners: DocumentCorners,\n source: FrameSize,\n target: FrameSize,\n): DocumentCorners {\n return {\n topLeft: mapPointFromVisibleSourceToTarget(corners.topLeft, source, target),\n topRight: mapPointFromVisibleSourceToTarget(corners.topRight, source, target),\n bottomRight: mapPointFromVisibleSourceToTarget(corners.bottomRight, source, target),\n bottomLeft: mapPointFromVisibleSourceToTarget(corners.bottomLeft, source, target),\n }\n}\n","import { canvasUnavailable } from './errors'\nimport type { DocumentQuad, Point } from './types'\n\nexport interface SimulatorPose {\n centerX: number\n centerY: number\n rotation: number\n width: number\n height: number\n phase: 'entering' | 'jittering' | 'settled'\n}\n\nexport interface FinderSimulator {\n start: () => void\n stop: () => void\n reset: () => void\n}\n\nexport function simulatorPose(elapsedMs: number, canvasWidth = 960, canvasHeight = 600): SimulatorPose {\n // Settle comfortably above the finder's default 72% limiting-axis fill target so\n // the simulator demonstrates Move closer before becoming eligible for auto-capture.\n const width = canvasWidth * 0.86\n const height = canvasHeight * 0.86\n if (elapsedMs < 1200) {\n const progress = elapsedMs / 1200\n return {\n centerX: -width / 2 + (canvasWidth / 2 + width / 2) * (1 - Math.pow(1 - progress, 3)),\n centerY: canvasHeight / 2,\n rotation: -0.14 + progress * 0.08,\n width,\n height,\n phase: 'entering',\n }\n }\n if (elapsedMs < 3600) {\n const progress = (elapsedMs - 1200) / 2400\n const amplitude = (1 - progress) * 18\n return {\n centerX: canvasWidth / 2 + Math.sin(elapsedMs / 85) * amplitude,\n centerY: canvasHeight / 2 + Math.cos(elapsedMs / 110) * amplitude * 0.55,\n rotation: -0.055 + Math.sin(elapsedMs / 125) * (1 - progress) * 0.045,\n width,\n height,\n phase: 'jittering',\n }\n }\n return {\n centerX: canvasWidth / 2,\n centerY: canvasHeight / 2,\n rotation: 0,\n width,\n height,\n phase: 'settled',\n }\n}\n\nfunction rotatePoint(x: number, y: number, pose: SimulatorPose): Point {\n const cosine = Math.cos(pose.rotation)\n const sine = Math.sin(pose.rotation)\n return {\n x: pose.centerX + x * cosine - y * sine,\n y: pose.centerY + x * sine + y * cosine,\n }\n}\n\nexport function simulatorQuad(pose: SimulatorPose): DocumentQuad {\n const topLeft = rotatePoint(-pose.width / 2, -pose.height / 2, pose)\n const topRight = rotatePoint(pose.width / 2, -pose.height / 2, pose)\n const bottomRight = rotatePoint(pose.width / 2, pose.height / 2, pose)\n const bottomLeft = rotatePoint(-pose.width / 2, pose.height / 2, pose)\n return {\n topLeft,\n topRight,\n bottomRight,\n bottomLeft,\n edges: {\n top: { start: topLeft, end: topRight },\n right: { start: topRight, end: bottomRight },\n bottom: { start: bottomRight, end: bottomLeft },\n left: { start: bottomLeft, end: topLeft },\n },\n }\n}\n\nfunction drawFrame(canvas: HTMLCanvasElement, pose: SimulatorPose) {\n const context = canvas.getContext('2d')\n if (!context) throw canvasUnavailable()\n const background = context.createLinearGradient(0, 0, canvas.width, canvas.height)\n background.addColorStop(0, '#16231c')\n background.addColorStop(1, '#32443a')\n context.fillStyle = background\n context.fillRect(0, 0, canvas.width, canvas.height)\n context.save()\n context.translate(pose.centerX, pose.centerY)\n context.rotate(pose.rotation)\n context.shadowColor = 'rgba(0,0,0,.45)'\n context.shadowBlur = 24\n context.fillStyle = '#fffdf4'\n context.strokeStyle = '#111914'\n context.lineWidth = 7\n context.fillRect(-pose.width / 2, -pose.height / 2, pose.width, pose.height)\n context.strokeRect(-pose.width / 2, -pose.height / 2, pose.width, pose.height)\n context.shadowColor = 'transparent'\n const left = -pose.width / 2\n const top = -pose.height / 2\n context.fillStyle = '#d7ded8'\n context.fillRect(left + 38, top + 84, 145, 190)\n context.fillStyle = '#617269'\n context.beginPath()\n context.arc(left + 110, top + 140, 38, 0, Math.PI * 2)\n context.fill()\n context.fillRect(left + 70, top + 185, 80, 65)\n context.fillStyle = '#17231c'\n context.font = '700 25px Arial'\n context.fillText('IDENTITY CARD', left + 38, top + 48)\n context.font = '700 31px Arial'\n context.fillText('TAYLOR MORGAN', left + 220, top + 118)\n context.font = '23px Arial'\n context.fillText('01/02/1990', left + 220, top + 166)\n context.fillText('A1234567', left + 220, top + 208)\n context.fillText('123 MAIN ST', left + 220, top + 270)\n context.fillText('AUSTIN TX 78701', left + 220, top + 307)\n context.restore()\n}\n\nexport function createFinderSimulator(canvas: HTMLCanvasElement): FinderSimulator {\n if (!canvas.width) canvas.width = 960\n if (!canvas.height) canvas.height = 600\n let running = false\n let startedAt = 0\n let animationFrame = 0\n const render = (time: number) => {\n if (!running) return\n if (!startedAt) startedAt = time\n drawFrame(canvas, simulatorPose(time - startedAt, canvas.width, canvas.height))\n animationFrame = requestAnimationFrame(render)\n }\n const reset = () => {\n startedAt = 0\n drawFrame(canvas, simulatorPose(0, canvas.width, canvas.height))\n }\n reset()\n return {\n start() {\n if (running) return\n running = true\n animationFrame = requestAnimationFrame(render)\n },\n stop() {\n running = false\n cancelAnimationFrame(animationFrame)\n },\n reset,\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AAMVA DL/ID compact barcode (~2020). Line-based three-letter elements after
|
|
3
|
+
* the `@` / `ANSI` header. Unknown IDs stay in `rawElements`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const AAMVA_ELEMENT_IDS: readonly ["DCA", "DCB", "DCD", "DBA", "DCS", "DAC", "DAD", "DBD", "DBB", "DBC", "DAY", "DAU", "DAG", "DAI", "DAJ", "DAK", "DAQ", "DCF", "DCG", "DDE", "DDF", "DDG", "DAH", "DAZ", "DCI", "DCJ", "DCK", "DBN", "DBG", "DBS", "DCU", "DCE", "DCL", "DCM", "DCN", "DCO", "DCP", "DCQ", "DCR", "DDA", "DDB", "DDC", "DDD", "DAW", "DAX", "DDH", "DDI", "DDJ", "DDK", "DDL", "DCT", "DAL", "DAM", "DAN", "DAO", "DAR", "DAS", "DAT", "DAV", "DBH", "DBI", "DBJ", "DBK", "DBL", "DBM", "DCH"];
|
|
6
|
+
export type AamvaElementId = (typeof AAMVA_ELEMENT_IDS)[number];
|
|
7
|
+
export type ScanUseCase = 'document' | 'drivers-license';
|
|
8
|
+
export type DriverLicenseFieldSource = 'barcode' | 'ocr' | 'merged';
|
|
9
|
+
export interface DriverLicenseFields {
|
|
10
|
+
issuerJurisdiction?: string;
|
|
11
|
+
expirationDate?: string;
|
|
12
|
+
documentNumber?: string;
|
|
13
|
+
firstName?: string;
|
|
14
|
+
middleName?: string;
|
|
15
|
+
lastName?: string;
|
|
16
|
+
dateOfBirth?: string;
|
|
17
|
+
sex?: string;
|
|
18
|
+
eyeColor?: string;
|
|
19
|
+
height?: string;
|
|
20
|
+
addressStreet?: string;
|
|
21
|
+
addressCity?: string;
|
|
22
|
+
addressPostalCode?: string;
|
|
23
|
+
issueDate?: string;
|
|
24
|
+
country?: string;
|
|
25
|
+
addressStreet2?: string;
|
|
26
|
+
hairColor?: string;
|
|
27
|
+
nameSuffix?: string;
|
|
28
|
+
vehicleClass?: string;
|
|
29
|
+
restrictionCodes?: string;
|
|
30
|
+
endorsementCodes?: string;
|
|
31
|
+
documentDiscriminator?: string;
|
|
32
|
+
inventoryControlNumber?: string;
|
|
33
|
+
raceEthnicity?: string;
|
|
34
|
+
organDonor?: string;
|
|
35
|
+
veteran?: string;
|
|
36
|
+
weight?: string;
|
|
37
|
+
}
|
|
38
|
+
export type DriverLicenseFieldKey = keyof DriverLicenseFields;
|
|
39
|
+
export declare const DRIVER_LICENSE_FIELD_KEYS: readonly ["issuerJurisdiction", "expirationDate", "documentNumber", "firstName", "middleName", "lastName", "dateOfBirth", "sex", "eyeColor", "height", "addressStreet", "addressCity", "addressPostalCode", "issueDate", "country", "addressStreet2", "hairColor", "nameSuffix", "vehicleClass", "restrictionCodes", "endorsementCodes", "documentDiscriminator", "inventoryControlNumber", "raceEthnicity", "organDonor", "veteran", "weight"];
|
|
40
|
+
export interface AamvaParseResult {
|
|
41
|
+
rawElements: Record<string, string>;
|
|
42
|
+
fields: DriverLicenseFields;
|
|
43
|
+
header?: {
|
|
44
|
+
iin?: string;
|
|
45
|
+
aamvaVersion?: string;
|
|
46
|
+
jurisdictionVersion?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `MMDDYYYY` / `YYYYMMDD` / `MMDDYY` / slash dates → `YYYY-MM-DD`.
|
|
51
|
+
* Unparseable values return undefined so the caller can keep the raw string.
|
|
52
|
+
*/
|
|
53
|
+
export declare function parseAamvaDate(value: string, preferPast?: boolean): string | undefined;
|
|
54
|
+
export declare function parseAamvaElements(text: string): Record<string, string>;
|
|
55
|
+
export declare function fieldsFromAamvaElements(rawElements: Record<string, string>): DriverLicenseFields;
|
|
56
|
+
export declare function parseAamvaPayload(text: string): AamvaParseResult;
|
|
57
|
+
export declare function emptyFieldSources(): Partial<Record<DriverLicenseFieldKey, DriverLicenseFieldSource>>;
|
|
58
|
+
export declare function assignFieldSource(sources: Partial<Record<DriverLicenseFieldKey, DriverLicenseFieldSource>>, fields: DriverLicenseFields, source: DriverLicenseFieldSource): Partial<Record<keyof DriverLicenseFields, DriverLicenseFieldSource>>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ImageSource } from './types';
|
|
2
|
+
export type Pdf417DecodeSource = 'barcode-detector' | 'zxing-wasm';
|
|
3
|
+
export interface Pdf417DecodeResult {
|
|
4
|
+
text: string;
|
|
5
|
+
source: Pdf417DecodeSource;
|
|
6
|
+
}
|
|
7
|
+
export declare function pdf417BarcodeDetectorSupported(): Promise<boolean>;
|
|
8
|
+
/**
|
|
9
|
+
* PDF417 from a still. Prefers `BarcodeDetector` when the UA lists `pdf417`,
|
|
10
|
+
* otherwise zxing-wasm with the packaged reader wasm.
|
|
11
|
+
*/
|
|
12
|
+
export declare function decodePdf417(source: ImageSource): Promise<Pdf417DecodeResult | undefined>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type DriverLicenseFieldKey, type DriverLicenseFieldSource, type DriverLicenseFields, type ScanUseCase } from './aamva';
|
|
2
|
+
import { type ScanOptions } from './scan';
|
|
3
|
+
import type { DocumentDetectionResult, DocumentQuad, OcrResult } from './types';
|
|
4
|
+
export type { DriverLicenseFieldKey, DriverLicenseFieldSource, DriverLicenseFields, ScanUseCase };
|
|
5
|
+
export type DriverLicenseStatus = 'success' | 'frontFailed' | 'backFailed' | 'barcodeUnreadable';
|
|
6
|
+
export interface DriverLicenseResult {
|
|
7
|
+
status: DriverLicenseStatus;
|
|
8
|
+
fields: DriverLicenseFields;
|
|
9
|
+
rawElements: Record<string, string>;
|
|
10
|
+
sources: Partial<Record<DriverLicenseFieldKey, DriverLicenseFieldSource>>;
|
|
11
|
+
front: {
|
|
12
|
+
canvas?: HTMLCanvasElement;
|
|
13
|
+
ocr?: OcrResult;
|
|
14
|
+
quad?: DocumentQuad;
|
|
15
|
+
};
|
|
16
|
+
back: {
|
|
17
|
+
canvas?: HTMLCanvasElement;
|
|
18
|
+
barcodeText?: string;
|
|
19
|
+
};
|
|
20
|
+
message?: string;
|
|
21
|
+
}
|
|
22
|
+
export type DriverLicenseStep = {
|
|
23
|
+
kind: 'need-back';
|
|
24
|
+
front: DriverLicenseResult['front'];
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'retry';
|
|
27
|
+
status: 'frontFailed' | 'backFailed';
|
|
28
|
+
message: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'complete';
|
|
31
|
+
result: DriverLicenseResult;
|
|
32
|
+
};
|
|
33
|
+
export interface DriverLicenseSessionOptions {
|
|
34
|
+
scan?: ScanOptions;
|
|
35
|
+
onDriverLicense?: (result: DriverLicenseResult) => void | Promise<void>;
|
|
36
|
+
onCancel?: () => void;
|
|
37
|
+
}
|
|
38
|
+
export interface DriverLicenseSession {
|
|
39
|
+
readonly side: 'front' | 'back';
|
|
40
|
+
submitStill(frame: HTMLCanvasElement, detection?: DocumentDetectionResult): Promise<DriverLicenseStep>;
|
|
41
|
+
cancel(): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function driverLicenseFieldsFromOcr(ocr: OcrResult): DriverLicenseFields;
|
|
44
|
+
export declare function mergeDriverLicenseFields(barcode: DriverLicenseFields, ocr: DriverLicenseFields): {
|
|
45
|
+
fields: DriverLicenseFields;
|
|
46
|
+
sources: DriverLicenseResult['sources'];
|
|
47
|
+
};
|
|
48
|
+
export declare function processDriverLicenseFront(frame: HTMLCanvasElement, detection?: DocumentDetectionResult, scan?: ScanOptions): Promise<Extract<DriverLicenseStep, {
|
|
49
|
+
kind: 'need-back' | 'retry';
|
|
50
|
+
}>>;
|
|
51
|
+
export declare function processDriverLicenseBack(frame: HTMLCanvasElement, front: DriverLicenseResult['front'], scan?: ScanOptions): Promise<DriverLicenseResult>;
|
|
52
|
+
export declare function driverLicensePrompt(side: 'front' | 'back', usingSystemCamera: boolean): {
|
|
53
|
+
title: string;
|
|
54
|
+
headline: string;
|
|
55
|
+
detail: string;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Two-sided US/Canada AAMVA session. Front is find + OCR. Back is PDF417.
|
|
59
|
+
* Barcode wins; front OCR fills empty fields only.
|
|
60
|
+
*
|
|
61
|
+
* @throws {LicenseError} when `scan` or `ocr` is not licensed.
|
|
62
|
+
*/
|
|
63
|
+
export declare function startDriverLicenseSession(options?: DriverLicenseSessionOptions): Promise<DriverLicenseSession>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zxing-wasm reader 3.1.6. PDF417 fallback when BarcodeDetector cannot
|
|
3
|
+
* decode it. Literal `new URL` is what Webpack and Turbopack copy.
|
|
4
|
+
*/
|
|
5
|
+
export declare const packagedZxingReaderWasm: URL;
|
|
6
|
+
export declare const PINNED_ZXING_WASM_VERSION: "3.1.6";
|
|
7
|
+
export declare const ZXING_READER_WASM_FILENAME = "zxing_reader.wasm";
|
|
8
|
+
export declare const PREFERRED_ZXING_CDN_BASE: string;
|
|
9
|
+
export declare const LAST_RESORT_ZXING_CDN_BASE: string;
|
|
10
|
+
export declare function joinZxingCdnUrl(base: string, fileName: string): string;
|
|
11
|
+
export declare function pinnedZxingReaderWasmUrl(base?: string): string;
|
|
12
|
+
export declare function packagedZxingReaderWasmUrl(): string;
|
|
13
|
+
/** Injected only for Node tests so jsDelivr is not required. */
|
|
14
|
+
export declare function setZxingReaderWasmForTests(value?: {
|
|
15
|
+
binary?: ArrayBuffer;
|
|
16
|
+
url?: string;
|
|
17
|
+
}): void;
|
|
18
|
+
export declare function zxingReaderWasmForTests(): {
|
|
19
|
+
binary: ArrayBuffer | undefined;
|
|
20
|
+
url: string | undefined;
|
|
21
|
+
};
|
|
22
|
+
export declare function resolveUsableZxingReaderWasmUrl(fetchImpl?: typeof fetch): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exige/web-scanner-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.30",
|
|
4
4
|
"description": "@exige/web-scanner-sdk is a client-side web document scanner SDK. ML document finding, OCR, and redaction run in the browser. The public demo is the only path that does not need an account. A hostname you control needs a signed license key.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"./assets/tesseract/tesseract-core-simd-lstm.wasm.js": "./dist/assets/tesseract/tesseract-core-simd-lstm.wasm.js",
|
|
36
36
|
"./assets/tesseract/tesseract-core-relaxedsimd-lstm.wasm.js": "./dist/assets/tesseract/tesseract-core-relaxedsimd-lstm.wasm.js",
|
|
37
37
|
"./assets/tesseract/eng.traineddata.gz": "./dist/assets/tesseract/eng.traineddata.gz",
|
|
38
|
+
"./assets/zxing_reader.wasm": "./dist/assets/zxing_reader.wasm",
|
|
38
39
|
"./assets-manifest.json": "./dist/assets-manifest.json"
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|
|
@@ -62,7 +63,8 @@
|
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
65
|
"onnxruntime-web": "1.30.0",
|
|
65
|
-
"tesseract.js": "^7.0.0"
|
|
66
|
+
"tesseract.js": "^7.0.0",
|
|
67
|
+
"zxing-wasm": "3.1.6"
|
|
66
68
|
},
|
|
67
69
|
"peerDependencies": {
|
|
68
70
|
"react": ">=18"
|