{"version":3,"file":"tesseract_js.cjs","names":[],"sources":["../../../../src/batteries/media/engines/tesseract_js.ts"],"sourcesContent":["/**\n * A cross-environment OCR {@link @nhtio/adk/batteries/media/contracts!MediaEngine}\n * backed by tesseract.js (WASM Tesseract — Node and browsers, no binary).\n *\n * @module @nhtio/adk/batteries/media/engines/tesseract_js\n *\n * @remarks\n * The local-by-default OCR engine. Language data files download on first use of each\n * language; point `langPath`/`cachePath` at a writable location to control where (the\n * factory requires `languages` up front so a deployment never silently fetches packs for\n * languages it didn't plan for). Workers are created per recognize call and terminated in\n * `finally` — slower than a pooled setup but leak-free by construction; consumers who need\n * pooling can BYO an engine that holds a worker.\n *\n * `tesseract.js` is an optional peer dependency, lazily imported on first actual use.\n *\n * Declares one convert capability: `image/*` to `txt`/`hocr`/`json` (OCR is a format\n * conversion — image in, text out). Language hints ride `request.options.languages`.\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { E_INVALID_MEDIA_PIPELINE_CONFIG } from '../exceptions'\nimport type * as TesseractNS from 'tesseract.js'\nimport type { MediaEngine, ConvertRequest, ConvertResult } from '../contracts'\n\ntype TesseractModule = typeof TesseractNS\n\n/** Options for {@link tesseractJsEngine}. */\nexport interface TesseractJsEngineOptions {\n  /** Languages to load, e.g. `['eng']`. REQUIRED — language packs download on first use. */\n  languages: readonly string[]\n  /** Where language data is fetched from / cached (forwarded to createWorker). */\n  langPath?: string\n  /** Local cache directory for downloaded language data. */\n  cachePath?: string\n  /** Override the module resolution. Default: `import('tesseract.js')`. */\n  tesseract?: () => TesseractModule | Promise<TesseractModule>\n}\n\n/**\n * Construct the tesseract.js OCR engine.\n *\n * @param options - Languages (required) and data-path overrides.\n * @returns The engine.\n */\nexport const tesseractJsEngine = (options: TesseractJsEngineOptions): MediaEngine => {\n  if (!Array.isArray(options?.languages) || options.languages.length === 0) {\n    throw new E_INVALID_MEDIA_PIPELINE_CONFIG([\n      'tesseractJsEngine requires languages (e.g. [\"eng\"]) — language packs download on first use and are never chosen silently',\n    ])\n  }\n  let modPromise: Promise<TesseractModule> | undefined\n  const getModule = (): Promise<TesseractModule> => {\n    modPromise ??= Promise.resolve(\n      options.tesseract ? options.tesseract() : import('tesseract.js')\n    ).catch((err) => {\n      const detail = isError(err) ? err.message : String(err)\n      throw new E_INVALID_MEDIA_PIPELINE_CONFIG([\n        `the tesseract.js engine could not load its peer dependency \"tesseract.js\": ${detail} — install it (pnpm add tesseract.js)`,\n      ])\n    })\n    return modPromise\n  }\n\n  const convert = async (request: ConvertRequest): Promise<ConvertResult> => {\n    const mod = await getModule()\n    const languages = (request.options?.languages ?? options.languages) as string[]\n    const worker = await mod.createWorker(languages, undefined, {\n      ...(options.langPath ? { langPath: options.langPath } : {}),\n      ...(options.cachePath ? { cachePath: options.cachePath } : {}),\n    })\n    try {\n      const result = await worker.recognize(\n        // tesseract.js accepts Buffer/Blob/ImageLike; raw bytes work via Buffer in Node\n        typeof globalThis.Buffer !== 'undefined'\n          ? globalThis.Buffer.from(\n              request.bytes.buffer,\n              request.bytes.byteOffset,\n              request.bytes.byteLength\n            )\n          : new Blob([request.bytes as BlobPart], { type: request.mimeType })\n      )\n      let text = result.data.text\n      let mimeType = 'text/plain'\n      if (request.to === 'json') {\n        text = JSON.stringify({ text: result.data.text, confidence: result.data.confidence })\n        mimeType = 'application/json'\n      } else if (request.to === 'hocr') {\n        // hocr requires opting into the output at recognize time in tesseract.js v5+;\n        // fall back to text when unavailable rather than failing the read.\n        const hocr = (result.data as { hocr?: string | null }).hocr\n        text = hocr ?? result.data.text\n        mimeType = hocr ? 'text/html' : 'text/plain'\n      }\n      return { outputs: [{ bytes: new TextEncoder().encode(text), mimeType }] }\n    } finally {\n      await worker.terminate()\n    }\n  }\n\n  return {\n    id: 'tesseract.js',\n    converts: [\n      {\n        from: ['image/*'],\n        to: ['txt', 'hocr', 'json'],\n        convert,\n      },\n    ],\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,IAAa,qBAAqB,YAAmD;CACnF,IAAI,CAAC,MAAM,QAAQ,SAAS,SAAS,KAAK,QAAQ,UAAU,WAAW,GACrE,MAAM,IAAI,mBAAA,gCAAgC,CACxC,4HACF,CAAC;CAEH,IAAI;CACJ,MAAM,kBAA4C;EAChD,eAAe,QAAQ,QACrB,QAAQ,YAAY,QAAQ,UAAU,IAAI,OAAO,eACnD,EAAE,OAAO,QAAQ;GAEf,MAAM,IAAI,mBAAA,gCAAgC,CACxC,8EAFa,eAAA,QAAQ,GAAG,IAAI,IAAI,UAAU,OAAO,GAAG,EAEiC,sCACvF,CAAC;EACH,CAAC;EACD,OAAO;CACT;CAEA,MAAM,UAAU,OAAO,YAAoD;EACzE,MAAM,MAAM,MAAM,UAAU;EAC5B,MAAM,YAAa,QAAQ,SAAS,aAAa,QAAQ;EACzD,MAAM,SAAS,MAAM,IAAI,aAAa,WAAW,KAAA,GAAW;GAC1D,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;GACzD,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;EAC9D,CAAC;EACD,IAAI;GACF,MAAM,SAAS,MAAM,OAAO,UAE1B,OAAO,WAAW,WAAW,cACzB,WAAW,OAAO,KAChB,QAAQ,MAAM,QACd,QAAQ,MAAM,YACd,QAAQ,MAAM,UAChB,IACA,IAAI,KAAK,CAAC,QAAQ,KAAiB,GAAG,EAAE,MAAM,QAAQ,SAAS,CAAC,CACtE;GACA,IAAI,OAAO,OAAO,KAAK;GACvB,IAAI,WAAW;GACf,IAAI,QAAQ,OAAO,QAAQ;IACzB,OAAO,KAAK,UAAU;KAAE,MAAM,OAAO,KAAK;KAAM,YAAY,OAAO,KAAK;IAAW,CAAC;IACpF,WAAW;GACb,OAAO,IAAI,QAAQ,OAAO,QAAQ;IAGhC,MAAM,OAAQ,OAAO,KAAkC;IACvD,OAAO,QAAQ,OAAO,KAAK;IAC3B,WAAW,OAAO,cAAc;GAClC;GACA,OAAO,EAAE,SAAS,CAAC;IAAE,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI;IAAG;GAAS,CAAC,EAAE;EAC1E,UAAU;GACR,MAAM,OAAO,UAAU;EACzB;CACF;CAEA,OAAO;EACL,IAAI;EACJ,UAAU,CACR;GACE,MAAM,CAAC,SAAS;GAChB,IAAI;IAAC;IAAO;IAAQ;GAAM;GAC1B;EACF,CACF;CACF;AACF"}